mirror of
https://github.com/hairyhenderson/go-onerng.git
synced 2025-04-04 09:40:12 -05:00
170 lines
4.0 KiB
Go
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
|
|
}
|