go-onerng/verify.go
Dave Henderson 5aa8605745
updates
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2023-12-18 20:18:58 -05:00

170 lines
4.0 KiB
Go

package onerng
import (
"bytes"
"context"
"fmt"
"io"
"os"
//nolint:staticcheck
"golang.org/x/crypto/openpgp"
)
// Verify reads a signed firmware image, extracts the signature, and verifies
// it against the given public key.
//
// Details are printed to Stderr on success, otherwise an error is returned.
//
// The general logic is ported from the official onerng_verify.py script
// distributed alongside the OneRNG package.
func Verify(_ context.Context, image io.Reader, pubkey string) error {
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
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
}