mirror of
https://github.com/hairyhenderson/go-onerng.git
synced 2025-04-20 23:45:03 -05:00
a bunch of updates, including verify function
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
This commit is contained in:
parent
946aa8c3f3
commit
008a314137
26
cmd/image.go
26
cmd/image.go
@ -3,6 +3,9 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/hairyhenderson/go-onerng"
|
"github.com/hairyhenderson/go-onerng"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -10,17 +13,34 @@ import (
|
|||||||
|
|
||||||
// imageCmd represents the image command
|
// imageCmd represents the image command
|
||||||
func imageCmd(ctx context.Context) *cobra.Command {
|
func imageCmd(ctx context.Context) *cobra.Command {
|
||||||
return &cobra.Command{
|
var imgOut string
|
||||||
|
cmd := &cobra.Command{
|
||||||
Use: "image",
|
Use: "image",
|
||||||
Short: "Dump the OneRNG's firmware image",
|
Short: "Dump the OneRNG's firmware image",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
o := onerng.OneRNG{Path: opts.Device}
|
o := onerng.OneRNG{Path: opts.Device}
|
||||||
|
err := o.Init(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "init failed before image extraction")
|
||||||
|
}
|
||||||
image, err := o.Image(ctx)
|
image, err := o.Image(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("%q\n", image)
|
var out *os.File
|
||||||
return nil
|
if imgOut == "-" {
|
||||||
|
out = os.Stdout
|
||||||
|
} else {
|
||||||
|
out, err = os.OpenFile(imgOut, os.O_RDWR|os.O_CREATE, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err := out.Write(image)
|
||||||
|
fmt.Fprintf(os.Stderr, "Wrote %db to %s\n", n, imgOut)
|
||||||
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cmd.Flags().StringVarP(&imgOut, "out", "o", "onerng.img", "output file for image (use - for stdout)")
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
21
cmd/init.go
Normal file
21
cmd/init.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hairyhenderson/go-onerng"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initCmd represents the init command
|
||||||
|
func initCmd(ctx context.Context) *cobra.Command {
|
||||||
|
return &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "Initialize the RNG",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
o := onerng.OneRNG{Path: opts.Device}
|
||||||
|
err := o.Init(ctx)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,7 @@ func initConfig(ctx context.Context, cmd *cobra.Command) {
|
|||||||
idCmd(ctx),
|
idCmd(ctx),
|
||||||
flushCmd(ctx),
|
flushCmd(ctx),
|
||||||
imageCmd(ctx),
|
imageCmd(ctx),
|
||||||
|
initCmd(ctx),
|
||||||
)
|
)
|
||||||
|
|
||||||
if cfgFile != "" { // enable ability to specify config file via flag
|
if cfgFile != "" { // enable ability to specify config file via flag
|
||||||
@ -63,7 +64,7 @@ func initConfig(ctx context.Context, cmd *cobra.Command) {
|
|||||||
|
|
||||||
// If a config file is found, read it in.
|
// If a config file is found, read it in.
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/hairyhenderson/go-onerng"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,7 +82,17 @@ func verifyCmd(ctx context.Context) *cobra.Command {
|
|||||||
Use: "verify",
|
Use: "verify",
|
||||||
Short: "Verify that OneRNG's firmware has not been tampered with.",
|
Short: "Verify that OneRNG's firmware has not been tampered with.",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
o := onerng.OneRNG{Path: opts.Device}
|
||||||
|
err := o.Init(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "init failed before image verification")
|
||||||
|
}
|
||||||
|
image, err := o.Image(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "image extraction failed before verification")
|
||||||
|
}
|
||||||
|
err = onerng.Verify(ctx, bytes.NewBuffer(image), publicKey)
|
||||||
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
162
onerng.go
162
onerng.go
@ -3,9 +3,12 @@ package onerng
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -38,22 +41,16 @@ func (o *OneRNG) Version(ctx context.Context) (int, error) {
|
|||||||
}
|
}
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
|
err = o.cmd(ctx, d, CmdPause)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
_, cancel := context.WithCancel(ctx)
|
_, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
buf := make(chan string)
|
buf := make(chan string)
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go scan(ctx, d, buf, errc)
|
||||||
defer close(buf)
|
|
||||||
defer close(errc)
|
|
||||||
scanner := bufio.NewScanner(d)
|
|
||||||
for scanner.Scan() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case buf <- scanner.Text():
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = o.cmd(ctx, d, CmdSilent, CmdVersion, CmdRun)
|
err = o.cmd(ctx, d, CmdSilent, CmdVersion, CmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,18 +97,7 @@ func (o *OneRNG) Identify(ctx context.Context) (string, error) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
buf := make(chan string)
|
buf := make(chan string)
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go scan(ctx, d, buf, errc)
|
||||||
defer close(buf)
|
|
||||||
defer close(errc)
|
|
||||||
scanner := bufio.NewScanner(d)
|
|
||||||
for scanner.Scan() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case buf <- scanner.Text():
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = o.cmd(ctx, d, CmdSilent, CmdID, CmdRun)
|
err = o.cmd(ctx, d, CmdSilent, CmdID, CmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -166,30 +152,18 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|
||||||
|
err = o.cmd(ctx, d, CmdPause, CmdSilent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
_, cancel := context.WithCancel(ctx)
|
_, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
buf := make(chan []byte)
|
buf := make(chan []byte)
|
||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go func() {
|
go stream(ctx, d, 4, buf, errc)
|
||||||
defer close(buf)
|
|
||||||
defer close(errc)
|
|
||||||
b := make([]byte, 128)
|
|
||||||
for {
|
|
||||||
n, err := d.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case buf <- b:
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = o.cmd(ctx, d, CmdSilent, CmdImage, CmdRun)
|
err = o.cmd(ctx, d, CmdSilent, CmdImage, CmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -197,6 +171,7 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
image := []byte{}
|
image := []byte{}
|
||||||
|
zeros := 0
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
var b []byte
|
var b []byte
|
||||||
@ -204,10 +179,17 @@ loop:
|
|||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
case b = <-buf:
|
case b = <-buf:
|
||||||
copy(image, b)
|
image = append(image, b...)
|
||||||
if len(b) == 0 {
|
for _, v := range b {
|
||||||
|
if v == 0 {
|
||||||
|
zeros++
|
||||||
|
} else {
|
||||||
|
zeros = 0
|
||||||
|
}
|
||||||
|
if zeros > 200 {
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -220,3 +202,91 @@ loop:
|
|||||||
|
|
||||||
return image, err
|
return image, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init - wait for the device to finish initializing and start returning data
|
||||||
|
func (o *OneRNG) Init(ctx context.Context) error {
|
||||||
|
i := 0
|
||||||
|
for ; i < 200; i++ {
|
||||||
|
n, err := o.readData(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Initialized after %d loops\n", i)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readData - try to read some data from the RNG
|
||||||
|
func (o *OneRNG) readData(ctx context.Context) (int, error) {
|
||||||
|
d, err := os.OpenFile(o.Path, os.O_RDWR, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
|
||||||
|
_, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
buf := make(chan []byte)
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
go stream(ctx, d, 1, buf, errc)
|
||||||
|
|
||||||
|
err = o.cmd(ctx, d, CmdAvalanche, CmdRun)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we always end with a pause/silence/flush
|
||||||
|
defer o.cmd(ctx, d, CmdPause, CmdSilent, CmdFlush)
|
||||||
|
|
||||||
|
// blocking read from the channel, with a timeout (from context)
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
case b := <-buf:
|
||||||
|
return len(b), nil
|
||||||
|
case err := <-errc:
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream from a file into a channel until an error is encountered, the channel
|
||||||
|
// is closed, or the context is cancelled.
|
||||||
|
func stream(ctx context.Context, d *os.File, bs int, buf chan []byte, errc chan error) {
|
||||||
|
defer close(buf)
|
||||||
|
defer close(errc)
|
||||||
|
for {
|
||||||
|
b := make([]byte, bs)
|
||||||
|
n, err := io.ReadAtLeast(d, b, len(b))
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n < len(b) {
|
||||||
|
errc <- errors.Errorf("unexpected short read - wanted %db, read %db", len(b), n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case buf <- b:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scan(ctx context.Context, d *os.File, buf chan string, errc chan error) {
|
||||||
|
defer close(buf)
|
||||||
|
defer close(errc)
|
||||||
|
scanner := bufio.NewScanner(d)
|
||||||
|
for scanner.Scan() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case buf <- scanner.Text():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
140
verify.go
Normal file
140
verify.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package onerng
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/crypto/openpgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// looks like:
|
||||||
|
// fe ed be ef 20 14 - magic number
|
||||||
|
// 00 00 04 - len (little endian)
|
||||||
|
// 00 00 - version (little endian)
|
||||||
|
// image - len bytes of image
|
||||||
|
type fwImage struct {
|
||||||
|
magic [6]byte // must be 0xfeedbeef2014
|
||||||
|
length [3]byte // LE
|
||||||
|
version [2]byte // LE
|
||||||
|
_ [1]byte //
|
||||||
|
fullimg [262144]byte // full image
|
||||||
|
// image [261536]byte // 256kb minus end offset
|
||||||
|
// slen [2]byte // signature length
|
||||||
|
// sig [543]byte // signature
|
||||||
|
// endOff [608]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify - this is a more-or-less straight port from the onerng_verify.py script
|
||||||
|
// distributed with the OneRNG package
|
||||||
|
func Verify(ctx context.Context, image io.Reader, pubkey string) error {
|
||||||
|
var x byte
|
||||||
|
length := 0
|
||||||
|
version := 0
|
||||||
|
state := int8(0)
|
||||||
|
for {
|
||||||
|
c := make([]byte, 1)
|
||||||
|
n, err := io.ReadAtLeast(image, c, len(c))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return errors.Errorf("Short image")
|
||||||
|
}
|
||||||
|
x = c[0]
|
||||||
|
|
||||||
|
// 1. read magic (6 bytes)
|
||||||
|
if state == 0 && x == 0xfe {
|
||||||
|
state++
|
||||||
|
} else if state == 1 && x == 0xed {
|
||||||
|
state++
|
||||||
|
} else if state == 2 && x == 0xbe {
|
||||||
|
state++
|
||||||
|
} else if state == 3 && x == 0xef {
|
||||||
|
state++
|
||||||
|
} else if state == 4 && x == 0x20 {
|
||||||
|
state++
|
||||||
|
} else if state == 5 && x == 0x14 {
|
||||||
|
state++
|
||||||
|
// 2. read length (3 bytes)
|
||||||
|
} else if state == 6 {
|
||||||
|
length = int(x)
|
||||||
|
state++
|
||||||
|
} else if state == 7 {
|
||||||
|
length = length | (int(x) << 8)
|
||||||
|
state++
|
||||||
|
} else if state == 8 {
|
||||||
|
length = length | (int(x) << 16)
|
||||||
|
state++
|
||||||
|
// 3. read version (2 bytes)
|
||||||
|
} else if state == 9 {
|
||||||
|
version = int(x)
|
||||||
|
state++
|
||||||
|
} else if state == 10 {
|
||||||
|
version = version | (int(x) << 8)
|
||||||
|
state++
|
||||||
|
} else if state == 11 {
|
||||||
|
// skip a padding byte I guess...
|
||||||
|
state++
|
||||||
|
} else if state == 12 {
|
||||||
|
// 4. read image
|
||||||
|
c := make([]byte, length)
|
||||||
|
n, err := io.ReadAtLeast(image, c, length)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != length {
|
||||||
|
return errors.Errorf("Bad image")
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine end offset
|
||||||
|
endOff := 0
|
||||||
|
if version >= 3 {
|
||||||
|
endOff = 680
|
||||||
|
} else {
|
||||||
|
endOff = 600
|
||||||
|
}
|
||||||
|
|
||||||
|
// read length of signature - 2 bytes between image and signature
|
||||||
|
x = c[length-endOff]
|
||||||
|
klen := int(x)
|
||||||
|
x = c[length-endOff+1]
|
||||||
|
klen = klen | (int(x) << 8)
|
||||||
|
|
||||||
|
// split last part into image (signed part) & signature
|
||||||
|
signature := bytes.NewBuffer(c[length-endOff+2 : length-endOff+2+klen])
|
||||||
|
signed := bytes.NewBuffer(c[0 : length-endOff])
|
||||||
|
|
||||||
|
// leftovers := c[length-endOff+2+klen : length]
|
||||||
|
// fmt.Fprintf(os.Stderr, "leftovers (%d):\n%#x\n", len(leftovers), leftovers)
|
||||||
|
|
||||||
|
// read public key
|
||||||
|
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(pubkey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify
|
||||||
|
signer, err := openpgp.CheckDetachedSignature(keyring, signed, signature)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to verify firmware signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "firmware verification passed OK - version=%d\n", version)
|
||||||
|
for _, id := range signer.Identities {
|
||||||
|
fmt.Fprintf(os.Stderr, "signed by: %#v\n", id.Name)
|
||||||
|
fmt.Fprintf(os.Stderr, "\tcreated: %q\n", id.SelfSignature.CreationTime)
|
||||||
|
fmt.Fprintf(os.Stderr, "\tfingerprint: %X\n", signer.PrimaryKey.Fingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// something didn't line up, so we need to begin again...
|
||||||
|
state = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user