235 lines
7.8 KiB
Go
235 lines
7.8 KiB
Go
// 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
|
|
}
|