feat(audit): 1.1 — project scaffold, schema types, collector stub, updater trust
- go.mod: module bee/audit - schema/hardware.go: HardwareIngestRequest types (compatible with core) - collector/collector.go: Run() stub, logs start/finish, returns empty snapshot - updater/trust.go: Ed25519 multi-key verification via ldflags injection - updater/trust_test.go: valid sig, tampered, multi-key any-match, dev build - cmd/audit/main.go: --output stdout|file:<path>|usb, --version flag - Version = "dev" by default, injected via ldflags at release Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
59
audit/internal/updater/trust.go
Normal file
59
audit/internal/updater/trust.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Package updater handles binary self-update with Ed25519 signature verification.
|
||||
// See bible/rules/patterns/release-signing/contract.md for the full contract.
|
||||
package updater
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// trustedKeysRaw is injected at release build time via:
|
||||
//
|
||||
// -ldflags "-X bee/audit/internal/updater.trustedKeysRaw=base64key1:base64key2"
|
||||
//
|
||||
// Empty in dev builds — updates are disabled when empty.
|
||||
var trustedKeysRaw string
|
||||
|
||||
// TrustedKeys decodes the embedded public keys.
|
||||
// Returns an error if the binary was built without key injection (dev build).
|
||||
func TrustedKeys() ([]ed25519.PublicKey, error) {
|
||||
if strings.TrimSpace(trustedKeysRaw) == "" {
|
||||
return nil, fmt.Errorf("dev build: trusted keys not embedded, updates disabled")
|
||||
}
|
||||
var keys []ed25519.PublicKey
|
||||
for _, enc := range strings.Split(trustedKeysRaw, ":") {
|
||||
enc = strings.TrimSpace(enc)
|
||||
if enc == "" {
|
||||
continue
|
||||
}
|
||||
b, err := base64.StdEncoding.DecodeString(enc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode trusted key: %w", err)
|
||||
}
|
||||
if len(b) != ed25519.PublicKeySize {
|
||||
return nil, fmt.Errorf("trusted key has wrong size: got %d, want %d", len(b), ed25519.PublicKeySize)
|
||||
}
|
||||
keys = append(keys, ed25519.PublicKey(b))
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil, fmt.Errorf("no valid trusted keys found in build")
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// VerifySignature returns nil if sig was produced by any trusted key over data.
|
||||
// Returns an error (never panics) on any failure — caller logs and continues.
|
||||
func VerifySignature(data, sig []byte) error {
|
||||
keys, err := TrustedKeys()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
if ed25519.Verify(key, data, sig) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("signature verification failed: no trusted key matched")
|
||||
}
|
||||
72
audit/internal/updater/trust_test.go
Normal file
72
audit/internal/updater/trust_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVerifySignature_valid(t *testing.T) {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data := []byte("release binary content")
|
||||
sig := ed25519.Sign(priv, data)
|
||||
|
||||
trustedKeysRaw = base64.StdEncoding.EncodeToString(pub)
|
||||
t.Cleanup(func() { trustedKeysRaw = "" })
|
||||
|
||||
if err := VerifySignature(data, sig); err != nil {
|
||||
t.Fatalf("expected valid signature to pass: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifySignature_tampered(t *testing.T) {
|
||||
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = pub
|
||||
|
||||
data := []byte("original content")
|
||||
sig := ed25519.Sign(priv, data)
|
||||
|
||||
// different key embedded
|
||||
pub2, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
trustedKeysRaw = base64.StdEncoding.EncodeToString(pub2)
|
||||
t.Cleanup(func() { trustedKeysRaw = "" })
|
||||
|
||||
if err := VerifySignature(data, sig); err == nil {
|
||||
t.Fatal("expected tampered signature to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifySignature_multiKey_anyMatches(t *testing.T) {
|
||||
pub1, _, _ := ed25519.GenerateKey(rand.Reader)
|
||||
pub2, priv2, _ := ed25519.GenerateKey(rand.Reader)
|
||||
|
||||
data := []byte("signed by developer 2")
|
||||
sig := ed25519.Sign(priv2, data)
|
||||
|
||||
trustedKeysRaw = base64.StdEncoding.EncodeToString(pub1) + ":" +
|
||||
base64.StdEncoding.EncodeToString(pub2)
|
||||
t.Cleanup(func() { trustedKeysRaw = "" })
|
||||
|
||||
if err := VerifySignature(data, sig); err != nil {
|
||||
t.Fatalf("expected any-key match to pass: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifySignature_devBuild(t *testing.T) {
|
||||
trustedKeysRaw = ""
|
||||
_, priv, _ := ed25519.GenerateKey(rand.Reader)
|
||||
data := []byte("content")
|
||||
sig := ed25519.Sign(priv, data)
|
||||
|
||||
if err := VerifySignature(data, sig); err == nil {
|
||||
t.Fatal("expected dev build (no keys) to fail")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user