a bunch of updates, including verify function

Signed-off-by: Dave Henderson <dhenderson@gmail.com>
This commit is contained in:
Dave Henderson 2018-08-09 00:30:16 -04:00
parent 946aa8c3f3
commit 008a314137
No known key found for this signature in database
GPG Key ID: 765A97405DCE5AFA
6 changed files with 318 additions and 52 deletions

View File

@ -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
}

21
cmd/init.go Normal file
View 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
},
}
}

View File

@ -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())
}
}

View File

@ -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
},
}
}

164
onerng.go
View File

@ -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():
}
}
}

140
verify.go Normal file
View 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
}