Files
StarFleetCPTN 31871bd16e feat: Implement storage provider management functionality
- Added new routes and handlers for managing storage providers, including creation, editing, and deletion.
- Introduced a new StorageProvider form component for user input.
- Enhanced the database schema to support storage provider references in transfer configurations.
- Implemented encryption for sensitive fields in storage provider data.
- Added tests for storage provider API endpoints and integration with the database.
- Updated frontend components to support storage provider selection and testing.
2025-04-16 17:18:53 -07:00

195 lines
5.5 KiB
Go

package encryption
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"io"
)
// Standard errors for encryption operations
var (
ErrEncryptionFailed = errors.New("encryption failed")
ErrDecryptionFailed = errors.New("decryption failed")
ErrInvalidBlockSize = errors.New("invalid block size")
ErrInvalidCiphertext = errors.New("invalid ciphertext")
ErrInvalidKeySize = errors.New("invalid key size")
ErrEmptyPlaintext = errors.New("plaintext is empty")
ErrEmptyCiphertext = errors.New("ciphertext is empty")
ErrMissingIV = errors.New("initialization vector missing")
)
// EncryptionService provides methods to encrypt and decrypt data
type EncryptionService struct {
keyManager KeyManager
}
// NewEncryptionService creates a new encryption service using the provided key manager
func NewEncryptionService(km KeyManager) (*EncryptionService, error) {
if km == nil {
return nil, errors.New("key manager is required")
}
return &EncryptionService{keyManager: km}, nil
}
// Encrypt encrypts the plaintext using AES-256-CBC with PKCS7 padding
// It returns a base64-encoded string of the IV + ciphertext
func (s *EncryptionService) Encrypt(plaintext []byte) (string, error) {
if len(plaintext) == 0 {
return "", ErrEmptyPlaintext
}
// Get the encryption key
key, err := s.keyManager.GetPrimaryKey()
if err != nil {
return "", fmt.Errorf("failed to get encryption key: %w", err)
}
// Create a new AES cipher block
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("%w: %v", ErrEncryptionFailed, err)
}
// Pad the plaintext to be a multiple of the block size
paddedPlaintext := pkcs7Pad(plaintext, block.BlockSize())
// Generate a random IV
iv := make([]byte, block.BlockSize())
if _, err := io.ReadFull(SecureRandomReader, iv); err != nil {
return "", fmt.Errorf("%w: failed to generate IV: %v", ErrEncryptionFailed, err)
}
// Create CBC encrypter
mode := cipher.NewCBCEncrypter(block, iv)
// Encrypt the data
ciphertext := make([]byte, len(paddedPlaintext))
mode.CryptBlocks(ciphertext, paddedPlaintext)
// Prepend IV to ciphertext
combined := append(iv, ciphertext...)
// Encode with base64
encoded := base64.StdEncoding.EncodeToString(combined)
return encoded, nil
}
// Decrypt decrypts the base64-encoded ciphertext using AES-256-CBC with PKCS7 padding
// It expects the ciphertext to be a base64-encoded string of the IV + actual ciphertext
func (s *EncryptionService) Decrypt(encodedCiphertext string) ([]byte, error) {
if encodedCiphertext == "" {
return nil, ErrEmptyCiphertext
}
// Decode the base64 encoded data
combined, err := base64.StdEncoding.DecodeString(encodedCiphertext)
if err != nil {
return nil, fmt.Errorf("%w: invalid base64 encoding: %v", ErrDecryptionFailed, err)
}
// Get the encryption key
key, err := s.keyManager.GetPrimaryKey()
if err != nil {
return nil, fmt.Errorf("failed to get encryption key: %w", err)
}
// Create a new AES cipher block
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrDecryptionFailed, err)
}
// Extract IV and ciphertext
blockSize := block.BlockSize()
if len(combined) < blockSize {
return nil, ErrMissingIV
}
iv := combined[:blockSize]
ciphertext := combined[blockSize:]
// Verify ciphertext length
if len(ciphertext) == 0 {
return nil, ErrEmptyCiphertext
}
if len(ciphertext)%blockSize != 0 {
return nil, ErrInvalidBlockSize
}
// Create CBC decrypter
mode := cipher.NewCBCDecrypter(block, iv)
// Decrypt the data
decrypted := make([]byte, len(ciphertext))
mode.CryptBlocks(decrypted, ciphertext)
// Remove padding
unpadded, err := pkcs7Unpad(decrypted, blockSize)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrDecryptionFailed, err)
}
return unpadded, nil
}
// EncryptString encrypts a string and returns a base64-encoded result
func (s *EncryptionService) EncryptString(plaintext string) (string, error) {
return s.Encrypt([]byte(plaintext))
}
// DecryptString decrypts a base64-encoded ciphertext and returns the plaintext string
func (s *EncryptionService) DecryptString(encodedCiphertext string) (string, error) {
plaintext, err := s.Decrypt(encodedCiphertext)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// pkcs7Pad adds PKCS#7 padding to the data to make it a multiple of the block size
func pkcs7Pad(data []byte, blockSize int) []byte {
padding := blockSize - (len(data) % blockSize)
padText := make([]byte, padding)
for i := range padText {
padText[i] = byte(padding)
}
return append(data, padText...)
}
// pkcs7Unpad removes PKCS#7 padding from the data
func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
if len(data) == 0 || len(data)%blockSize != 0 {
return nil, ErrInvalidBlockSize
}
padding := int(data[len(data)-1])
if padding <= 0 || padding > blockSize {
return nil, errors.New("invalid padding value")
}
// Validate that all padding bytes have the correct value
for i := len(data) - padding; i < len(data); i++ {
if data[i] != byte(padding) {
return nil, errors.New("invalid padding")
}
}
return data[:len(data)-padding], nil
}
// GetGlobalEncryptionService creates an EncryptionService using the global key manager
// It initializes the key manager if it hasn't been initialized yet
func GetGlobalEncryptionService() (*EncryptionService, error) {
// Make sure key manager is initialized
if GetKeyManager() == nil {
if err := InitializeKeyManager(""); err != nil {
return nil, fmt.Errorf("failed to initialize key manager: %w", err)
}
}
return NewEncryptionService(GetKeyManager())
}