Files
mpc/pkg/integrity/verify.go
T
zeekay 4e915d77c3 security: fix 15 custody audit findings, add regression + fuzz tests
Fixes all findings from the Blue Team defensive security review of the
redacted → redacted custody architecture transition.

HIGH:
- S-1: Implement P-256 ECDH + AES-256-GCM encryption of passkey backup shards
- W-2: Reject unparseable WebAuthn public keys (was silently skipping verification)
- O-1: Add Ed25519 binary signature verification package (pkg/integrity)

MEDIUM:
- W-1: Validate attestation is a real P-256 curve point, not raw attestation object
- W-3: Add origin validation to all WebAuthn flows (register, verify, trade approve)
- N-2: Remove plaintext TCP fallback from MPC node transport (TLS mandatory)
- K-1: Fail-closed on empty/whitespace orgID (prevents cross-tenant key leakage)
- A-1: Make AuditEntry immutable (Update/Delete return error)
- A-2: Add MPC-level signing audit log (wallet, message hash, intent, trigger)
- O-3: Invalidate backup shards after key reshare (stale shares are useless)
- O-2: Reject env/file password providers in production (require cloud HSM)

LOW:
- T-1: Add error handling + MarkSignFailed to signing goroutine
- T-2: Verify caller authorization in handleTradeSubmit
- K-2: Secret erasure stub now calls runtime.GC() instead of no-op
- N-1: Add VerifyConnection callback to reject peers without TLS certs

Tests: 69 unit + 7 fuzz targets, ~900K fuzz executions, 19/19 packages green.
Makefile: add -tags json1 for SQLite JSON function support in ORM tests.
2025-12-28 01:00:12 -08:00

92 lines
2.8 KiB
Go

// Package integrity provides Ed25519 signature verification for MPC binaries
// and configuration files. This prevents code injection via compromised S3
// buckets or supply-chain attacks on binary distribution.
package integrity
import (
"crypto/ed25519"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
)
// TrustedSigningKeys contains the Ed25519 public keys authorized to sign MPC
// binaries. The binary signature file (<binary>.sig) must be verifiable by at
// least one of these keys. Keys are hex-encoded 32-byte Ed25519 public keys.
//
// These are baked into the binary at compile time. To rotate, update this list
// and rebuild. The old key should be kept for one release cycle to allow
// rollback.
var TrustedSigningKeys []ed25519.PublicKey
// SetTrustedKeys sets the signing keys. Called from main() with keys loaded
// from config or hardcoded.
func SetTrustedKeys(hexKeys []string) error {
TrustedSigningKeys = nil
for _, h := range hexKeys {
b, err := hex.DecodeString(h)
if err != nil || len(b) != ed25519.PublicKeySize {
return fmt.Errorf("invalid signing key: %s", h)
}
TrustedSigningKeys = append(TrustedSigningKeys, ed25519.PublicKey(b))
}
return nil
}
// VerifyFile checks that a file's Ed25519 signature is valid against at least
// one trusted signing key. The signature file must exist at <path>.sig and
// contain a hex-encoded 64-byte Ed25519 signature of SHA-256(file contents).
func VerifyFile(path string) error {
if len(TrustedSigningKeys) == 0 {
return fmt.Errorf("no trusted signing keys configured")
}
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %w", err)
}
sigHex, err := os.ReadFile(path + ".sig")
if err != nil {
return fmt.Errorf("read signature file %s.sig: %w", path, err)
}
sig, err := hex.DecodeString(string(sigHex))
if err != nil || len(sig) != ed25519.SignatureSize {
return fmt.Errorf("invalid signature format in %s.sig", path)
}
hash := sha256.Sum256(data)
for _, key := range TrustedSigningKeys {
if ed25519.Verify(key, hash[:], sig) {
return nil
}
}
return fmt.Errorf("signature verification failed: no trusted key signed %s", path)
}
// HashFile returns the hex-encoded SHA-256 hash of a file.
func HashFile(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:]), nil
}
// SignFile creates a signature file for the given path using the provided
// Ed25519 private key. The signature covers SHA-256(file contents).
func SignFile(path string, privKey ed25519.PrivateKey) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read file: %w", err)
}
hash := sha256.Sum256(data)
sig := ed25519.Sign(privKey, hash[:])
return os.WriteFile(path+".sig", []byte(hex.EncodeToString(sig)), 0644)
}