Files
crypto/wasm/verifier.go

225 lines
5.5 KiB
Go

// Package wasm provides cryptographic verification for WebAssembly modules
package wasm
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sync"
)
// HashVerifier provides SHA256 hash verification for WASM modules
type HashVerifier struct {
// trustedHashes stores SHA256 hashes of trusted WASM modules
trustedHashes map[string]string
mu sync.RWMutex
}
// NewHashVerifier creates a new WASM hash verifier
func NewHashVerifier() *HashVerifier {
return &HashVerifier{
trustedHashes: make(map[string]string),
}
}
// ComputeHash calculates SHA256 hash of WASM bytecode
func (v *HashVerifier) ComputeHash(wasmBytes []byte) string {
hash := sha256.Sum256(wasmBytes)
return hex.EncodeToString(hash[:])
}
// AddTrustedHash adds a trusted hash for a named WASM module
func (v *HashVerifier) AddTrustedHash(name, hash string) {
v.mu.Lock()
defer v.mu.Unlock()
v.trustedHashes[name] = hash
}
// VerifyHash verifies WASM bytecode against trusted hash
func (v *HashVerifier) VerifyHash(name string, wasmBytes []byte) error {
v.mu.RLock()
trustedHash, exists := v.trustedHashes[name]
v.mu.RUnlock()
if !exists {
return fmt.Errorf("no trusted hash found for WASM module: %s", name)
}
computedHash := v.ComputeHash(wasmBytes)
if computedHash != trustedHash {
return fmt.Errorf(
"WASM hash verification failed for %s: expected %s, got %s",
name, trustedHash, computedHash,
)
}
return nil
}
// VerifyHashWithFallback verifies against primary hash or fallback list
func (v *HashVerifier) VerifyHashWithFallback(name string, wasmBytes []byte, fallbackHashes []string) error {
// Try primary verification first
if err := v.VerifyHash(name, wasmBytes); err == nil {
return nil
}
// Check against fallback hashes
computedHash := v.ComputeHash(wasmBytes)
for _, fallbackHash := range fallbackHashes {
if computedHash == fallbackHash {
// Update trusted hash for future use
v.AddTrustedHash(name, computedHash)
return nil
}
}
return fmt.Errorf(
"WASM hash verification failed: computed hash %s not in trusted set",
computedHash,
)
}
// GetTrustedHash retrieves the trusted hash for a module
func (v *HashVerifier) GetTrustedHash(name string) (string, bool) {
v.mu.RLock()
defer v.mu.RUnlock()
hash, exists := v.trustedHashes[name]
return hash, exists
}
// ClearTrustedHashes removes all trusted hashes
func (v *HashVerifier) ClearTrustedHashes() {
v.mu.Lock()
defer v.mu.Unlock()
v.trustedHashes = make(map[string]string)
}
// HashChain provides hash chain verification for plugin updates
type HashChain struct {
chain []HashEntry
mu sync.RWMutex
}
// HashEntry represents a single entry in the hash chain
type HashEntry struct {
Version string `json:"version"`
Hash string `json:"hash"`
PreviousHash string `json:"previous_hash"`
Timestamp int64 `json:"timestamp"`
}
// NewHashChain creates a new hash chain
func NewHashChain() *HashChain {
return &HashChain{
chain: make([]HashEntry, 0),
}
}
// AddEntry adds a new entry to the hash chain
func (hc *HashChain) AddEntry(version, hash string, timestamp int64) error {
hc.mu.Lock()
defer hc.mu.Unlock()
previousHash := ""
if len(hc.chain) > 0 {
previousHash = hc.chain[len(hc.chain)-1].Hash
}
entry := HashEntry{
Version: version,
Hash: hash,
PreviousHash: previousHash,
Timestamp: timestamp,
}
hc.chain = append(hc.chain, entry)
return nil
}
// VerifyChain verifies the integrity of the hash chain
func (hc *HashChain) VerifyChain() error {
hc.mu.RLock()
defer hc.mu.RUnlock()
if len(hc.chain) == 0 {
return nil
}
// First entry should have empty previous hash
if hc.chain[0].PreviousHash != "" {
return fmt.Errorf("invalid hash chain: first entry has non-empty previous hash")
}
// Verify chain continuity
for i := 1; i < len(hc.chain); i++ {
if hc.chain[i].PreviousHash != hc.chain[i-1].Hash {
return fmt.Errorf(
"hash chain broken at version %s: expected previous hash %s, got %s",
hc.chain[i].Version,
hc.chain[i-1].Hash,
hc.chain[i].PreviousHash,
)
}
}
return nil
}
// GetLatestEntry returns the most recent hash chain entry
func (hc *HashChain) GetLatestEntry() (*HashEntry, error) {
hc.mu.RLock()
defer hc.mu.RUnlock()
if len(hc.chain) == 0 {
return nil, fmt.Errorf("hash chain is empty")
}
latest := hc.chain[len(hc.chain)-1]
return &latest, nil
}
// VerificationError represents a WASM verification failure
type VerificationError struct {
Module string
ExpectedHash string
ActualHash string
Reason string
}
// Error implements the error interface
func (e *VerificationError) Error() string {
return fmt.Sprintf(
"WASM verification failed for %s: %s (expected: %s, actual: %s)",
e.Module, e.Reason, e.ExpectedHash, e.ActualHash,
)
}
// SecurityPolicy defines verification requirements
type SecurityPolicy struct {
RequireHashVerification bool
RequireSignature bool
AllowedHashes []string
MaxModuleSize int64
}
// DefaultSecurityPolicy returns a secure default policy
func DefaultSecurityPolicy() *SecurityPolicy {
return &SecurityPolicy{
RequireHashVerification: true,
RequireSignature: false, // Will be enabled in next phase
AllowedHashes: []string{},
MaxModuleSize: 10 * 1024 * 1024, // 10MB max
}
}
// Validate checks if WASM module meets security policy
func (p *SecurityPolicy) Validate(wasmBytes []byte) error {
if p.MaxModuleSize > 0 && int64(len(wasmBytes)) > p.MaxModuleSize {
return fmt.Errorf(
"WASM module size %d exceeds maximum allowed size %d",
len(wasmBytes), p.MaxModuleSize,
)
}
return nil
}