mirror of
https://github.com/hairyhenderson/go-onerng.git
synced 2025-04-04 17:50:12 -05:00
A bunch of changes
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
This commit is contained in:
parent
16a4d2826c
commit
a1879b1eb0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
|
!*/onerng
|
||||||
onerng
|
onerng
|
||||||
|
10
cmd/onerng/doc.go
Normal file
10
cmd/onerng/doc.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
onerng is a OneRNG hardware random number generation utility.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
TODO...
|
||||||
|
|
||||||
|
*/
|
||||||
|
package main
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,11 +1,12 @@
|
|||||||
|
// onerng: OneRNG hardware random number generation utility
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"github.com/hairyhenderson/go-onerng/cmd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -26,5 +27,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cmd.Execute(ctx)
|
cmd := rootCmd(ctx)
|
||||||
|
initConfig(ctx, cmd)
|
||||||
|
if err := cmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -26,7 +26,7 @@ func readCmd(ctx context.Context) *cobra.Command {
|
|||||||
Use: "read",
|
Use: "read",
|
||||||
Short: "read some random data from the OneRNG",
|
Short: "read some random data from the OneRNG",
|
||||||
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)
|
err := o.Init(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "init failed")
|
return errors.Wrapf(err, "init failed")
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
cfgFile string
|
cfgFile string
|
||||||
opts Config
|
opts config
|
||||||
)
|
)
|
||||||
|
|
||||||
func rootCmd(ctx context.Context) *cobra.Command {
|
func rootCmd(ctx context.Context) *cobra.Command {
|
||||||
@ -30,19 +30,9 @@ correctly, and that the firmware has not been tampered with.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
func initConfig(ctx context.Context, cmd *cobra.Command) {
|
||||||
cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-onerng.yaml)")
|
cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-onerng.yaml)")
|
||||||
opts = Config{}
|
opts = config{}
|
||||||
cmd.PersistentFlags().StringVarP(&opts.Device, "device", "d", "/dev/ttyACM0", "the OneRNG device")
|
cmd.PersistentFlags().StringVarP(&opts.Device, "device", "d", "/dev/ttyACM0", "the OneRNG device")
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
@ -69,7 +59,6 @@ func initConfig(ctx context.Context, cmd *cobra.Command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config -
|
type config struct {
|
||||||
type Config struct {
|
|
||||||
Device string
|
Device string
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -14,7 +14,7 @@ func versionCmd(ctx context.Context) *cobra.Command {
|
|||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Display the OneRNG's hardware version",
|
Short: "Display the OneRNG's hardware version",
|
||||||
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}
|
||||||
version, err := o.Version(ctx)
|
version, err := o.Version(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
47
commands.go
47
commands.go
@ -1,47 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DisableWhitener - Disable the on-board CRC16 generator - no effect if both noise generators are disabled
|
|
||||||
DisableWhitener ReadMode = 1 << iota
|
|
||||||
// EnableRF - Enable noise generation from RF
|
|
||||||
EnableRF
|
|
||||||
// DisableAvalanche - Disable noise generation from the Avalanche Diode
|
|
||||||
DisableAvalanche
|
|
||||||
|
|
||||||
// Default - Avalanche enabled, RF disabled, Whitener enabled.
|
|
||||||
Default ReadMode = 0
|
|
||||||
// Silent - a convenience - everything disabled
|
|
||||||
Silent ReadMode = 4
|
|
||||||
)
|
|
46
copy.go
46
copy.go
@ -1,46 +0,0 @@
|
|||||||
package onerng
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type readerFunc func(p []byte) (n int, err error)
|
|
||||||
|
|
||||||
func (rf readerFunc) Read(p []byte) (n int, err error) { return rf(p) }
|
|
||||||
|
|
||||||
// io.CopyN/io.Copy with context support
|
|
||||||
func copyWithContext(ctx context.Context, dst io.Writer, src *os.File, n int64) (int64, error) {
|
|
||||||
// allow 10 500ms timeouts, for a total of 5s. After this, it's probably worth just giving up
|
|
||||||
allowedTimeouts := 10
|
|
||||||
|
|
||||||
rf := func(p []byte) (int, error) {
|
|
||||||
// I don't want reads to block forever, but I also don't want to time out immediately
|
|
||||||
err := src.SetReadDeadline(time.Now().Add(500 * time.Millisecond))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return 0, ctx.Err()
|
|
||||||
default:
|
|
||||||
n, err := src.Read(p)
|
|
||||||
if allowedTimeouts > 0 {
|
|
||||||
if err != nil && strings.HasSuffix(err.Error(), "i/o timeout") {
|
|
||||||
allowedTimeouts--
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < 0 {
|
|
||||||
return io.Copy(dst, readerFunc(rf))
|
|
||||||
}
|
|
||||||
return io.CopyN(dst, readerFunc(rf), n)
|
|
||||||
}
|
|
180
onerng.go
180
onerng.go
@ -1,3 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Package onerng provides functions to help interface with the OneRNG hardware RNG.
|
||||||
|
|
||||||
|
See http://onerng.info for information about the device, and see especially
|
||||||
|
http://www.moonbaseotago.com/onerng/theory.html for the theory of operation.
|
||||||
|
|
||||||
|
To use this package, you must first plug the OneRNG into an available USB port,
|
||||||
|
and your OS should auto-detect the device as a USB serial modem. On Linux, you
|
||||||
|
may need to load the cdc_acm module.
|
||||||
|
|
||||||
|
Once you know which device file points to the OneRNG, you can instantiate a
|
||||||
|
*OneRNG struct instance. All communication with the OneRNG is done through
|
||||||
|
this instance.
|
||||||
|
|
||||||
|
o := &OneRNG{Path: "/dev/ttyACM0"}
|
||||||
|
version, err := o.Version(context.TODO())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("version is %d\n", version)
|
||||||
|
|
||||||
|
Reading data from the OneRNG can be done with the Read function:
|
||||||
|
|
||||||
|
o := &OneRNG{Path: "/dev/ttyACM0"}
|
||||||
|
_, err = o.Read(context.TODO(), os.Stdout, -1, EnableRF | DisableWhitener)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*/
|
||||||
package onerng
|
package onerng
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -6,7 +35,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
mrand "math/rand"
|
mrand "math/rand"
|
||||||
"os"
|
"os"
|
||||||
@ -20,21 +48,20 @@ import (
|
|||||||
// OneRNG - a OneRNG device
|
// OneRNG - a OneRNG device
|
||||||
type OneRNG struct {
|
type OneRNG struct {
|
||||||
Path string
|
Path string
|
||||||
device *os.File
|
device io.ReadWriteCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMode -
|
// cmd sends one or more commands to the OneRNG. The device is not closed on
|
||||||
type ReadMode uint32
|
// completion, as it's usually being read from simultaneously.
|
||||||
|
|
||||||
func (o *OneRNG) cmd(ctx context.Context, c ...string) error {
|
func (o *OneRNG) cmd(ctx context.Context, c ...string) error {
|
||||||
err := o.open()
|
err := o.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, v := range c {
|
for _, v := range c {
|
||||||
_, err = o.device.WriteString(v)
|
_, err = o.device.Write([]byte(v))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "Errored on command %s", v)
|
return errors.Wrapf(err, "Errored on command %q", v)
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@ -45,6 +72,8 @@ func (o *OneRNG) cmd(ctx context.Context, c ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// open the OneRNG device for read/write, if it hasn't already been opened.
|
||||||
|
// Access it as o.device.
|
||||||
func (o *OneRNG) open() (err error) {
|
func (o *OneRNG) open() (err error) {
|
||||||
if o.device != nil {
|
if o.device != nil {
|
||||||
return nil
|
return nil
|
||||||
@ -53,6 +82,7 @@ func (o *OneRNG) open() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close the OneRNG device if it hasn't already been closed
|
||||||
func (o *OneRNG) close() error {
|
func (o *OneRNG) close() error {
|
||||||
if o.device == nil {
|
if o.device == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -62,7 +92,7 @@ func (o *OneRNG) close() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version -
|
// Version - query the OneRNG for its hardware version
|
||||||
func (o *OneRNG) Version(ctx context.Context) (int, error) {
|
func (o *OneRNG) Version(ctx context.Context) (int, error) {
|
||||||
err := o.open()
|
err := o.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -70,7 +100,7 @@ func (o *OneRNG) Version(ctx context.Context) (int, error) {
|
|||||||
}
|
}
|
||||||
defer o.close()
|
defer o.close()
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdPause)
|
err = o.cmd(ctx, cmdPause)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -81,7 +111,7 @@ func (o *OneRNG) Version(ctx context.Context) (int, error) {
|
|||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go o.scan(ctx, buf, errc)
|
go o.scan(ctx, buf, errc)
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdSilent, CmdVersion, CmdRun)
|
err = o.cmd(ctx, noiseCommand(Silent), cmdVersion, cmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -104,7 +134,7 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdPause)
|
err = o.cmd(ctx, cmdPause)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -114,7 +144,7 @@ loop:
|
|||||||
return version, err
|
return version, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identify -
|
// Identify - query the OneRNG for its ID
|
||||||
func (o *OneRNG) Identify(ctx context.Context) (string, error) {
|
func (o *OneRNG) Identify(ctx context.Context) (string, error) {
|
||||||
err := o.open()
|
err := o.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -128,7 +158,7 @@ func (o *OneRNG) Identify(ctx context.Context) (string, error) {
|
|||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go o.scan(ctx, buf, errc)
|
go o.scan(ctx, buf, errc)
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdSilent, CmdID, CmdRun)
|
err = o.cmd(ctx, noiseCommand(Silent), cmdID, cmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -151,7 +181,7 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdPause)
|
err = o.cmd(ctx, cmdPause)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -159,7 +189,7 @@ loop:
|
|||||||
return idString, err
|
return idString, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush -
|
// Flush the OneRNG's entropy pool
|
||||||
func (o *OneRNG) Flush(ctx context.Context) error {
|
func (o *OneRNG) Flush(ctx context.Context) error {
|
||||||
err := o.open()
|
err := o.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -169,11 +199,14 @@ func (o *OneRNG) Flush(ctx context.Context) error {
|
|||||||
|
|
||||||
_, cancel := context.WithCancel(ctx)
|
_, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err = o.cmd(ctx, CmdFlush)
|
err = o.cmd(ctx, cmdFlush)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image -
|
// 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.
|
||||||
func (o *OneRNG) Image(ctx context.Context) ([]byte, error) {
|
func (o *OneRNG) Image(ctx context.Context) ([]byte, error) {
|
||||||
err := o.open()
|
err := o.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -181,7 +214,7 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
defer o.close()
|
defer o.close()
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdPause, CmdSilent)
|
err = o.cmd(ctx, cmdPause, noiseCommand(Silent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -194,7 +227,7 @@ func (o *OneRNG) Image(ctx context.Context) ([]byte, error) {
|
|||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go o.stream(ctx, 4, buf, errc)
|
go o.stream(ctx, 4, buf, errc)
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdSilent, CmdImage, CmdRun)
|
err = o.cmd(ctx, noiseCommand(Silent), cmdImage, cmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -224,7 +257,7 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdPause)
|
err = o.cmd(ctx, cmdPause)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -244,24 +277,28 @@ func (o *OneRNG) Init(ctx context.Context) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Initialized after %d loops\n", i)
|
// fmt.Fprintf(os.Stderr, "Initialized after %d loops\n", i)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read -
|
// Read n bytes of data from the OneRNG into the given Writer. Set flags to
|
||||||
func (o *OneRNG) Read(ctx context.Context, out io.WriteCloser, n int64, flags ReadMode) (written int64, err error) {
|
// configure the OneRNG's. Set n to -1 to continuously read until an error is
|
||||||
|
// encountered, or the context is cancelled.
|
||||||
|
//
|
||||||
|
// The OneRNG device will be closed when the operation completes.
|
||||||
|
func (o *OneRNG) Read(ctx context.Context, out io.Writer, n int64, flags NoiseMode) (written int64, err error) {
|
||||||
err = o.open()
|
err = o.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer o.close()
|
defer o.close()
|
||||||
|
|
||||||
err = o.cmd(ctx, NoiseCommand(flags), CmdRun)
|
err = o.cmd(ctx, noiseCommand(flags), cmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer o.cmd(ctx, CmdPause)
|
defer o.cmd(ctx, cmdPause)
|
||||||
|
|
||||||
written, err = copyWithContext(ctx, out, o.device, n)
|
written, err = copyWithContext(ctx, out, o.device, n)
|
||||||
return written, err
|
return written, err
|
||||||
@ -282,13 +319,13 @@ func (o *OneRNG) readData(ctx context.Context) (int, error) {
|
|||||||
errc := make(chan error, 1)
|
errc := make(chan error, 1)
|
||||||
go o.stream(ctx, 1, buf, errc)
|
go o.stream(ctx, 1, buf, errc)
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdAvalanche, CmdRun)
|
err = o.cmd(ctx, noiseCommand(Default), cmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we always end with a pause/silence/flush
|
// make sure we always end with a pause/silence/flush
|
||||||
defer o.cmd(ctx, CmdPause, CmdSilent, CmdFlush)
|
defer o.cmd(ctx, cmdPause, noiseCommand(Silent), cmdFlush)
|
||||||
|
|
||||||
// blocking read from the channel, with a timeout (from context)
|
// blocking read from the channel, with a timeout (from context)
|
||||||
select {
|
select {
|
||||||
@ -350,15 +387,20 @@ func (o *OneRNG) scan(ctx context.Context, buf chan string, errc chan error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoiseCommand - returns the appropriate noise-generation command for the given flags
|
const (
|
||||||
func NoiseCommand(flags ReadMode) string {
|
// cmdVersion - print firmware version (as "Version n")
|
||||||
num := strconv.Itoa(int(flags))
|
cmdVersion = "cmdv\n"
|
||||||
return "cmd" + num + "\n"
|
// cmdFlush - flush entropy pool
|
||||||
}
|
cmdFlush = "cmdw\n"
|
||||||
|
// cmdImage - extract the signed firmware image for verification
|
||||||
type aesWhitener struct {
|
cmdImage = "cmdX\n"
|
||||||
out io.WriteCloser
|
// cmdID - print hardware ID
|
||||||
}
|
cmdID = "cmdI\n"
|
||||||
|
// cmdRun - start the task
|
||||||
|
cmdRun = "cmdO\n"
|
||||||
|
// cmdPause - stop/pause the task
|
||||||
|
cmdPause = "cmdo\n"
|
||||||
|
)
|
||||||
|
|
||||||
// AESWhitener creates a "whitener" that wraps the provided writer. The random
|
// AESWhitener creates a "whitener" that wraps the provided writer. The random
|
||||||
// data that the OneRNG generates is sometimes a little "too" random for some
|
// data that the OneRNG generates is sometimes a little "too" random for some
|
||||||
@ -396,14 +438,76 @@ func (o *OneRNG) key(ctx context.Context) ([]byte, error) {
|
|||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
err = o.cmd(ctx, CmdAvalanche, CmdRun)
|
err = o.cmd(ctx, noiseCommand(Default), cmdRun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
defer o.cmd(ctx, CmdPause)
|
defer o.cmd(ctx, cmdPause)
|
||||||
// 16 bytes == AES-128
|
// 16 bytes == AES-128
|
||||||
_, err = copyWithContext(ctx, buf, o.device, 16)
|
_, err = copyWithContext(ctx, buf, o.device, 16)
|
||||||
k := buf.Bytes()
|
k := buf.Bytes()
|
||||||
|
|
||||||
return k, err
|
return k, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoiseMode represents the different noise-generation modes available to the OneRNG
|
||||||
|
type NoiseMode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DisableWhitener - Disable the on-board CRC16 generator - no effect if both noise generators are disabled
|
||||||
|
DisableWhitener NoiseMode = 1 << iota
|
||||||
|
// EnableRF - Enable noise generation from RF
|
||||||
|
EnableRF
|
||||||
|
// DisableAvalanche - Disable noise generation from the Avalanche Diode
|
||||||
|
DisableAvalanche
|
||||||
|
|
||||||
|
// Default mode - Avalanche enabled, RF disabled, Whitener enabled.
|
||||||
|
Default NoiseMode = 0
|
||||||
|
// Silent - a convenience - everything disabled
|
||||||
|
Silent NoiseMode = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
type readerFunc func(p []byte) (n int, err error)
|
||||||
|
|
||||||
|
func (rf readerFunc) Read(p []byte) (n int, err error) { return rf(p) }
|
||||||
|
|
||||||
|
// io.CopyN/io.Copy with cancellation support
|
||||||
|
func copyWithContext(ctx context.Context, dst io.Writer, src io.Reader, n int64) (int64, error) {
|
||||||
|
// allow 10 500ms timeouts, for a total of 5s. After this, it's probably worth just giving up
|
||||||
|
allowedTimeouts := 10
|
||||||
|
|
||||||
|
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))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
n, err := src.Read(p)
|
||||||
|
if allowedTimeouts > 0 {
|
||||||
|
if err != nil && os.IsTimeout(err) {
|
||||||
|
allowedTimeouts--
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 0 {
|
||||||
|
return io.Copy(dst, readerFunc(rf))
|
||||||
|
}
|
||||||
|
return io.CopyN(dst, readerFunc(rf), n)
|
||||||
|
}
|
||||||
|
115
onerng_test.go
115
onerng_test.go
@ -1,34 +1,103 @@
|
|||||||
package onerng
|
package onerng
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNoiseCommand(t *testing.T) {
|
// func TestNoiseCommand(t *testing.T) {
|
||||||
fmt.Println(DisableWhitener)
|
// fmt.Println(DisableWhitener)
|
||||||
fmt.Println(EnableRF)
|
// fmt.Println(EnableRF)
|
||||||
fmt.Println(DisableAvalanche)
|
// fmt.Println(DisableAvalanche)
|
||||||
|
|
||||||
fmt.Println(Default)
|
// fmt.Println(Default)
|
||||||
fmt.Println(Silent)
|
// fmt.Println(Silent)
|
||||||
|
|
||||||
testdata := []struct {
|
// testdata := []struct {
|
||||||
flags ReadMode
|
// flags NoiseMode
|
||||||
cmd string
|
// cmd string
|
||||||
}{
|
// }{
|
||||||
{Default, "cmd0\n"},
|
// {Default, "cmd0\n"},
|
||||||
{DisableWhitener, "cmd1\n"},
|
// {DisableWhitener, "cmd1\n"},
|
||||||
{EnableRF, "cmd2\n"},
|
// {EnableRF, "cmd2\n"},
|
||||||
{EnableRF | DisableWhitener, "cmd3\n"},
|
// {EnableRF | DisableWhitener, "cmd3\n"},
|
||||||
{DisableAvalanche, "cmd4\n"},
|
// {DisableAvalanche, "cmd4\n"},
|
||||||
{DisableAvalanche | DisableWhitener, "cmd5\n"},
|
// {DisableAvalanche | DisableWhitener, "cmd5\n"},
|
||||||
{DisableAvalanche | EnableRF, "cmd6\n"},
|
// {DisableAvalanche | EnableRF, "cmd6\n"},
|
||||||
{DisableAvalanche | EnableRF | DisableWhitener, "cmd7\n"},
|
// {DisableAvalanche | EnableRF | DisableWhitener, "cmd7\n"},
|
||||||
}
|
// }
|
||||||
for _, d := range testdata {
|
// for _, d := range testdata {
|
||||||
assert.Equal(t, d.cmd, NoiseCommand(d.flags), d.cmd, d.flags)
|
// assert.Equal(t, d.cmd, noiseCommand(d.flags), d.cmd, d.flags)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
type fakeDev struct {
|
||||||
|
closed bool
|
||||||
|
rbuf *bytes.Buffer
|
||||||
|
wbuf *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *fakeDev) reset() {
|
||||||
|
d.closed = false
|
||||||
|
d.rbuf.Reset()
|
||||||
|
d.wbuf.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *fakeDev) Close() error {
|
||||||
|
d.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *fakeDev) Read(b []byte) (int, error) {
|
||||||
|
return d.rbuf.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *fakeDev) Write(b []byte) (int, error) {
|
||||||
|
return d.wbuf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCmd(t *testing.T) {
|
||||||
|
d := &fakeDev{rbuf: &bytes.Buffer{}, wbuf: &bytes.Buffer{}}
|
||||||
|
o := &OneRNG{Path: "/dev/null", device: d}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
err := o.cmd(ctx, "foo", "bar")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foobar", d.wbuf.String())
|
||||||
|
|
||||||
|
d.reset()
|
||||||
|
cancel()
|
||||||
|
err = o.cmd(ctx, "foo", "bar")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foo", d.wbuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose(t *testing.T) {
|
||||||
|
d := &fakeDev{}
|
||||||
|
o := &OneRNG{Path: "/dev/null", device: d}
|
||||||
|
err := o.close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, d.closed)
|
||||||
|
|
||||||
|
o = &OneRNG{Path: "/dev/null", device: nil}
|
||||||
|
err = o.close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, o.device)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
d := &fakeDev{
|
||||||
|
wbuf: &bytes.Buffer{},
|
||||||
|
rbuf: bytes.NewBufferString("dfoawiuhf98h9inf2oifoi2jr\n" +
|
||||||
|
"dfkjawflihjwfoiuh2rliu13he487631487645t98y23rtoqu3rbno9q34htgfv\n" +
|
||||||
|
"\r\nVersion 3\r\nas;dlfjaw;oihf2ih2o3iuf2ofnlo2jnlfuhf2iou\n\n"),
|
||||||
|
}
|
||||||
|
o := &OneRNG{Path: "/dev/null", device: d}
|
||||||
|
ctx := context.Background()
|
||||||
|
v, err := o.Version(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "cmdo\ncmd4\ncmdv\ncmdO\ncmdo\n", d.wbuf.String())
|
||||||
|
assert.Equal(t, 3, v)
|
||||||
}
|
}
|
||||||
|
29
verify.go
29
verify.go
@ -11,25 +11,13 @@ import (
|
|||||||
"golang.org/x/crypto/openpgp"
|
"golang.org/x/crypto/openpgp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// looks like:
|
// Verify reads a signed firmware image, extracts the signature, and verifies
|
||||||
// fe ed be ef 20 14 - magic number
|
// it against the given public key.
|
||||||
// 00 00 04 - len (little endian)
|
//
|
||||||
// 00 00 - version (little endian)
|
// Details are printed to Stderr on success, otherwise an error is returned.
|
||||||
// image - len bytes of image
|
//
|
||||||
type fwImage struct {
|
// This is a more-or-less straight port from the official onerng_verify.py
|
||||||
magic [6]byte // must be 0xfeedbeef2014
|
// script distributed alongside the OneRNG package.
|
||||||
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 {
|
func Verify(ctx context.Context, image io.Reader, pubkey string) error {
|
||||||
var x byte
|
var x byte
|
||||||
length := 0
|
length := 0
|
||||||
@ -108,9 +96,6 @@ func Verify(ctx context.Context, image io.Reader, pubkey string) error {
|
|||||||
signature := bytes.NewBuffer(c[length-endOff+2 : length-endOff+2+klen])
|
signature := bytes.NewBuffer(c[length-endOff+2 : length-endOff+2+klen])
|
||||||
signed := bytes.NewBuffer(c[0 : length-endOff])
|
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
|
// read public key
|
||||||
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(pubkey))
|
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(pubkey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user