diff --git a/.golangci.yml b/.golangci.yml index cf8696e..79a2e91 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,8 +12,6 @@ linters-settings: goconst: min-len: 2 min-occurrences: 4 - lll: - line-length: 140 nolintlint: allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) allow-unused: false # report any unused nolint directives @@ -29,37 +27,36 @@ linters: - depguard - dogsled - dupl - # - errcheck + - errcheck - exhaustive - exportloopref # - funlen - # - gci + - gci # - gochecknoglobals - gochecknoinits - # - gocognit + - gocognit - goconst - # - gocritic - # - gocyclo + - gocritic + - gocyclo # - godox - gofmt - gofumpt - goheader - goimports - golint - # - gomnd + - gomnd - gomodguard - goprintffuncname - # - gosec + - gosec - gosimple - # - govet + - govet - ineffassign - # - interfacer - # - lll + - interfacer - maligned - misspell - nakedret - # - nestif - # - nlreturn + - nestif + - nlreturn - noctx - nolintlint - prealloc @@ -71,7 +68,7 @@ linters: - stylecheck - typecheck - unconvert - # - unparam + - unparam - unused - varcheck - whitespace diff --git a/README.md b/README.md index dbe46df..3e849e7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ # go-onerng -This is a Go port of the OneRNG tools distributed at https://onerng.info/. Much credit is due to the OneRNG creators - all I'm doing here is porting a bunch of Bash and Python code to Go. +This is an unofficial Go version of the OneRNG tools distributed at https://onerng.info/. Much credit is due to the OneRNG creators - this all started as a port of a bunch of Bash and Python code to Go. + +The different commands available were discovered by reading [the firmware source code](https://github.com/OneRNG/firmware/blob/master/cdc_app.c#L346). ## Roadmap diff --git a/cmd/onerng/commands.go b/cmd/onerng/commands.go new file mode 100644 index 0000000..44ac9f5 --- /dev/null +++ b/cmd/onerng/commands.go @@ -0,0 +1,204 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "math" + "os" + "time" + + "github.com/hairyhenderson/go-onerng" + "github.com/spf13/cobra" +) + +func createORNG(cmd *cobra.Command) *onerng.OneRNG { + return &onerng.OneRNG{Path: cmd.Flag("device").Value.String()} +} + +func idCmd(cmd *cobra.Command, args []string) error { + o := createORNG(cmd) + id, err := o.Identify(cmd.Context()) + if err != nil { + return err + } + fmt.Printf("OneRNG Hardware ID: %s\n", id) + + return nil +} + +func versionCmd(cmd *cobra.Command, args []string) error { + o := createORNG(cmd) + version, err := o.Version(cmd.Context()) + if err != nil { + return err + } + fmt.Printf("OneRNG Hardware Version: %d\n", version) + + return nil +} + +func flushCmd(cmd *cobra.Command, args []string) error { + o := createORNG(cmd) + + return o.Flush(cmd.Context()) +} + +func initCmd(cmd *cobra.Command, args []string) error { + o := createORNG(cmd) + + return o.Init(cmd.Context()) +} + +func verifyCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + o := createORNG(cmd) + err := o.Init(ctx) + if err != nil { + return fmt.Errorf("init failed before image verification: %w", err) + } + image, err := o.Image(ctx) + if err != nil { + return fmt.Errorf("image extraction failed before verification: %w", err) + } + err = onerng.Verify(ctx, bytes.NewBuffer(image), publicKey) + + return err +} + +func imageCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + o := createORNG(cmd) + + err := o.Init(ctx) + if err != nil { + return fmt.Errorf("init failed before image extraction: %w", err) + } + image, err := o.Image(ctx) + if err != nil { + return err + } + + out := os.Stdout + imgOut := cmd.Flag("out").Value.String() + if imgOut != "-" { + out, err = os.OpenFile(imgOut, os.O_RDWR|os.O_CREATE, 0o644) + if err != nil { + return err + } + } + n, err := out.Write(image) + fmt.Fprintf(os.Stderr, "Wrote %db to %s\n", n, imgOut) + + return err +} + +func readFlags(cmd *cobra.Command) (onerng.NoiseMode, error) { + disableAvalanche, err := cmd.Flags().GetBool("disable-avalanche") + if err != nil { + return 0, err + } + + enableRF, err := cmd.Flags().GetBool("enable-rf") + if err != nil { + return 0, err + } + + disableWhitener, err := cmd.Flags().GetBool("disable-whitener") + if err != nil { + return 0, err + } + + // set flags based on commandline options + flags := onerng.Default + if disableAvalanche { + flags |= onerng.DisableAvalanche + } + if enableRF { + flags |= onerng.EnableRF + } + if disableWhitener { + flags |= onerng.DisableWhitener + } + + return flags, nil +} + +//nolint:gocyclo +func readCmd(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + o := createORNG(cmd) + err := o.Init(ctx) + if err != nil { + return fmt.Errorf("init failed before read: %w", err) + } + + enableAESWhiten, err := cmd.Flags().GetBool("aes-whitener") + if err != nil { + return err + } + count, err := cmd.Flags().GetInt64("count") + if err != nil { + return err + } + flags, err := readFlags(cmd) + if err != nil { + return err + } + + // waste some entropy... + devNull, err := os.OpenFile("/dev/null", os.O_WRONLY, 0o200) + if err != nil { + return err + } + wasteAmount := 10240 + _, err = o.Read(ctx, devNull, int64(wasteAmount), flags) + if err != nil { + fmt.Fprintf(os.Stderr, "warning: entropy wasteage failed or incomplete, continuing anyway\n") + } + + out := io.WriteCloser(os.Stdout) + readOut := cmd.Flag("out").Value.String() + if readOut != "-" { + out, err = os.OpenFile(readOut, os.O_RDWR|os.O_CREATE, 0o644) + if err != nil { + return err + } + } + + if enableAESWhiten { + out, err = o.AESWhitener(ctx, out) + if err != nil { + return err + } + } + + start := time.Now() + written, err := o.Read(ctx, out, count, flags) + delta := time.Since(start) + rate := float64(written) / delta.Seconds() + fmt.Fprintf(os.Stderr, "%s written in %s (%s/s)\n", humanizeBytes(float64(written)), delta, humanizeBytes(rate)) + + return err +} + +// humanizeBytes produces a human readable representation of an IEC size. +// Taken from github.com/dustin/go-humanize +//nolint:gomnd +func humanizeBytes(s float64) string { + base := 1024.0 + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} + if s < 10 { + return fmt.Sprintf("%f B", s) + } + + e := math.Floor(math.Log(s) / math.Log(base)) + suffix := sizes[int(e)] + val := math.Floor(s/math.Pow(base, e)*10+0.5) / 10 + f := "%.0f %s" + if val < 10 { + f = "%.1f %s" + } + + return fmt.Sprintf(f, val, suffix) +} diff --git a/cmd/onerng/flush.go b/cmd/onerng/flush.go deleted file mode 100644 index 38ff207..0000000 --- a/cmd/onerng/flush.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "context" - - "github.com/hairyhenderson/go-onerng" - "github.com/spf13/cobra" -) - -// flushCmd represents the flush command -func flushCmd(ctx context.Context) *cobra.Command { - return &cobra.Command{ - Use: "flush", - Short: "Flush the OneRNG's entropy pool", - RunE: func(cmd *cobra.Command, args []string) error { - o := onerng.OneRNG{Path: opts.Device} - return o.Flush(ctx) - }, - } -} diff --git a/cmd/onerng/id.go b/cmd/onerng/id.go deleted file mode 100644 index 61a46e6..0000000 --- a/cmd/onerng/id.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "context" - "fmt" - - "github.com/hairyhenderson/go-onerng" - "github.com/spf13/cobra" -) - -// idCmd represents the id command -func idCmd(ctx context.Context) *cobra.Command { - return &cobra.Command{ - Use: "id", - Short: "Display the OneRNG's hardware id", - RunE: func(cmd *cobra.Command, args []string) error { - o := onerng.OneRNG{Path: opts.Device} - id, err := o.Identify(ctx) - if err != nil { - return err - } - fmt.Printf("OneRNG Hardware ID: %s\n", id) - return nil - }, - } -} diff --git a/cmd/onerng/image.go b/cmd/onerng/image.go deleted file mode 100644 index edbc730..0000000 --- a/cmd/onerng/image.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/pkg/errors" - - "github.com/hairyhenderson/go-onerng" - "github.com/spf13/cobra" -) - -// imageCmd represents the image command -func imageCmd(ctx context.Context) *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 - } - 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/onerng/init.go b/cmd/onerng/init.go deleted file mode 100644 index 1604bf2..0000000 --- a/cmd/onerng/init.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -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/onerng/main.go b/cmd/onerng/main.go index 5042c66..0356967 100644 --- a/cmd/onerng/main.go +++ b/cmd/onerng/main.go @@ -7,18 +7,89 @@ import ( "fmt" "os" "os/signal" + + "github.com/hairyhenderson/go-onerng/version" + "github.com/spf13/cobra" ) -func main() { - ctx := context.Background() +func commands() *cobra.Command { + cmd := &cobra.Command{ + Use: "onerng [opts] COMMAND", + Short: "Tool for the OneRNG open source hardware entropy generator", + Long: `OneRNG is an open source hardware entropy generator in a USB dongle. + +This tool can be used to verify that the OneRNG device operates +correctly, and that the firmware has not been tampered with.`, + Version: version.Version, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + return nil + }, + } + cmd.PersistentFlags().StringP("device", "d", "/dev/ttyACM0", "the OneRNG device") + + flush := &cobra.Command{ + Use: "flush", + Short: "Flush the OneRNG's entropy pool", + RunE: flushCmd, + } + id := &cobra.Command{ + Use: "id", + Short: "Display the OneRNG's hardware id", + RunE: idCmd, + } + init := &cobra.Command{ + Use: "init", + Short: "Initialize the RNG", + RunE: initCmd, + } + verify := &cobra.Command{ + Use: "verify", + Short: "Verify that OneRNG's firmware has not been tampered with.", + RunE: verifyCmd, + } + version := &cobra.Command{ + Use: "version", + Short: "Display the OneRNG's hardware version", + RunE: versionCmd, + } + image := &cobra.Command{ + Use: "image", + Short: "Dump the OneRNG's firmware image", + RunE: imageCmd, + } + image.Flags().StringP("out", "o", "onerng.img", "output file for image (use - for stdout)") + + read := &cobra.Command{ + Use: "read", + Short: "read some random data from the OneRNG", + RunE: readCmd, + } + read.Flags().StringP("out", "o", "-", "output file for data (use - for stdout)") + read.Flags().Bool("disable-avalanche", false, "Disable noise generation from the Avalanche Diode") + read.Flags().Bool("enable-rf", false, "Enable noise generation from RF") + read.Flags().Bool("disable-whitener", false, "Disable the on-board CRC16 generator") + read.Flags().Int64P("count", "n", -1, "Read only N bytes (use -1 for unlimited)") + read.Flags().Bool("aes-whitener", true, "encrypt with AES-128 to 'whiten' the input stream with a random key obtained from the OneRNG") + + cmd.AddCommand(flush, id, init, image, read, verify, version) + + return cmd +} + +func main() { + returncode := 0 + defer func() { os.Exit(returncode) }() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - ctx, cancel := context.WithCancel(ctx) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) - defer func() { - signal.Stop(c) - cancel() - }() + defer signal.Stop(c) + go func() { select { case <-c: @@ -27,10 +98,9 @@ func main() { } }() - cmd := rootCmd(ctx) - initConfig(ctx, cmd) - if err := cmd.Execute(); err != nil { + cmd := commands() + if err := cmd.ExecuteContext(ctx); err != nil { fmt.Fprintln(os.Stderr, err) - os.Exit(1) + returncode = 1 } } diff --git a/cmd/onerng/verify.go b/cmd/onerng/public_key.go similarity index 83% rename from cmd/onerng/verify.go rename to cmd/onerng/public_key.go index 95a1219..d64462d 100644 --- a/cmd/onerng/verify.go +++ b/cmd/onerng/public_key.go @@ -1,17 +1,9 @@ package main -import ( - "bytes" - "context" - - "github.com/pkg/errors" - - "github.com/hairyhenderson/go-onerng" - "github.com/spf13/cobra" -) - -const ( - publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +// extracted from onerng_verify.py bundled in +// https://github.com/OneRNG/onerng.github.io/raw/master/sw/onerng_3.6-1_all.deb +// SHA256: a9ccf7b04ee317dbfc91518542301e2d60ebe205d38e80563f29aac7cd845ccb +const publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 mQINBFPXhxIBEADHeR56yhuF77hOErNk6LXTvbNIViVBG/Ss6cHJcnarnLjaGZ5y @@ -75,24 +67,3 @@ W2KHfJBcr1Ag0zZ5q1SoyMiqFmhgo0i+D58QIjtNw7JVyOYZPw== =IjnI -----END PGP PUBLIC KEY BLOCK----- ` -) - -func verifyCmd(ctx context.Context) *cobra.Command { - return &cobra.Command{ - Use: "verify", - Short: "Verify that OneRNG's firmware has not been tampered with.", - 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 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/cmd/onerng/read.go b/cmd/onerng/read.go deleted file mode 100644 index 6b02049..0000000 --- a/cmd/onerng/read.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "os" - "time" - - "github.com/pkg/errors" - - "github.com/dustin/go-humanize" - "github.com/hairyhenderson/go-onerng" - "github.com/spf13/cobra" -) - -func readCmd(ctx context.Context) *cobra.Command { - readOut := "" - disableAvalanche := false - enableRF := false - disableWhitener := false - enableAESWhiten := true - - count := int64(-1) - cmd := &cobra.Command{ - Use: "read", - Short: "read some random data from the OneRNG", - 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") - } - - // set flags based on commandline options - flags := onerng.Default - if disableAvalanche { - flags |= onerng.DisableAvalanche - } - if enableRF { - flags |= onerng.EnableRF - } - if disableWhitener { - flags |= onerng.DisableWhitener - } - - // waste some entropy... - devNull, err := os.OpenFile("/dev/null", os.O_WRONLY, 0200) - if err != nil { - return err - } - _, err = o.Read(ctx, devNull, 10*1024, flags) - if err != nil { - fmt.Fprintf(os.Stderr, "warning: entropy wasteage failed or incomplete, continuing anyway\n") - } - - var out io.WriteCloser - if readOut == "-" { - out = os.Stdout - } else { - out, err = os.OpenFile(readOut, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return err - } - } - - if enableAESWhiten { - out, err = o.AESWhitener(ctx, out) - if err != nil { - return err - } - } - - start := time.Now() - written, err := o.Read(ctx, out, count, flags) - delta := time.Since(start) - rate := float64(written) / delta.Seconds() - fmt.Fprintf(os.Stderr, "%s written in %s (%s/s)\n", humanize.Bytes(uint64(written)), delta, humanize.Bytes(uint64(rate))) - return err - }, - } - cmd.Flags().StringVarP(&readOut, "out", "o", "-", "output file for data (use - for stdout)") - cmd.Flags().BoolVar(&disableAvalanche, "disable-avalanche", false, "Disable noise generation from the Avalanche Diode") - cmd.Flags().BoolVar(&enableRF, "enable-rf", false, "Enable noise generation from RF") - cmd.Flags().BoolVar(&disableWhitener, "disable-whitener", false, "Disable the on-board CRC16 generator") - cmd.Flags().Int64VarP(&count, "count", "n", -1, "Read only N bytes (use -1 for unlimited)") - cmd.Flags().BoolVar(&enableAESWhiten, "aes-whitener", true, "encrypt with AES-128 to 'whiten' the input stream with a random key obtained from the OneRNG") - return cmd -} diff --git a/cmd/onerng/root.go b/cmd/onerng/root.go deleted file mode 100644 index ba9f424..0000000 --- a/cmd/onerng/root.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var ( - cfgFile string - opts config -) - -func rootCmd(ctx context.Context) *cobra.Command { - return &cobra.Command{ - Use: "onerng [opts] COMMAND", - Short: "Tool for the OneRNG open source hardware entropy generator", - Long: `OneRNG is an open source hardware entropy generator in a USB dongle. - -This tool can be used to verify that the OneRNG device operates -correctly, and that the firmware has not been tampered with.`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - cmd.SilenceErrors = true - cmd.SilenceUsage = true - return nil - }, - } -} - -func initConfig(ctx context.Context, cmd *cobra.Command) { - cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-onerng.yaml)") - opts = config{} - cmd.PersistentFlags().StringVarP(&opts.Device, "device", "d", "/dev/ttyACM0", "the OneRNG device") - - cmd.AddCommand( - verifyCmd(ctx), - versionCmd(ctx), - idCmd(ctx), - flushCmd(ctx), - imageCmd(ctx), - initCmd(ctx), - readCmd(ctx), - ) - - if cfgFile != "" { // enable ability to specify config file via flag - viper.SetConfigFile(cfgFile) - } - - viper.SetConfigName(".go-onerng") // name of config file (without extension) - viper.AddConfigPath(os.Getenv("HOME")) // adding home directory as first search path - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} - -type config struct { - Device string -} diff --git a/cmd/onerng/version.go b/cmd/onerng/version.go deleted file mode 100644 index 5b3b0cc..0000000 --- a/cmd/onerng/version.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "context" - "fmt" - - "github.com/hairyhenderson/go-onerng" - "github.com/spf13/cobra" -) - -// versionCmd represents the version command -func versionCmd(ctx context.Context) *cobra.Command { - return &cobra.Command{ - Use: "version", - Short: "Display the OneRNG's hardware version", - RunE: func(cmd *cobra.Command, args []string) error { - o := &onerng.OneRNG{Path: opts.Device} - version, err := o.Version(ctx) - if err != nil { - return err - } - fmt.Printf("OneRNG Hardware Version: %d\n", version) - return nil - }, - } -} diff --git a/go.mod b/go.mod index 9f141ab..f65e894 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,7 @@ module github.com/hairyhenderson/go-onerng go 1.15 require ( - github.com/dustin/go-humanize v1.0.0 - github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.1.1 - github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.6.1 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 ) diff --git a/go.sum b/go.sum index 4220796..092b288 100644 --- a/go.sum +++ b/go.sum @@ -11,7 +11,6 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -37,10 +36,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -68,7 +64,6 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -89,7 +84,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -100,7 +94,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -112,7 +105,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -125,19 +117,15 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -157,33 +145,25 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -252,11 +232,9 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -304,13 +282,11 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/onerng.go b/onerng.go index f654ce1..996307b 100644 --- a/onerng.go +++ b/onerng.go @@ -35,14 +35,13 @@ import ( "context" "crypto/aes" "crypto/cipher" + "fmt" "io" mrand "math/rand" "os" "strconv" "strings" "time" - - "github.com/pkg/errors" ) // OneRNG - a OneRNG device @@ -51,6 +50,8 @@ type OneRNG struct { device io.ReadWriteCloser } +const copyReadTimeout = 500 * time.Millisecond + // cmd sends one or more commands to the OneRNG. The device is not closed on // completion, as it's usually being read from simultaneously. func (o *OneRNG) cmd(ctx context.Context, c ...string) error { @@ -61,7 +62,7 @@ func (o *OneRNG) cmd(ctx context.Context, c ...string) error { for _, v := range c { _, err = o.device.Write([]byte(v)) if err != nil { - return errors.Wrapf(err, "Errored on command %q", v) + return fmt.Errorf("errored on command %q: %w", v, err) } select { case <-ctx.Done(): @@ -69,6 +70,7 @@ func (o *OneRNG) cmd(ctx context.Context, c ...string) error { default: } } + return nil } @@ -79,6 +81,7 @@ func (o *OneRNG) open() (err error) { return nil } o.device, err = os.OpenFile(o.Path, os.O_RDWR, 0600) + return err } @@ -89,6 +92,7 @@ func (o *OneRNG) close() error { } err := o.device.Close() o.device = nil + return err } @@ -105,7 +109,7 @@ func (o *OneRNG) Version(ctx context.Context) (int, error) { return 0, err } - _, cancel := context.WithCancel(ctx) + ctx, cancel := context.WithCancel(ctx) defer cancel() buf := make(chan string) errc := make(chan error, 1) @@ -127,9 +131,10 @@ loop: if strings.HasPrefix(b, "Version ") { verString = b cancel() + break loop } - case err := <-errc: + case err = <-errc: return 0, err } } @@ -141,6 +146,7 @@ loop: n := strings.Replace(verString, "Version ", "", 1) version, err := strconv.Atoi(n) + return version, err } @@ -174,9 +180,10 @@ loop: if strings.HasPrefix(b, "___") { idString = b cancel() + break loop } - case err := <-errc: + case err = <-errc: return "", err } } @@ -199,14 +206,15 @@ func (o *OneRNG) Flush(ctx context.Context) error { _, cancel := context.WithCancel(ctx) defer cancel() - err = o.cmd(ctx, cmdFlush) - return err + + return o.cmd(ctx, cmdFlush) } // Image extracts the firmware image. This image is padded with random data to // either 128Kb or 256Kb (depending on hardware), and signed. // // See also the Verify function. +//nolint:gocyclo func (o *OneRNG) Image(ctx context.Context) ([]byte, error) { err := o.open() if err != nil { @@ -219,9 +227,7 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) { return nil, err } - time.Sleep(2 * time.Second) - - _, cancel := context.WithCancel(ctx) + ctx, cancel := context.WithCancel(ctx) defer cancel() buf := make(chan []byte) errc := make(chan error, 1) @@ -234,25 +240,24 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) { image := []byte{} zeros := 0 -loop: - for { + // stream data until we're done, or until we have 200+ consecutive zeroes + //nolint:gomnd + for zeros <= 200 { var b []byte select { case <-ctx.Done(): return nil, ctx.Err() case b = <-buf: image = append(image, b...) + // count consecutive zeroes - if we hit non-zero, reset for _, v := range b { if v == 0 { zeros++ } else { zeros = 0 } - if zeros > 200 { - break loop - } } - case err := <-errc: + case err = <-errc: return nil, err } } @@ -278,6 +283,7 @@ func (o *OneRNG) Init(ctx context.Context) error { } } // fmt.Fprintf(os.Stderr, "Initialized after %d loops\n", i) + return nil } @@ -298,13 +304,15 @@ func (o *OneRNG) Read(ctx context.Context, out io.Writer, n int64, flags NoiseMo return 0, err } + //nolint:errcheck defer o.cmd(ctx, cmdPause) written, err = copyWithContext(ctx, out, o.device, n) + return written, err } -// readData - try to read some data from the RNG +// readData - try to read some data from the RNG (during initialization) func (o *OneRNG) readData(ctx context.Context) (int, error) { err := o.open() if err != nil { @@ -312,7 +320,8 @@ func (o *OneRNG) readData(ctx context.Context) (int, error) { } defer o.close() - _, cancel := context.WithTimeout(ctx, 50*time.Millisecond) + const readTimeout = 50 * time.Millisecond + _, cancel := context.WithTimeout(ctx, readTimeout) defer cancel() buf := make(chan []byte) @@ -325,6 +334,7 @@ func (o *OneRNG) readData(ctx context.Context) (int, error) { } // make sure we always end with a pause/silence/flush + //nolint:errcheck defer o.cmd(ctx, cmdPause, noiseCommand(Silent), cmdFlush) // blocking read from the channel, with a timeout (from context) @@ -344,6 +354,7 @@ func (o *OneRNG) stream(ctx context.Context, bs int, buf chan []byte, errc chan err := o.open() if err != nil { errc <- err + return } @@ -354,10 +365,12 @@ func (o *OneRNG) stream(ctx context.Context, bs int, buf chan []byte, errc chan n, err := io.ReadAtLeast(o.device, 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) + errc <- fmt.Errorf("unexpected short read: wanted %db, read %db", len(b), n) + return } @@ -373,6 +386,7 @@ func (o *OneRNG) scan(ctx context.Context, buf chan string, errc chan error) { err := o.open() if err != nil { errc <- err + return } defer close(buf) @@ -408,7 +422,7 @@ const ( // predictable ways. // // This uses AES-128. -func (o *OneRNG) AESWhitener(ctx context.Context, out io.WriteCloser) (io.WriteCloser, error) { +func (o *OneRNG) AESWhitener(ctx context.Context, out io.Writer) (io.WriteCloser, error) { k, err := o.key(ctx) if err != nil { return nil, err @@ -418,8 +432,10 @@ func (o *OneRNG) AESWhitener(ctx context.Context, out io.WriteCloser) (io.WriteC return nil, err } - // create a random IV with math/rand - doesn't need to be cryptographically-random + // create a random IV with math/rand - doesn't need to be cryptographically-random, + // and we don't want to consume entropy while trying to generate entropy... iv := make([]byte, aes.BlockSize) + //nolint:gosec _, err = mrand.Read(iv) if err != nil { return nil, err @@ -427,6 +443,7 @@ func (o *OneRNG) AESWhitener(ctx context.Context, out io.WriteCloser) (io.WriteC stream := cipher.NewCFBEncrypter(block, iv) s := &cipher.StreamWriter{S: stream, W: out} + return s, nil } @@ -442,7 +459,10 @@ func (o *OneRNG) key(ctx context.Context) ([]byte, error) { if err != nil { return []byte{}, err } + + //nolint:errcheck defer o.cmd(ctx, cmdPause) + // 16 bytes == AES-128 _, err = copyWithContext(ctx, buf, o.device, 16) k := buf.Bytes() @@ -464,12 +484,13 @@ const ( // Default mode - Avalanche enabled, RF disabled, Whitener enabled. Default NoiseMode = 0 // Silent - a convenience - everything disabled - Silent NoiseMode = 4 + Silent NoiseMode = DisableAvalanche ) // noiseCommand converts the given mode to the appropriate command to send to the OneRNG func noiseCommand(flags NoiseMode) string { num := strconv.Itoa(int(flags)) + return "cmd" + num + "\n" } @@ -485,7 +506,7 @@ func copyWithContext(ctx context.Context, dst io.Writer, src io.Reader, n int64) rf := func(p []byte) (int, error) { if f, ok := src.(*os.File); ok { // I don't want reads to block forever, but I also don't want to time out immediately - err := f.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) + err := f.SetReadDeadline(time.Now().Add(copyReadTimeout)) if err != nil { return 0, err } @@ -499,9 +520,11 @@ func copyWithContext(ctx context.Context, dst io.Writer, src io.Reader, n int64) if allowedTimeouts > 0 { if err != nil && os.IsTimeout(err) { allowedTimeouts-- + return n, nil } } + return n, err } } @@ -509,5 +532,6 @@ func copyWithContext(ctx context.Context, dst io.Writer, src io.Reader, n int64) if n < 0 { return io.Copy(dst, readerFunc(rf)) } + return io.CopyN(dst, readerFunc(rf), n) } diff --git a/onerng_test.go b/onerng_test.go index cf36a16..9087dee 100644 --- a/onerng_test.go +++ b/onerng_test.go @@ -8,31 +8,25 @@ import ( "github.com/stretchr/testify/assert" ) -// func TestNoiseCommand(t *testing.T) { -// fmt.Println(DisableWhitener) -// fmt.Println(EnableRF) -// fmt.Println(DisableAvalanche) - -// fmt.Println(Default) -// fmt.Println(Silent) - -// testdata := []struct { -// flags NoiseMode -// cmd string -// }{ -// {Default, "cmd0\n"}, -// {DisableWhitener, "cmd1\n"}, -// {EnableRF, "cmd2\n"}, -// {EnableRF | DisableWhitener, "cmd3\n"}, -// {DisableAvalanche, "cmd4\n"}, -// {DisableAvalanche | DisableWhitener, "cmd5\n"}, -// {DisableAvalanche | EnableRF, "cmd6\n"}, -// {DisableAvalanche | EnableRF | DisableWhitener, "cmd7\n"}, -// } -// for _, d := range testdata { -// assert.Equal(t, d.cmd, noiseCommand(d.flags), d.cmd, d.flags) -// } -// } +func TestNoiseCommand(t *testing.T) { + testdata := []struct { + flags NoiseMode + cmd string + }{ + {Default, "cmd0\n"}, + {DisableWhitener, "cmd1\n"}, + {EnableRF, "cmd2\n"}, + {EnableRF | DisableWhitener, "cmd3\n"}, + {Silent, "cmd4\n"}, + {DisableAvalanche, "cmd4\n"}, + {DisableAvalanche | DisableWhitener, "cmd5\n"}, + {DisableAvalanche | EnableRF, "cmd6\n"}, + {DisableAvalanche | EnableRF | DisableWhitener, "cmd7\n"}, + } + for _, d := range testdata { + assert.Equal(t, d.cmd, noiseCommand(d.flags), d.cmd, d.flags) + } +} type fakeDev struct { closed bool @@ -48,6 +42,7 @@ func (d *fakeDev) reset() { func (d *fakeDev) Close() error { d.closed = true + return nil } @@ -101,3 +96,19 @@ func TestVersion(t *testing.T) { assert.Equal(t, "cmdo\ncmd4\ncmdv\ncmdO\ncmdo\n", d.wbuf.String()) assert.Equal(t, 3, v) } + +func TestIdentify(t *testing.T) { + d := &fakeDev{ + wbuf: &bytes.Buffer{}, + rbuf: bytes.NewBufferString("dfoawiuhf98h9inf2oifoi2jr\n" + + "dfkjawflihjwfoiuh2rliu13he487631487645t98y23rtoqu3rbno9q34htgfv\n" + + "\r\nVersion 3\r\nas;dlfjaw;oihf2ih2o3iuf2ofnlo2jnlfuhf2iou\n\n" + + "dlfkhadslfihwaflkhjw\n___lskdjfalsdkjflsd___\n\n"), + } + o := &OneRNG{Path: "/dev/null", device: d} + ctx := context.Background() + id, err := o.Identify(ctx) + assert.NoError(t, err) + assert.Equal(t, "cmd4\ncmdI\ncmdO\ncmdo\n", d.wbuf.String()) + assert.Equal(t, "___lskdjfalsdkjflsd___", id) +} diff --git a/verify.go b/verify.go index 40be3a0..8984a5c 100644 --- a/verify.go +++ b/verify.go @@ -7,7 +7,6 @@ import ( "io" "os" - "github.com/pkg/errors" "golang.org/x/crypto/openpgp" ) @@ -16,110 +15,154 @@ import ( // // Details are printed to Stderr on success, otherwise an error is returned. // -// This is a more-or-less straight port from the official onerng_verify.py -// script distributed alongside the OneRNG package. +// The general logic is ported from the official onerng_verify.py script +// distributed alongside 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]) - - // 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 - } + if err := readMagic(image); err != nil { + return fmt.Errorf("failed to find magic number: %w", err) } + length, version, err := readHeader(image) + if err != nil { + return fmt.Errorf("failed to read header: %w", err) + } + + return readAndVerify(image, version, length, pubkey) +} + +func read(r io.Reader, p []byte) error { + n, err := r.Read(p) + if err != nil { + return fmt.Errorf("read failed: %w", err) + } + if n == 0 { + return fmt.Errorf("short read") + } + return nil } + +// readHeader reads the header and returns the length and the version +//nolint:gomnd +func readHeader(r io.Reader) (length, version int, err error) { + // read the length + l := make([]byte, 3) + if err := read(r, l); err != nil { + return 0, 0, fmt.Errorf("failed reading length: %w", err) + } + length = int(l[0]) + length |= int(l[1]) << 8 + length |= int(l[2]) << 16 + + // read the version + l = make([]byte, 2) + if err := read(r, l); err != nil { + return 0, 0, fmt.Errorf("failed reading version: %w", err) + } + version = int(l[0]) + version |= int(l[1]) << 8 + + // read the actual code size - we don't use it yet + if err := read(r, make([]byte, 2)); err != nil { + return 0, 0, fmt.Errorf("failed reading actual code size: %w", err) + } + + return length, version, nil +} + +// readMagic reads the input until the magic sequence 0xfeedbeef2014 is found +//nolint:gomnd, gocyclo +func readMagic(r io.Reader) error { + for i := int8(0); i < 6; i++ { + c := make([]byte, 1) + if err := read(r, c); err != nil { + return fmt.Errorf("couldn't read header: %w", err) + } + x := c[0] + + switch { + case i == 0 && x == 0xfe, + i == 1 && x == 0xed, + i == 2 && x == 0xbe, + i == 3 && x == 0xef, + i == 4 && x == 0x20, + i == 5 && x == 0x14: + // as long as this looks like the magic number, keep going! + continue + default: + i = 0 + } + } + + return nil +} + +func readAndVerify(image io.Reader, version, length int, pubkey string) error { + signed, signature, err := parseImage(image, version, length) + if err != nil { + return err + } + + signer, err := verifyImage(signed, signature, pubkey) + if err != nil { + return err + } + + 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) + } + + return nil +} + +func verifyImage(signed, sig []byte, pubkey string) (signer *openpgp.Entity, err error) { + // read public key + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(pubkey)) + if err != nil { + return nil, err + } + + // verify + signer, err = openpgp.CheckDetachedSignature( + keyring, + bytes.NewBuffer(signed), + bytes.NewBuffer(sig), + ) + if err != nil { + return nil, fmt.Errorf("failed to verify firmware signature: %w", err) + } + + return signer, nil +} + +//nolint:gomnd +func parseImage(image io.Reader, version, length int) (signed, sig []byte, err error) { + c := make([]byte, length) + n, err := io.ReadAtLeast(image, c, length) + if err != nil { + return nil, nil, err + } + if n != length { + return nil, nil, fmt.Errorf("bad image: wrong length: was %d, expected %d", n, length) + } + + // determine end offset + endOff := 0 + if version >= 3 { + endOff = 680 + } else { + endOff = 600 + } + + // signature length - 2 bytes between image and signature + slen := int(c[length-endOff]) + slen |= int(c[length-endOff+1]) << 8 + + // split last part into image (signed part) & signature + signed = c[0 : length-endOff] + sig = c[length-endOff+2 : length-endOff+2+slen] + + return signed, sig, nil +} diff --git a/verify_test.go b/verify_test.go new file mode 100644 index 0000000..2bb023e --- /dev/null +++ b/verify_test.go @@ -0,0 +1,52 @@ +package onerng + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReadMagic(t *testing.T) { + r := &bytes.Buffer{} + err := readMagic(r) + assert.Error(t, err) + + r = bytes.NewBufferString("abcdefg") + err = readMagic(r) + assert.Error(t, err) + + r = bytes.NewBuffer([]byte{0x00, 0x01, 0x02, 0xfe, 0xed, 0xbe, 0xee, 0xff}) + err = readMagic(r) + assert.Error(t, err) + + r = bytes.NewBuffer([]byte{0x00, 0x01, 0x02, 0xfe, 0xed, 0xbe, 0xef, 0x20, 0x14}) + err = readMagic(r) + assert.NoError(t, err) +} + +func TestReadHeader(t *testing.T) { + r := &bytes.Buffer{} + _, _, err := readHeader(r) + assert.Error(t, err) + + r = bytes.NewBuffer([]byte{0x00, 0x00, 0x00}) + _, _, err = readHeader(r) + assert.Error(t, err) + + r = bytes.NewBuffer([]byte{0x00, 0x00, 0x00, 0x00}) + _, _, err = readHeader(r) + assert.Error(t, err) + + r = bytes.NewBuffer([]byte{0x0f, 0x00, 0x00, 0x07, 0x00, 0xff, 0xee}) + l, v, err := readHeader(r) + assert.NoError(t, err) + assert.Equal(t, 15, l) + assert.Equal(t, 7, v) + + r = bytes.NewBuffer([]byte{0x0f, 0xf0, 0x01, 0x07, 0x70, 0xff, 0xee, 0x11, 0x42}) + l, v, err = readHeader(r) + assert.NoError(t, err) + assert.Equal(t, 0x01f00f, l) + assert.Equal(t, 0x7007, v) +}