// Package e2ee provides end-to-end encryption operations for the MapleFile SDK. package e2ee import ( "encoding/json" "fmt" ) // FileMetadata represents decrypted file metadata. type FileMetadata struct { Name string `json:"name"` MimeType string `json:"mime_type"` Size int64 `json:"size"` CreatedAt int64 `json:"created_at"` } // EncryptFile encrypts file content using the file key. // Returns the combined nonce + ciphertext. // NOTE: This uses ChaCha20-Poly1305 (12-byte nonce). For web frontend compatibility, // use EncryptFileSecretBox instead. func EncryptFile(plaintext, fileKey []byte) ([]byte, error) { encryptedData, err := Encrypt(plaintext, fileKey) if err != nil { return nil, fmt.Errorf("failed to encrypt file: %w", err) } // Combine nonce and ciphertext for storage combined := CombineNonceAndCiphertext(encryptedData.Nonce, encryptedData.Ciphertext) return combined, nil } // EncryptFileSecretBox encrypts file content using XSalsa20-Poly1305 (NaCl secretbox). // Returns the combined nonce (24 bytes) + ciphertext. // This is compatible with the web frontend's libsodium implementation. func EncryptFileSecretBox(plaintext, fileKey []byte) ([]byte, error) { encryptedData, err := EncryptWithSecretBox(plaintext, fileKey) if err != nil { return nil, fmt.Errorf("failed to encrypt file: %w", err) } // Combine nonce and ciphertext for storage (matching web frontend format) combined := CombineNonceAndCiphertext(encryptedData.Nonce, encryptedData.Ciphertext) return combined, nil } // DecryptFile decrypts file content using the file key. // The input should be combined nonce + ciphertext. // Auto-detects the cipher based on nonce size: // - 24-byte nonce: XSalsa20-Poly1305 (web frontend / SecretBox) // - 12-byte nonce: ChaCha20-Poly1305 (legacy native app) func DecryptFile(encryptedData, fileKey []byte) ([]byte, error) { // Split nonce and ciphertext (auto-detect nonce size) nonce, ciphertext, err := SplitNonceAndCiphertextAuto(encryptedData) if err != nil { return nil, fmt.Errorf("failed to split encrypted data: %w", err) } // Decrypt using appropriate algorithm based on nonce size plaintext, err := DecryptWithAlgorithm(ciphertext, nonce, fileKey) if err != nil { return nil, fmt.Errorf("failed to decrypt file: %w", err) } return plaintext, nil } // EncryptFileWithNonce encrypts file content and returns the ciphertext and nonce separately. func EncryptFileWithNonce(plaintext, fileKey []byte) (ciphertext []byte, nonce []byte, err error) { encryptedData, err := Encrypt(plaintext, fileKey) if err != nil { return nil, nil, fmt.Errorf("failed to encrypt file: %w", err) } return encryptedData.Ciphertext, encryptedData.Nonce, nil } // DecryptFileWithNonce decrypts file content using separate ciphertext and nonce. func DecryptFileWithNonce(ciphertext, nonce, fileKey []byte) ([]byte, error) { plaintext, err := Decrypt(ciphertext, nonce, fileKey) if err != nil { return nil, fmt.Errorf("failed to decrypt file: %w", err) } return plaintext, nil } // EncryptMetadata encrypts file metadata using the file key. // Returns base64-encoded combined nonce + ciphertext. // NOTE: This uses ChaCha20-Poly1305 (12-byte nonce). For web frontend compatibility, // use EncryptMetadataSecretBox instead. func EncryptMetadata(metadata *FileMetadata, fileKey []byte) (string, error) { // Convert metadata to JSON metadataBytes, err := json.Marshal(metadata) if err != nil { return "", fmt.Errorf("failed to marshal metadata: %w", err) } // Encrypt metadata encryptedData, err := Encrypt(metadataBytes, fileKey) if err != nil { return "", fmt.Errorf("failed to encrypt metadata: %w", err) } // Combine nonce and ciphertext combined := CombineNonceAndCiphertext(encryptedData.Nonce, encryptedData.Ciphertext) // Encode to base64 return EncodeToBase64(combined), nil } // EncryptMetadataSecretBox encrypts file metadata using XSalsa20-Poly1305 (NaCl secretbox). // Returns base64-encoded combined nonce + ciphertext. // This is compatible with the web frontend's libsodium implementation. func EncryptMetadataSecretBox(metadata *FileMetadata, fileKey []byte) (string, error) { // Convert metadata to JSON metadataBytes, err := json.Marshal(metadata) if err != nil { return "", fmt.Errorf("failed to marshal metadata: %w", err) } // Encrypt metadata using SecretBox encryptedData, err := EncryptWithSecretBox(metadataBytes, fileKey) if err != nil { return "", fmt.Errorf("failed to encrypt metadata: %w", err) } // Combine nonce and ciphertext combined := CombineNonceAndCiphertext(encryptedData.Nonce, encryptedData.Ciphertext) // Encode to base64 return EncodeToBase64(combined), nil } // DecryptMetadata decrypts file metadata using the file key. // The input should be base64-encoded combined nonce + ciphertext. func DecryptMetadata(encryptedMetadata string, fileKey []byte) (*FileMetadata, error) { // Decode from base64 combined, err := DecodeFromBase64(encryptedMetadata) if err != nil { return nil, fmt.Errorf("failed to decode encrypted metadata: %w", err) } // Split nonce and ciphertext nonce, ciphertext, err := SplitNonceAndCiphertext(combined) if err != nil { return nil, fmt.Errorf("failed to split encrypted metadata: %w", err) } // Decrypt decryptedBytes, err := Decrypt(ciphertext, nonce, fileKey) if err != nil { return nil, fmt.Errorf("failed to decrypt metadata: %w", err) } // Parse JSON var metadata FileMetadata if err := json.Unmarshal(decryptedBytes, &metadata); err != nil { return nil, fmt.Errorf("failed to parse decrypted metadata: %w", err) } return &metadata, nil } // EncryptMetadataWithNonce encrypts file metadata and returns nonce separately. func EncryptMetadataWithNonce(metadata *FileMetadata, fileKey []byte) (ciphertext []byte, nonce []byte, err error) { // Convert metadata to JSON metadataBytes, err := json.Marshal(metadata) if err != nil { return nil, nil, fmt.Errorf("failed to marshal metadata: %w", err) } // Encrypt metadata encryptedData, err := Encrypt(metadataBytes, fileKey) if err != nil { return nil, nil, fmt.Errorf("failed to encrypt metadata: %w", err) } return encryptedData.Ciphertext, encryptedData.Nonce, nil } // DecryptMetadataWithNonce decrypts file metadata using separate ciphertext and nonce. func DecryptMetadataWithNonce(ciphertext, nonce, fileKey []byte) (*FileMetadata, error) { // Decrypt decryptedBytes, err := Decrypt(ciphertext, nonce, fileKey) if err != nil { return nil, fmt.Errorf("failed to decrypt metadata: %w", err) } // Parse JSON var metadata FileMetadata if err := json.Unmarshal(decryptedBytes, &metadata); err != nil { return nil, fmt.Errorf("failed to parse decrypted metadata: %w", err) } return &metadata, nil } // EncryptData encrypts arbitrary data using the provided key. // Returns base64-encoded combined nonce + ciphertext. func EncryptData(data, key []byte) (string, error) { encryptedData, err := Encrypt(data, key) if err != nil { return "", fmt.Errorf("failed to encrypt data: %w", err) } // Combine nonce and ciphertext combined := CombineNonceAndCiphertext(encryptedData.Nonce, encryptedData.Ciphertext) // Encode to base64 return EncodeToBase64(combined), nil } // DecryptData decrypts arbitrary data using the provided key. // The input should be base64-encoded combined nonce + ciphertext. func DecryptData(encryptedData string, key []byte) ([]byte, error) { // Decode from base64 combined, err := DecodeFromBase64(encryptedData) if err != nil { return nil, fmt.Errorf("failed to decode encrypted data: %w", err) } // Split nonce and ciphertext nonce, ciphertext, err := SplitNonceAndCiphertext(combined) if err != nil { return nil, fmt.Errorf("failed to split encrypted data: %w", err) } // Decrypt plaintext, err := Decrypt(ciphertext, nonce, key) if err != nil { return nil, fmt.Errorf("failed to decrypt data: %w", err) } return plaintext, nil }