Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,235 @@
// 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
}