From 926b9f6304e9258319ce943961f3cb4c183184c8 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Wed, 1 Aug 2018 23:46:37 -0400 Subject: [PATCH] Some incomplete initial code Signed-off-by: Dave Henderson --- .gitignore | 1 + cmd/flush.go | 20 ++++ cmd/id.go | 26 ++++++ cmd/image.go | 26 ++++++ cmd/onerng/main.go | 31 +++++++ cmd/root.go | 73 +++++++++++++++ cmd/verify.go | 84 +++++++++++++++++ cmd/version.go | 26 ++++++ commands.go | 33 +++++++ onerng.go | 222 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 542 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/flush.go create mode 100644 cmd/id.go create mode 100644 cmd/image.go create mode 100644 cmd/onerng/main.go create mode 100644 cmd/root.go create mode 100644 cmd/verify.go create mode 100644 cmd/version.go create mode 100644 commands.go create mode 100644 onerng.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ffbc64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +onerng diff --git a/cmd/flush.go b/cmd/flush.go new file mode 100644 index 0000000..83a671f --- /dev/null +++ b/cmd/flush.go @@ -0,0 +1,20 @@ +package cmd + +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/id.go b/cmd/id.go new file mode 100644 index 0000000..95db05b --- /dev/null +++ b/cmd/id.go @@ -0,0 +1,26 @@ +package cmd + +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/image.go b/cmd/image.go new file mode 100644 index 0000000..9ba6f98 --- /dev/null +++ b/cmd/image.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "context" + "fmt" + + "github.com/hairyhenderson/go-onerng" + "github.com/spf13/cobra" +) + +// imageCmd represents the image command +func imageCmd(ctx context.Context) *cobra.Command { + return &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} + image, err := o.Image(ctx) + if err != nil { + return err + } + fmt.Printf("%q\n", image) + return nil + }, + } +} diff --git a/cmd/onerng/main.go b/cmd/onerng/main.go new file mode 100644 index 0000000..b6b117d --- /dev/null +++ b/cmd/onerng/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "context" + "os" + "os/signal" + "time" + + "github.com/hairyhenderson/go-onerng/cmd" +) + +func main() { + ctx := context.Background() + + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(3*time.Second)) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + defer func() { + signal.Stop(c) + cancel() + }() + go func() { + select { + case <-c: + cancel() + case <-ctx.Done(): + } + }() + + cmd.Execute(ctx) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..5720d2c --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,73 @@ +package cmd + +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 + }, + } +} + +// Execute - +func Execute(ctx context.Context) { + cmd := rootCmd(ctx) + initConfig(ctx, cmd) + if err := cmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +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), + ) + + 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.Println("Using config file:", viper.ConfigFileUsed()) + } +} + +// Config - +type Config struct { + Device string +} diff --git a/cmd/verify.go b/cmd/verify.go new file mode 100644 index 0000000..71e7abe --- /dev/null +++ b/cmd/verify.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "context" + + "github.com/spf13/cobra" +) + +const ( + publicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +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 +=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 { + return nil + }, + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..48a06eb --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,26 @@ +package cmd + +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/commands.go b/commands.go new file mode 100644 index 0000000..1c0d148 --- /dev/null +++ b/commands.go @@ -0,0 +1,33 @@ +package onerng + +const ( + // CmdVersion - print firmware version (as "Version n") + CmdVersion = "cmdv\n" + // CmdFlush - flush entropy pool + CmdFlush = "cmdw\n" + // CmdImage - extract the signed firmware image for verification + CmdImage = "cmdX\n" + // CmdID - print hardware ID + CmdID = "cmdI\n" + // CmdRun - start the task + CmdRun = "cmdO\n" + // CmdPause - stop/pause the task + CmdPause = "cmdo\n" + + // CmdAvalancheWhitener - Avalanche noise with whitener (default) + CmdAvalancheWhitener = "cmd0\n" + // CmdAvalanche - Raw avalanche noise + CmdAvalanche = "cmd1\n" + // CmdAvalancheRFWhitener - Avalanche noise and RF noise with whitener + CmdAvalancheRFWhitener = "cmd2\n" + // CmdAvalancheRF - Raw avalanche noise and RF noise + CmdAvalancheRF = "cmd3\n" + // CmdSilent - No noise (necessary for image extraction) + CmdSilent = "cmd4\n" + // CmdSilent2 - No noise + CmdSilent2 = "cmd5\n" + // CmdRFWhitener - RF noise with whitener + CmdRFWhitener = "cmd6\n" + // CmdRF - Raw RF noise + CmdRF = "cmd7\n" +) diff --git a/onerng.go b/onerng.go new file mode 100644 index 0000000..c39eada --- /dev/null +++ b/onerng.go @@ -0,0 +1,222 @@ +package onerng + +import ( + "bufio" + "context" + "os" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +// OneRNG - a OneRNG device +type OneRNG struct { + Path string +} + +func (o *OneRNG) cmd(ctx context.Context, d *os.File, c ...string) (err error) { + for _, v := range c { + _, err = d.WriteString(v) + if err != nil { + return errors.Wrapf(err, "Errored on command %s", v) + } + select { + case <-ctx.Done(): + return nil + default: + } + } + return nil +} + +// Version - +func (o *OneRNG) Version(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.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(): + } + } + }() + + err = o.cmd(ctx, d, CmdSilent, CmdVersion, CmdRun) + if err != nil { + return 0, err + } + + verString := "" +loop: + for { + var b string + select { + case <-ctx.Done(): + return 0, ctx.Err() + case b = <-buf: + if strings.HasPrefix(b, "Version ") { + verString = b + cancel() + break loop + } + case err := <-errc: + return 0, err + } + } + + err = o.cmd(ctx, d, CmdPause) + if err != nil { + return 0, err + } + + n := strings.Replace(verString, "Version ", "", 1) + version, err := strconv.Atoi(n) + return version, err +} + +// Identify - +func (o *OneRNG) Identify(ctx context.Context) (string, error) { + d, err := os.OpenFile(o.Path, os.O_RDWR, 0600) + if err != nil { + return "", err + } + defer d.Close() + + _, 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(): + } + } + }() + + err = o.cmd(ctx, d, CmdSilent, CmdID, CmdRun) + if err != nil { + return "", err + } + + idString := "" +loop: + for { + var b string + select { + case <-ctx.Done(): + return "", ctx.Err() + case b = <-buf: + if strings.HasPrefix(b, "___") { + idString = b + cancel() + break loop + } + case err := <-errc: + return "", err + } + } + + err = o.cmd(ctx, d, CmdPause) + if err != nil { + return "", err + } + + return idString, err +} + +// Flush - +func (o *OneRNG) Flush(ctx context.Context) error { + d, err := os.OpenFile(o.Path, os.O_RDWR, 0600) + if err != nil { + return err + } + defer d.Close() + + _, cancel := context.WithCancel(ctx) + defer cancel() + err = o.cmd(ctx, d, CmdFlush) + return err +} + +// Image - +func (o *OneRNG) Image(ctx context.Context) ([]byte, error) { + d, err := os.OpenFile(o.Path, os.O_RDWR, 0600) + if err != nil { + return nil, err + } + defer d.Close() + + _, 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 + } + } + }() + + err = o.cmd(ctx, d, CmdSilent, CmdImage, CmdRun) + if err != nil { + return nil, err + } + + image := []byte{} +loop: + for { + var b []byte + select { + case <-ctx.Done(): + return nil, ctx.Err() + case b = <-buf: + copy(image, b) + if len(b) == 0 { + break loop + } + case err := <-errc: + return nil, err + } + } + + err = o.cmd(ctx, d, CmdPause) + if err != nil { + return nil, err + } + + return image, err +}