diff --git a/cmd/image.go b/cmd/image.go index 9ba6f98..8c0d51d 100644 --- a/cmd/image.go +++ b/cmd/image.go @@ -3,6 +3,9 @@ package cmd import ( "context" "fmt" + "os" + + "github.com/pkg/errors" "github.com/hairyhenderson/go-onerng" "github.com/spf13/cobra" @@ -10,17 +13,34 @@ import ( // imageCmd represents the image command func imageCmd(ctx context.Context) *cobra.Command { - return &cobra.Command{ + var imgOut string + cmd := &cobra.Command{ Use: "image", Short: "Dump the OneRNG's firmware image", RunE: func(cmd *cobra.Command, args []string) error { 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) if err != nil { return err } - fmt.Printf("%q\n", image) - return nil + var out *os.File + 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 } diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..19883ef --- /dev/null +++ b/cmd/init.go @@ -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 + }, + } +} diff --git a/cmd/root.go b/cmd/root.go index 5720d2c..b5b8435 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,6 +51,7 @@ func initConfig(ctx context.Context, cmd *cobra.Command) { idCmd(ctx), flushCmd(ctx), imageCmd(ctx), + initCmd(ctx), ) 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 err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } diff --git a/cmd/verify.go b/cmd/verify.go index 71e7abe..d060324 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -1,8 +1,12 @@ package cmd import ( + "bytes" "context" + "github.com/pkg/errors" + + "github.com/hairyhenderson/go-onerng" "github.com/spf13/cobra" ) @@ -78,7 +82,17 @@ func verifyCmd(ctx context.Context) *cobra.Command { Use: "verify", Short: "Verify that OneRNG's firmware has not been tampered with.", 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 }, } } diff --git a/onerng.go b/onerng.go index c39eada..650e264 100644 --- a/onerng.go +++ b/onerng.go @@ -3,9 +3,12 @@ package onerng import ( "bufio" "context" + "fmt" + "io" "os" "strconv" "strings" + "time" "github.com/pkg/errors" ) @@ -38,22 +41,16 @@ func (o *OneRNG) Version(ctx context.Context) (int, error) { } defer d.Close() + err = o.cmd(ctx, d, CmdPause) + if err != nil { + return 0, err + } + _, cancel := context.WithCancel(ctx) defer cancel() buf := make(chan string) errc := make(chan error, 1) - go func() { - defer close(buf) - defer close(errc) - scanner := bufio.NewScanner(d) - for scanner.Scan() { - select { - case <-ctx.Done(): - return - case buf <- scanner.Text(): - } - } - }() + go scan(ctx, d, buf, errc) err = o.cmd(ctx, d, CmdSilent, CmdVersion, CmdRun) if err != nil { @@ -100,18 +97,7 @@ func (o *OneRNG) Identify(ctx context.Context) (string, error) { defer cancel() buf := make(chan string) errc := make(chan error, 1) - go func() { - defer close(buf) - defer close(errc) - scanner := bufio.NewScanner(d) - for scanner.Scan() { - select { - case <-ctx.Done(): - return - case buf <- scanner.Text(): - } - } - }() + go scan(ctx, d, buf, errc) err = o.cmd(ctx, d, CmdSilent, CmdID, CmdRun) if err != nil { @@ -166,30 +152,18 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) { } 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) defer cancel() buf := make(chan []byte) errc := make(chan error, 1) - go func() { - 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 - } - } - }() + go stream(ctx, d, 4, buf, errc) err = o.cmd(ctx, d, CmdSilent, CmdImage, CmdRun) if err != nil { @@ -197,6 +171,7 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) { } image := []byte{} + zeros := 0 loop: for { var b []byte @@ -204,9 +179,16 @@ loop: case <-ctx.Done(): return nil, ctx.Err() case b = <-buf: - copy(image, b) - if len(b) == 0 { - break loop + image = append(image, b...) + for _, v := range b { + if v == 0 { + zeros++ + } else { + zeros = 0 + } + if zeros > 200 { + break loop + } } case err := <-errc: return nil, err @@ -220,3 +202,91 @@ loop: 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(): + } + } +} diff --git a/verify.go b/verify.go new file mode 100644 index 0000000..a2ec490 --- /dev/null +++ b/verify.go @@ -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 +}