Files
crypto/wasm/signer.go

337 lines
9.6 KiB
Go

// Package wasm provides cryptographic signing and verification for WebAssembly modules
package wasm
import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"sync"
"time"
)
// Signer provides Ed25519 digital signature operations for WASM modules
type Signer struct {
privateKey ed25519.PrivateKey
publicKey ed25519.PublicKey
}
// NewSigner creates a new signer with a generated Ed25519 key pair
func NewSigner() (*Signer, error) {
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate Ed25519 key pair: %w", err)
}
return &Signer{
privateKey: priv,
publicKey: pub,
}, nil
}
// NewSignerFromPrivateKey creates a signer from an existing private key
func NewSignerFromPrivateKey(privateKey ed25519.PrivateKey) (*Signer, error) {
if len(privateKey) != ed25519.PrivateKeySize {
return nil, fmt.Errorf("invalid private key size: expected %d, got %d",
ed25519.PrivateKeySize, len(privateKey))
}
publicKey := privateKey.Public().(ed25519.PublicKey)
return &Signer{
privateKey: privateKey,
publicKey: publicKey,
}, nil
}
// Sign creates an Ed25519 signature for the given WASM bytecode
func (s *Signer) Sign(wasmBytes []byte) ([]byte, error) {
if s.privateKey == nil {
return nil, fmt.Errorf("private key not initialized")
}
signature := ed25519.Sign(s.privateKey, wasmBytes)
return signature, nil
}
// GetPublicKey returns the public key bytes
func (s *Signer) GetPublicKey() []byte {
return s.publicKey
}
// GetPublicKeyHex returns the public key as hex string
func (s *Signer) GetPublicKeyHex() string {
return hex.EncodeToString(s.publicKey)
}
// ExportPrivateKey exports the private key (handle with care)
func (s *Signer) ExportPrivateKey() []byte {
return s.privateKey
}
// SignatureVerifier verifies Ed25519 signatures on WASM modules
type SignatureVerifier struct {
trustedKeys map[string]ed25519.PublicKey
mu sync.RWMutex
}
// NewSignatureVerifier creates a new signature verifier
func NewSignatureVerifier() *SignatureVerifier {
return &SignatureVerifier{
trustedKeys: make(map[string]ed25519.PublicKey),
}
}
// AddTrustedKey adds a trusted public key for signature verification
func (v *SignatureVerifier) AddTrustedKey(keyID string, publicKey ed25519.PublicKey) error {
if len(publicKey) != ed25519.PublicKeySize {
return fmt.Errorf("invalid public key size: expected %d, got %d",
ed25519.PublicKeySize, len(publicKey))
}
v.mu.Lock()
defer v.mu.Unlock()
v.trustedKeys[keyID] = publicKey
return nil
}
// AddTrustedKeyFromHex adds a trusted public key from hex string
func (v *SignatureVerifier) AddTrustedKeyFromHex(keyID string, publicKeyHex string) error {
publicKey, err := hex.DecodeString(publicKeyHex)
if err != nil {
return fmt.Errorf("failed to decode public key hex: %w", err)
}
return v.AddTrustedKey(keyID, ed25519.PublicKey(publicKey))
}
// Verify verifies a signature against trusted public keys
func (v *SignatureVerifier) Verify(wasmBytes []byte, signature []byte) error {
v.mu.RLock()
defer v.mu.RUnlock()
if len(v.trustedKeys) == 0 {
return fmt.Errorf("no trusted keys configured")
}
// Try to verify with any trusted key
for keyID, publicKey := range v.trustedKeys {
if ed25519.Verify(publicKey, wasmBytes, signature) {
// Signature valid with this key
return nil
}
_ = keyID // Key ID available for logging if needed
}
return fmt.Errorf("signature verification failed: no matching trusted key")
}
// VerifyWithKey verifies a signature with a specific key
func (v *SignatureVerifier) VerifyWithKey(keyID string, wasmBytes []byte, signature []byte) error {
v.mu.RLock()
publicKey, exists := v.trustedKeys[keyID]
v.mu.RUnlock()
if !exists {
return fmt.Errorf("trusted key not found: %s", keyID)
}
if !ed25519.Verify(publicKey, wasmBytes, signature) {
return fmt.Errorf("signature verification failed for key: %s", keyID)
}
return nil
}
// RemoveTrustedKey removes a trusted key
func (v *SignatureVerifier) RemoveTrustedKey(keyID string) {
v.mu.Lock()
defer v.mu.Unlock()
delete(v.trustedKeys, keyID)
}
// GetTrustedKeyIDs returns all trusted key IDs
func (v *SignatureVerifier) GetTrustedKeyIDs() []string {
v.mu.RLock()
defer v.mu.RUnlock()
ids := make([]string, 0, len(v.trustedKeys))
for id := range v.trustedKeys {
ids = append(ids, id)
}
return ids
}
// SignedModule represents a WASM module with its signature
type SignedModule struct {
Module []byte `json:"-"` // WASM bytecode (excluded from JSON)
Hash string `json:"hash"` // SHA256 hash of module
Signature []byte `json:"signature"` // Ed25519 signature
SignerID string `json:"signer_id"` // ID of signing key
Timestamp time.Time `json:"timestamp"` // Signing timestamp
Version string `json:"version"` // Module version
}
// SignModule creates a signed module package
func SignModule(signer *Signer, module []byte, signerID string, version string) (*SignedModule, error) {
// Compute hash
hashVerifier := NewHashVerifier()
hash := hashVerifier.ComputeHash(module)
// Sign the module
signature, err := signer.Sign(module)
if err != nil {
return nil, fmt.Errorf("failed to sign module: %w", err)
}
return &SignedModule{
Module: module,
Hash: hash,
Signature: signature,
SignerID: signerID,
Timestamp: time.Now(),
Version: version,
}, nil
}
// VerifySignedModule verifies a signed module
func VerifySignedModule(verifier *SignatureVerifier, module *SignedModule) error {
// Verify hash matches
hashVerifier := NewHashVerifier()
computedHash := hashVerifier.ComputeHash(module.Module)
if computedHash != module.Hash {
return fmt.Errorf("hash mismatch: expected %s, got %s", module.Hash, computedHash)
}
// Verify signature
if module.SignerID != "" {
return verifier.VerifyWithKey(module.SignerID, module.Module, module.Signature)
}
return verifier.Verify(module.Module, module.Signature)
}
// SignatureManifest contains signature metadata for a WASM module
type SignatureManifest struct {
ModuleHash string `json:"module_hash"`
Signatures []SignatureEntry `json:"signatures"`
TrustedKeys []TrustedKeyEntry `json:"trusted_keys"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
// SignatureEntry represents a single signature in the manifest
type SignatureEntry struct {
Signature string `json:"signature"` // Base64 encoded
SignerID string `json:"signer_id"`
Timestamp time.Time `json:"timestamp"`
Algorithm string `json:"algorithm"` // Always "Ed25519"
}
// TrustedKeyEntry represents a trusted public key
type TrustedKeyEntry struct {
KeyID string `json:"key_id"`
PublicKey string `json:"public_key"` // Base64 encoded
AddedAt time.Time `json:"added_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
Purpose string `json:"purpose"` // e.g., "code-signing"
}
// CreateSignatureManifest creates a manifest for module signatures
func CreateSignatureManifest(module []byte, signer *Signer, signerID string) (*SignatureManifest, error) {
hashVerifier := NewHashVerifier()
moduleHash := hashVerifier.ComputeHash(module)
signature, err := signer.Sign(module)
if err != nil {
return nil, fmt.Errorf("failed to sign module: %w", err)
}
manifest := &SignatureManifest{
ModuleHash: moduleHash,
Signatures: []SignatureEntry{
{
Signature: base64.StdEncoding.EncodeToString(signature),
SignerID: signerID,
Timestamp: time.Now(),
Algorithm: "Ed25519",
},
},
TrustedKeys: []TrustedKeyEntry{
{
KeyID: signerID,
PublicKey: base64.StdEncoding.EncodeToString(signer.GetPublicKey()),
AddedAt: time.Now(),
Purpose: "code-signing",
},
},
CreatedAt: time.Now(),
}
return manifest, nil
}
// VerifyWithManifest verifies a module using a signature manifest
func VerifyWithManifest(module []byte, manifest *SignatureManifest) error {
// Verify hash
hashVerifier := NewHashVerifier()
computedHash := hashVerifier.ComputeHash(module)
if computedHash != manifest.ModuleHash {
return fmt.Errorf("module hash mismatch")
}
// Check expiration
if manifest.ExpiresAt != nil && time.Now().After(*manifest.ExpiresAt) {
return fmt.Errorf("signature manifest has expired")
}
// Create verifier with trusted keys from manifest
verifier := NewSignatureVerifier()
for _, key := range manifest.TrustedKeys {
// Check key expiration
if key.ExpiresAt != nil && time.Now().After(*key.ExpiresAt) {
continue // Skip expired keys
}
publicKey, err := base64.StdEncoding.DecodeString(key.PublicKey)
if err != nil {
return fmt.Errorf("failed to decode public key: %w", err)
}
if err := verifier.AddTrustedKey(key.KeyID, ed25519.PublicKey(publicKey)); err != nil {
return fmt.Errorf("failed to add trusted key: %w", err)
}
}
// Verify at least one signature
for _, sig := range manifest.Signatures {
signature, err := base64.StdEncoding.DecodeString(sig.Signature)
if err != nil {
continue // Skip invalid signatures
}
if err := verifier.VerifyWithKey(sig.SignerID, module, signature); err == nil {
// At least one valid signature found
return nil
}
}
return fmt.Errorf("no valid signatures found in manifest")
}
// ExportManifest exports a signature manifest as JSON
func ExportManifest(manifest *SignatureManifest) ([]byte, error) {
return json.MarshalIndent(manifest, "", " ")
}
// ImportManifest imports a signature manifest from JSON
func ImportManifest(data []byte) (*SignatureManifest, error) {
var manifest SignatureManifest
if err := json.Unmarshal(data, &manifest); err != nil {
return nil, fmt.Errorf("failed to parse manifest: %w", err)
}
return &manifest, nil
}