From b6a01a07ae566b9ead43f08fd8d1d9ae9a8fb6ca Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Sat, 10 Jan 2026 15:40:54 -0500 Subject: [PATCH] refactor(enclave): remove enclave package --- internal/enclave/crypto.go | 219 ------------------------------------ internal/enclave/enclave.go | 184 ------------------------------ 2 files changed, 403 deletions(-) delete mode 100644 internal/enclave/crypto.go delete mode 100644 internal/enclave/enclave.go diff --git a/internal/enclave/crypto.go b/internal/enclave/crypto.go deleted file mode 100644 index 18ca7f9..0000000 --- a/internal/enclave/crypto.go +++ /dev/null @@ -1,219 +0,0 @@ -// Package enclave provides encrypted database operations with WebAuthn PRF key derivation. -package enclave - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "fmt" - "io" - - "golang.org/x/crypto/hkdf" -) - -const ( - // EnclaveSalt is the salt used for HKDF key derivation - EnclaveSalt = "nebula-enclave-v1" - - // KeySize is the size of the derived encryption key (256 bits) - KeySize = 32 - - // NonceSize is the size of the GCM nonce (96 bits) - NonceSize = 12 - - // AuthTagSize is the size of the GCM authentication tag (128 bits) - AuthTagSize = 16 -) - -// DeriveEncryptionKey derives a 256-bit encryption key from WebAuthn PRF output using HKDF. -// -// Parameters: -// - prfOutput: The raw PRF output from WebAuthn (typically 32 bytes) -// -// Returns: -// - A 32-byte key suitable for AES-256-GCM encryption -// - An error if key derivation fails -func DeriveEncryptionKey(prfOutput []byte) ([]byte, error) { - if len(prfOutput) == 0 { - return nil, fmt.Errorf("enclave: PRF output cannot be empty") - } - - salt := []byte(EnclaveSalt) - info := []byte("database-encryption") - - hkdfReader := hkdf.New(sha256.New, prfOutput, salt, info) - - key := make([]byte, KeySize) - if _, err := io.ReadFull(hkdfReader, key); err != nil { - return nil, fmt.Errorf("enclave: failed to derive key: %w", err) - } - - return key, nil -} - -// DeriveKeyWithContext derives an encryption key with additional context binding. -// This allows deriving different keys for different purposes from the same PRF output. -// -// Parameters: -// - prfOutput: The raw PRF output from WebAuthn -// - context: Additional context to bind the key to (e.g., "database", "mpc-share") -func DeriveKeyWithContext(prfOutput []byte, context string) ([]byte, error) { - if len(prfOutput) == 0 { - return nil, fmt.Errorf("enclave: PRF output cannot be empty") - } - if context == "" { - return nil, fmt.Errorf("enclave: context cannot be empty") - } - - salt := []byte(EnclaveSalt) - info := []byte(context) - - hkdfReader := hkdf.New(sha256.New, prfOutput, salt, info) - - key := make([]byte, KeySize) - if _, err := io.ReadFull(hkdfReader, key); err != nil { - return nil, fmt.Errorf("enclave: failed to derive key: %w", err) - } - - return key, nil -} - -// EncryptedData represents encrypted data with its metadata. -type EncryptedData struct { - // Nonce is the unique nonce used for this encryption (12 bytes) - Nonce []byte `json:"nonce"` - // Ciphertext is the encrypted data including the GCM authentication tag - Ciphertext []byte `json:"ciphertext"` - // Version indicates the encryption scheme version - Version int `json:"version"` -} - -// Encrypt encrypts plaintext using AES-256-GCM with the provided key. -// -// Parameters: -// - key: 32-byte encryption key (from DeriveEncryptionKey) -// - plaintext: The data to encrypt -// -// Returns: -// - EncryptedData containing nonce and ciphertext -// - An error if encryption fails -func Encrypt(key, plaintext []byte) (*EncryptedData, error) { - if len(key) != KeySize { - return nil, fmt.Errorf("enclave: invalid key size: got %d, want %d", len(key), KeySize) - } - - block, err := aes.NewCipher(key) - if err != nil { - return nil, fmt.Errorf("enclave: failed to create cipher: %w", err) - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, fmt.Errorf("enclave: failed to create GCM: %w", err) - } - - nonce := make([]byte, NonceSize) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, fmt.Errorf("enclave: failed to generate nonce: %w", err) - } - - ciphertext := gcm.Seal(nil, nonce, plaintext, nil) - - return &EncryptedData{ - Nonce: nonce, - Ciphertext: ciphertext, - Version: 1, - }, nil -} - -// Decrypt decrypts ciphertext using AES-256-GCM with the provided key. -// -// Parameters: -// - key: 32-byte encryption key (from DeriveEncryptionKey) -// - data: The EncryptedData to decrypt -// -// Returns: -// - The decrypted plaintext -// - An error if decryption fails (including authentication failure) -func Decrypt(key []byte, data *EncryptedData) ([]byte, error) { - if len(key) != KeySize { - return nil, fmt.Errorf("enclave: invalid key size: got %d, want %d", len(key), KeySize) - } - if data == nil { - return nil, fmt.Errorf("enclave: encrypted data cannot be nil") - } - if len(data.Nonce) != NonceSize { - return nil, fmt.Errorf("enclave: invalid nonce size: got %d, want %d", len(data.Nonce), NonceSize) - } - - block, err := aes.NewCipher(key) - if err != nil { - return nil, fmt.Errorf("enclave: failed to create cipher: %w", err) - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, fmt.Errorf("enclave: failed to create GCM: %w", err) - } - - plaintext, err := gcm.Open(nil, data.Nonce, data.Ciphertext, nil) - if err != nil { - return nil, fmt.Errorf("enclave: decryption failed (authentication error): %w", err) - } - - return plaintext, nil -} - -// EncryptBytes is a convenience function that encrypts and returns serialized bytes. -// The format is: version (1 byte) + nonce (12 bytes) + ciphertext (variable) -func EncryptBytes(key, plaintext []byte) ([]byte, error) { - data, err := Encrypt(key, plaintext) - if err != nil { - return nil, err - } - - result := make([]byte, 1+NonceSize+len(data.Ciphertext)) - result[0] = byte(data.Version) - copy(result[1:1+NonceSize], data.Nonce) - copy(result[1+NonceSize:], data.Ciphertext) - - return result, nil -} - -// DecryptBytes is a convenience function that decrypts serialized encrypted bytes. -// Expected format: version (1 byte) + nonce (12 bytes) + ciphertext (variable) -func DecryptBytes(key, encryptedBytes []byte) ([]byte, error) { - if len(encryptedBytes) < 1+NonceSize+AuthTagSize { - return nil, fmt.Errorf("enclave: encrypted data too short") - } - - version := int(encryptedBytes[0]) - if version != 1 { - return nil, fmt.Errorf("enclave: unsupported encryption version: %d", version) - } - - data := &EncryptedData{ - Version: version, - Nonce: encryptedBytes[1 : 1+NonceSize], - Ciphertext: encryptedBytes[1+NonceSize:], - } - - return Decrypt(key, data) -} - -// GenerateNonce generates a cryptographically secure random nonce. -func GenerateNonce() ([]byte, error) { - nonce := make([]byte, NonceSize) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, fmt.Errorf("enclave: failed to generate nonce: %w", err) - } - return nonce, nil -} - -// SecureZero zeros out a byte slice to prevent sensitive data from remaining in memory. -func SecureZero(b []byte) { - for i := range b { - b[i] = 0 - } -} diff --git a/internal/enclave/enclave.go b/internal/enclave/enclave.go deleted file mode 100644 index f013b15..0000000 --- a/internal/enclave/enclave.go +++ /dev/null @@ -1,184 +0,0 @@ -package enclave - -import ( - "encoding/json" - "fmt" - - "enclave/internal/keybase" -) - -// Enclave wraps a Keybase with encryption capabilities using WebAuthn PRF-derived keys. -type Enclave struct { - keybase *keybase.Keybase - encryptionKey []byte -} - -// Config holds enclave configuration options. -type Config struct { - PRFOutput []byte -} - -// New creates a new Enclave with the given PRF output for key derivation. -func New(prfOutput []byte) (*Enclave, error) { - if len(prfOutput) == 0 { - return nil, fmt.Errorf("enclave: PRF output required") - } - - key, err := DeriveEncryptionKey(prfOutput) - if err != nil { - return nil, fmt.Errorf("enclave: key derivation failed: %w", err) - } - - kb, err := keybase.Open() - if err != nil { - SecureZero(key) - return nil, fmt.Errorf("enclave: failed to open keybase: %w", err) - } - - return &Enclave{ - keybase: kb, - encryptionKey: key, - }, nil -} - -// Keybase returns the underlying keybase instance. -func (e *Enclave) Keybase() *keybase.Keybase { - return e.keybase -} - -// DID returns the current DID. -func (e *Enclave) DID() string { - return e.keybase.DID() -} - -// IsInitialized returns true if the enclave has been initialized with a DID. -func (e *Enclave) IsInitialized() bool { - return e.keybase.IsInitialized() -} - -// SerializeEncrypted exports the database state as encrypted bytes. -func (e *Enclave) SerializeEncrypted() ([]byte, error) { - plaintext, err := e.keybase.Serialize() - if err != nil { - return nil, fmt.Errorf("enclave: serialization failed: %w", err) - } - - encrypted, err := EncryptBytes(e.encryptionKey, plaintext) - if err != nil { - SecureZero(plaintext) - return nil, fmt.Errorf("enclave: encryption failed: %w", err) - } - - SecureZero(plaintext) - return encrypted, nil -} - -// LoadEncrypted loads the database state from encrypted bytes. -func (e *Enclave) LoadEncrypted(encryptedData []byte) error { - plaintext, err := DecryptBytes(e.encryptionKey, encryptedData) - if err != nil { - return fmt.Errorf("enclave: decryption failed: %w", err) - } - defer SecureZero(plaintext) - - return e.loadFromPlaintext(plaintext) -} - -// loadFromPlaintext parses and executes the SQL statements to restore database state. -func (e *Enclave) loadFromPlaintext(data []byte) error { - if len(data) == 0 { - return fmt.Errorf("enclave: empty data") - } - - return e.keybase.RestoreFromDump(data) -} - -// Close securely closes the enclave and zeros out sensitive data. -func (e *Enclave) Close() error { - SecureZero(e.encryptionKey) - return keybase.Close() -} - -// EncryptedBundle represents a complete encrypted database export. -type EncryptedBundle struct { - Version int `json:"version"` - DID string `json:"did"` - Ciphertext []byte `json:"ciphertext"` - Nonce []byte `json:"nonce"` -} - -// Export creates a complete encrypted bundle for storage. -func (e *Enclave) Export() (*EncryptedBundle, error) { - plaintext, err := e.keybase.Serialize() - if err != nil { - return nil, fmt.Errorf("enclave: serialization failed: %w", err) - } - defer SecureZero(plaintext) - - encData, err := Encrypt(e.encryptionKey, plaintext) - if err != nil { - return nil, fmt.Errorf("enclave: encryption failed: %w", err) - } - - return &EncryptedBundle{ - Version: encData.Version, - DID: e.keybase.DID(), - Ciphertext: encData.Ciphertext, - Nonce: encData.Nonce, - }, nil -} - -// Import loads an encrypted bundle. -func (e *Enclave) Import(bundle *EncryptedBundle) error { - if bundle == nil { - return fmt.Errorf("enclave: bundle cannot be nil") - } - - encData := &EncryptedData{ - Version: bundle.Version, - Ciphertext: bundle.Ciphertext, - Nonce: bundle.Nonce, - } - - plaintext, err := Decrypt(e.encryptionKey, encData) - if err != nil { - return fmt.Errorf("enclave: decryption failed: %w", err) - } - defer SecureZero(plaintext) - - return e.loadFromPlaintext(plaintext) -} - -// MarshalBundle serializes an encrypted bundle to JSON. -func (b *EncryptedBundle) Marshal() ([]byte, error) { - return json.Marshal(b) -} - -// UnmarshalBundle deserializes an encrypted bundle from JSON. -func UnmarshalBundle(data []byte) (*EncryptedBundle, error) { - var bundle EncryptedBundle - if err := json.Unmarshal(data, &bundle); err != nil { - return nil, fmt.Errorf("enclave: failed to unmarshal bundle: %w", err) - } - return &bundle, nil -} - -// FromExisting wraps an existing keybase with encryption capabilities. -func FromExisting(kb *keybase.Keybase, prfOutput []byte) (*Enclave, error) { - if kb == nil { - return nil, fmt.Errorf("enclave: keybase cannot be nil") - } - if len(prfOutput) == 0 { - return nil, fmt.Errorf("enclave: PRF output required") - } - - key, err := DeriveEncryptionKey(prfOutput) - if err != nil { - return nil, fmt.Errorf("enclave: key derivation failed: %w", err) - } - - return &Enclave{ - keybase: kb, - encryptionKey: key, - }, nil -}