You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							568 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							568 lines
						
					
					
						
							17 KiB
						
					
					
				
								package local
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"crypto/aes"
							 | 
						|
									"crypto/cipher"
							 | 
						|
									"crypto/rand"
							 | 
						|
									"encoding/json"
							 | 
						|
									"fmt"
							 | 
						|
									"io"
							 | 
						|
									"sort"
							 | 
						|
									"strings"
							 | 
						|
									"sync"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/kms"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/util"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// LocalKMSProvider implements a local, in-memory KMS for development and testing
							 | 
						|
								// WARNING: This is NOT suitable for production use - keys are stored in memory
							 | 
						|
								type LocalKMSProvider struct {
							 | 
						|
									mu                   sync.RWMutex
							 | 
						|
									keys                 map[string]*LocalKey
							 | 
						|
									defaultKeyID         string
							 | 
						|
									enableOnDemandCreate bool // Whether to create keys on-demand for missing key IDs
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// LocalKey represents a key stored in the local KMS
							 | 
						|
								type LocalKey struct {
							 | 
						|
									KeyID       string            `json:"keyId"`
							 | 
						|
									ARN         string            `json:"arn"`
							 | 
						|
									Description string            `json:"description"`
							 | 
						|
									KeyMaterial []byte            `json:"keyMaterial"` // 256-bit master key
							 | 
						|
									KeyUsage    kms.KeyUsage      `json:"keyUsage"`
							 | 
						|
									KeyState    kms.KeyState      `json:"keyState"`
							 | 
						|
									Origin      kms.KeyOrigin     `json:"origin"`
							 | 
						|
									CreatedAt   time.Time         `json:"createdAt"`
							 | 
						|
									Aliases     []string          `json:"aliases"`
							 | 
						|
									Metadata    map[string]string `json:"metadata"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// LocalKMSConfig contains configuration for the local KMS provider
							 | 
						|
								type LocalKMSConfig struct {
							 | 
						|
									DefaultKeyID         string               `json:"defaultKeyId"`
							 | 
						|
									Keys                 map[string]*LocalKey `json:"keys"`
							 | 
						|
									EnableOnDemandCreate bool                 `json:"enableOnDemandCreate"`
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func init() {
							 | 
						|
									// Register the local KMS provider
							 | 
						|
									kms.RegisterProvider("local", NewLocalKMSProvider)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewLocalKMSProvider creates a new local KMS provider
							 | 
						|
								func NewLocalKMSProvider(config util.Configuration) (kms.KMSProvider, error) {
							 | 
						|
									provider := &LocalKMSProvider{
							 | 
						|
										keys:                 make(map[string]*LocalKey),
							 | 
						|
										enableOnDemandCreate: true, // Default to true for development/testing convenience
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Load configuration if provided
							 | 
						|
									if config != nil {
							 | 
						|
										if err := provider.loadConfig(config); err != nil {
							 | 
						|
											return nil, fmt.Errorf("failed to load local KMS config: %v", err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create a default key if none exists
							 | 
						|
									if len(provider.keys) == 0 {
							 | 
						|
										defaultKey, err := provider.createDefaultKey()
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("failed to create default key: %v", err)
							 | 
						|
										}
							 | 
						|
										provider.defaultKeyID = defaultKey.KeyID
							 | 
						|
										glog.V(1).Infof("Local KMS: Created default key %s", defaultKey.KeyID)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return provider, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// loadConfig loads configuration from the provided config
							 | 
						|
								func (p *LocalKMSProvider) loadConfig(config util.Configuration) error {
							 | 
						|
									if config == nil {
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									p.enableOnDemandCreate = config.GetBool("enableOnDemandCreate")
							 | 
						|
								
							 | 
						|
									// TODO: Load pre-existing keys from configuration if provided
							 | 
						|
									// For now, rely on default key creation in constructor
							 | 
						|
								
							 | 
						|
									glog.V(2).Infof("Local KMS: enableOnDemandCreate = %v", p.enableOnDemandCreate)
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// createDefaultKey creates a default master key for the local KMS
							 | 
						|
								func (p *LocalKMSProvider) createDefaultKey() (*LocalKey, error) {
							 | 
						|
									keyID, err := generateKeyID()
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate key ID: %w", err)
							 | 
						|
									}
							 | 
						|
									keyMaterial := make([]byte, 32) // 256-bit key
							 | 
						|
									if _, err := io.ReadFull(rand.Reader, keyMaterial); err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate key material: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									key := &LocalKey{
							 | 
						|
										KeyID:       keyID,
							 | 
						|
										ARN:         fmt.Sprintf("arn:aws:kms:local:000000000000:key/%s", keyID),
							 | 
						|
										Description: "Default local KMS key for SeaweedFS",
							 | 
						|
										KeyMaterial: keyMaterial,
							 | 
						|
										KeyUsage:    kms.KeyUsageEncryptDecrypt,
							 | 
						|
										KeyState:    kms.KeyStateEnabled,
							 | 
						|
										Origin:      kms.KeyOriginLocal,
							 | 
						|
										CreatedAt:   time.Now(),
							 | 
						|
										Aliases:     []string{"alias/seaweedfs-default"},
							 | 
						|
										Metadata:    make(map[string]string),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									p.mu.Lock()
							 | 
						|
									defer p.mu.Unlock()
							 | 
						|
									p.keys[keyID] = key
							 | 
						|
								
							 | 
						|
									// Also register aliases
							 | 
						|
									for _, alias := range key.Aliases {
							 | 
						|
										p.keys[alias] = key
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return key, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GenerateDataKey implements the KMSProvider interface
							 | 
						|
								func (p *LocalKMSProvider) GenerateDataKey(ctx context.Context, req *kms.GenerateDataKeyRequest) (*kms.GenerateDataKeyResponse, error) {
							 | 
						|
									if req.KeySpec != kms.KeySpecAES256 {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeInvalidKeyUsage,
							 | 
						|
											Message: fmt.Sprintf("Unsupported key spec: %s", req.KeySpec),
							 | 
						|
											KeyID:   req.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Resolve the key
							 | 
						|
									key, err := p.getKey(req.KeyID)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if key.KeyState != kms.KeyStateEnabled {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeKeyUnavailable,
							 | 
						|
											Message: fmt.Sprintf("Key %s is in state %s", key.KeyID, key.KeyState),
							 | 
						|
											KeyID:   key.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Generate a random 256-bit data key
							 | 
						|
									dataKey := make([]byte, 32)
							 | 
						|
									if _, err := io.ReadFull(rand.Reader, dataKey); err != nil {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeKMSInternalFailure,
							 | 
						|
											Message: "Failed to generate data key",
							 | 
						|
											KeyID:   key.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Encrypt the data key with the master key
							 | 
						|
									encryptedDataKey, err := p.encryptDataKey(dataKey, key, req.EncryptionContext)
							 | 
						|
									if err != nil {
							 | 
						|
										kms.ClearSensitiveData(dataKey)
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeKMSInternalFailure,
							 | 
						|
											Message: fmt.Sprintf("Failed to encrypt data key: %v", err),
							 | 
						|
											KeyID:   key.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &kms.GenerateDataKeyResponse{
							 | 
						|
										KeyID:          key.KeyID,
							 | 
						|
										Plaintext:      dataKey,
							 | 
						|
										CiphertextBlob: encryptedDataKey,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Decrypt implements the KMSProvider interface
							 | 
						|
								func (p *LocalKMSProvider) Decrypt(ctx context.Context, req *kms.DecryptRequest) (*kms.DecryptResponse, error) {
							 | 
						|
									// Parse the encrypted data key to extract metadata
							 | 
						|
									metadata, err := p.parseEncryptedDataKey(req.CiphertextBlob)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeInvalidCiphertext,
							 | 
						|
											Message: fmt.Sprintf("Invalid ciphertext format: %v", err),
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Verify encryption context matches
							 | 
						|
									if !p.encryptionContextMatches(metadata.EncryptionContext, req.EncryptionContext) {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeInvalidCiphertext,
							 | 
						|
											Message: "Encryption context mismatch",
							 | 
						|
											KeyID:   metadata.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Get the master key
							 | 
						|
									key, err := p.getKey(metadata.KeyID)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if key.KeyState != kms.KeyStateEnabled {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeKeyUnavailable,
							 | 
						|
											Message: fmt.Sprintf("Key %s is in state %s", key.KeyID, key.KeyState),
							 | 
						|
											KeyID:   key.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Decrypt the data key
							 | 
						|
									dataKey, err := p.decryptDataKey(metadata, key)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, &kms.KMSError{
							 | 
						|
											Code:    kms.ErrCodeInvalidCiphertext,
							 | 
						|
											Message: fmt.Sprintf("Failed to decrypt data key: %v", err),
							 | 
						|
											KeyID:   key.KeyID,
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &kms.DecryptResponse{
							 | 
						|
										KeyID:     key.KeyID,
							 | 
						|
										Plaintext: dataKey,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// DescribeKey implements the KMSProvider interface
							 | 
						|
								func (p *LocalKMSProvider) DescribeKey(ctx context.Context, req *kms.DescribeKeyRequest) (*kms.DescribeKeyResponse, error) {
							 | 
						|
									key, err := p.getKey(req.KeyID)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &kms.DescribeKeyResponse{
							 | 
						|
										KeyID:       key.KeyID,
							 | 
						|
										ARN:         key.ARN,
							 | 
						|
										Description: key.Description,
							 | 
						|
										KeyUsage:    key.KeyUsage,
							 | 
						|
										KeyState:    key.KeyState,
							 | 
						|
										Origin:      key.Origin,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// GetKeyID implements the KMSProvider interface
							 | 
						|
								func (p *LocalKMSProvider) GetKeyID(ctx context.Context, keyIdentifier string) (string, error) {
							 | 
						|
									key, err := p.getKey(keyIdentifier)
							 | 
						|
									if err != nil {
							 | 
						|
										return "", err
							 | 
						|
									}
							 | 
						|
									return key.KeyID, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Close implements the KMSProvider interface
							 | 
						|
								func (p *LocalKMSProvider) Close() error {
							 | 
						|
									p.mu.Lock()
							 | 
						|
									defer p.mu.Unlock()
							 | 
						|
								
							 | 
						|
									// Clear all key material from memory
							 | 
						|
									for _, key := range p.keys {
							 | 
						|
										kms.ClearSensitiveData(key.KeyMaterial)
							 | 
						|
									}
							 | 
						|
									p.keys = make(map[string]*LocalKey)
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// getKey retrieves a key by ID or alias, creating it on-demand if it doesn't exist
							 | 
						|
								func (p *LocalKMSProvider) getKey(keyIdentifier string) (*LocalKey, error) {
							 | 
						|
									p.mu.RLock()
							 | 
						|
								
							 | 
						|
									// Try direct lookup first
							 | 
						|
									if key, exists := p.keys[keyIdentifier]; exists {
							 | 
						|
										p.mu.RUnlock()
							 | 
						|
										return key, nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Try with default key if no identifier provided
							 | 
						|
									if keyIdentifier == "" && p.defaultKeyID != "" {
							 | 
						|
										if key, exists := p.keys[p.defaultKeyID]; exists {
							 | 
						|
											p.mu.RUnlock()
							 | 
						|
											return key, nil
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									p.mu.RUnlock()
							 | 
						|
								
							 | 
						|
									// Key doesn't exist - create on-demand if enabled and key identifier is reasonable
							 | 
						|
									if keyIdentifier != "" && p.enableOnDemandCreate && p.isReasonableKeyIdentifier(keyIdentifier) {
							 | 
						|
										glog.V(1).Infof("Creating on-demand local KMS key: %s", keyIdentifier)
							 | 
						|
										key, err := p.CreateKeyWithID(keyIdentifier, fmt.Sprintf("Auto-created local KMS key: %s", keyIdentifier))
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, &kms.KMSError{
							 | 
						|
												Code:    kms.ErrCodeKMSInternalFailure,
							 | 
						|
												Message: fmt.Sprintf("Failed to create on-demand key %s: %v", keyIdentifier, err),
							 | 
						|
												KeyID:   keyIdentifier,
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
										return key, nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil, &kms.KMSError{
							 | 
						|
										Code:    kms.ErrCodeNotFoundException,
							 | 
						|
										Message: fmt.Sprintf("Key not found: %s", keyIdentifier),
							 | 
						|
										KeyID:   keyIdentifier,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// isReasonableKeyIdentifier determines if a key identifier is reasonable for on-demand creation
							 | 
						|
								func (p *LocalKMSProvider) isReasonableKeyIdentifier(keyIdentifier string) bool {
							 | 
						|
									// Basic validation: reasonable length and character set
							 | 
						|
									if len(keyIdentifier) < 3 || len(keyIdentifier) > 100 {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Allow alphanumeric characters, hyphens, underscores, and forward slashes
							 | 
						|
									// This covers most reasonable key identifier formats without being overly restrictive
							 | 
						|
									for _, r := range keyIdentifier {
							 | 
						|
										if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
							 | 
						|
											(r >= '0' && r <= '9') || r == '-' || r == '_' || r == '/') {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Reject keys that start or end with separators
							 | 
						|
									if keyIdentifier[0] == '-' || keyIdentifier[0] == '_' || keyIdentifier[0] == '/' ||
							 | 
						|
										keyIdentifier[len(keyIdentifier)-1] == '-' || keyIdentifier[len(keyIdentifier)-1] == '_' || keyIdentifier[len(keyIdentifier)-1] == '/' {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// encryptedDataKeyMetadata represents the metadata stored with encrypted data keys
							 | 
						|
								type encryptedDataKeyMetadata struct {
							 | 
						|
									KeyID             string            `json:"keyId"`
							 | 
						|
									EncryptionContext map[string]string `json:"encryptionContext"`
							 | 
						|
									EncryptedData     []byte            `json:"encryptedData"`
							 | 
						|
									Nonce             []byte            `json:"nonce"` // Renamed from IV to be more explicit about AES-GCM usage
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// encryptDataKey encrypts a data key using the master key with AES-GCM for authenticated encryption
							 | 
						|
								func (p *LocalKMSProvider) encryptDataKey(dataKey []byte, masterKey *LocalKey, encryptionContext map[string]string) ([]byte, error) {
							 | 
						|
									block, err := aes.NewCipher(masterKey.KeyMaterial)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									gcm, err := cipher.NewGCM(block)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Generate a random nonce
							 | 
						|
									nonce := make([]byte, gcm.NonceSize())
							 | 
						|
									if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Prepare additional authenticated data (AAD) from the encryption context
							 | 
						|
									// Use deterministic marshaling to ensure consistent AAD
							 | 
						|
									var aad []byte
							 | 
						|
									if len(encryptionContext) > 0 {
							 | 
						|
										var err error
							 | 
						|
										aad, err = marshalEncryptionContextDeterministic(encryptionContext)
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("failed to marshal encryption context for AAD: %w", err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Encrypt using AES-GCM
							 | 
						|
									encryptedData := gcm.Seal(nil, nonce, dataKey, aad)
							 | 
						|
								
							 | 
						|
									// Create metadata structure
							 | 
						|
									metadata := &encryptedDataKeyMetadata{
							 | 
						|
										KeyID:             masterKey.KeyID,
							 | 
						|
										EncryptionContext: encryptionContext,
							 | 
						|
										EncryptedData:     encryptedData,
							 | 
						|
										Nonce:             nonce,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Serialize metadata to JSON
							 | 
						|
									return json.Marshal(metadata)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// decryptDataKey decrypts a data key using the master key with AES-GCM for authenticated decryption
							 | 
						|
								func (p *LocalKMSProvider) decryptDataKey(metadata *encryptedDataKeyMetadata, masterKey *LocalKey) ([]byte, error) {
							 | 
						|
									block, err := aes.NewCipher(masterKey.KeyMaterial)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									gcm, err := cipher.NewGCM(block)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Prepare additional authenticated data (AAD)
							 | 
						|
									var aad []byte
							 | 
						|
									if len(metadata.EncryptionContext) > 0 {
							 | 
						|
										var err error
							 | 
						|
										aad, err = marshalEncryptionContextDeterministic(metadata.EncryptionContext)
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("failed to marshal encryption context for AAD: %w", err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Decrypt using AES-GCM
							 | 
						|
									nonce := metadata.Nonce
							 | 
						|
									if len(nonce) != gcm.NonceSize() {
							 | 
						|
										return nil, fmt.Errorf("invalid nonce size: expected %d, got %d", gcm.NonceSize(), len(nonce))
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									dataKey, err := gcm.Open(nil, nonce, metadata.EncryptedData, aad)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to decrypt with GCM: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return dataKey, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// parseEncryptedDataKey parses the encrypted data key blob
							 | 
						|
								func (p *LocalKMSProvider) parseEncryptedDataKey(ciphertextBlob []byte) (*encryptedDataKeyMetadata, error) {
							 | 
						|
									var metadata encryptedDataKeyMetadata
							 | 
						|
									if err := json.Unmarshal(ciphertextBlob, &metadata); err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to parse ciphertext blob: %v", err)
							 | 
						|
									}
							 | 
						|
									return &metadata, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// encryptionContextMatches checks if two encryption contexts match
							 | 
						|
								func (p *LocalKMSProvider) encryptionContextMatches(ctx1, ctx2 map[string]string) bool {
							 | 
						|
									if len(ctx1) != len(ctx2) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
									for k, v := range ctx1 {
							 | 
						|
										if ctx2[k] != v {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// generateKeyID generates a random key ID
							 | 
						|
								func generateKeyID() (string, error) {
							 | 
						|
									// Generate a UUID-like key ID
							 | 
						|
									b := make([]byte, 16)
							 | 
						|
									if _, err := io.ReadFull(rand.Reader, b); err != nil {
							 | 
						|
										return "", fmt.Errorf("failed to generate random bytes for key ID: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
							 | 
						|
										b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CreateKey creates a new key in the local KMS (for testing)
							 | 
						|
								func (p *LocalKMSProvider) CreateKey(description string, aliases []string) (*LocalKey, error) {
							 | 
						|
									keyID, err := generateKeyID()
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate key ID: %w", err)
							 | 
						|
									}
							 | 
						|
									keyMaterial := make([]byte, 32)
							 | 
						|
									if _, err := io.ReadFull(rand.Reader, keyMaterial); err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									key := &LocalKey{
							 | 
						|
										KeyID:       keyID,
							 | 
						|
										ARN:         fmt.Sprintf("arn:aws:kms:local:000000000000:key/%s", keyID),
							 | 
						|
										Description: description,
							 | 
						|
										KeyMaterial: keyMaterial,
							 | 
						|
										KeyUsage:    kms.KeyUsageEncryptDecrypt,
							 | 
						|
										KeyState:    kms.KeyStateEnabled,
							 | 
						|
										Origin:      kms.KeyOriginLocal,
							 | 
						|
										CreatedAt:   time.Now(),
							 | 
						|
										Aliases:     aliases,
							 | 
						|
										Metadata:    make(map[string]string),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									p.mu.Lock()
							 | 
						|
									defer p.mu.Unlock()
							 | 
						|
								
							 | 
						|
									p.keys[keyID] = key
							 | 
						|
									for _, alias := range aliases {
							 | 
						|
										// Ensure alias has proper format
							 | 
						|
										if !strings.HasPrefix(alias, "alias/") {
							 | 
						|
											alias = "alias/" + alias
							 | 
						|
										}
							 | 
						|
										p.keys[alias] = key
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return key, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CreateKeyWithID creates a key with a specific keyID (for testing only)
							 | 
						|
								func (p *LocalKMSProvider) CreateKeyWithID(keyID, description string) (*LocalKey, error) {
							 | 
						|
									keyMaterial := make([]byte, 32)
							 | 
						|
									if _, err := io.ReadFull(rand.Reader, keyMaterial); err != nil {
							 | 
						|
										return nil, fmt.Errorf("failed to generate key material: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									key := &LocalKey{
							 | 
						|
										KeyID:       keyID,
							 | 
						|
										ARN:         fmt.Sprintf("arn:aws:kms:local:000000000000:key/%s", keyID),
							 | 
						|
										Description: description,
							 | 
						|
										KeyMaterial: keyMaterial,
							 | 
						|
										KeyUsage:    kms.KeyUsageEncryptDecrypt,
							 | 
						|
										KeyState:    kms.KeyStateEnabled,
							 | 
						|
										Origin:      kms.KeyOriginLocal,
							 | 
						|
										CreatedAt:   time.Now(),
							 | 
						|
										Aliases:     []string{}, // No aliases by default
							 | 
						|
										Metadata:    make(map[string]string),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									p.mu.Lock()
							 | 
						|
									defer p.mu.Unlock()
							 | 
						|
								
							 | 
						|
									// Register key with the exact keyID provided
							 | 
						|
									p.keys[keyID] = key
							 | 
						|
								
							 | 
						|
									return key, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// marshalEncryptionContextDeterministic creates a deterministic byte representation of encryption context
							 | 
						|
								// This ensures that the same encryption context always produces the same AAD for AES-GCM
							 | 
						|
								func marshalEncryptionContextDeterministic(encryptionContext map[string]string) ([]byte, error) {
							 | 
						|
									if len(encryptionContext) == 0 {
							 | 
						|
										return nil, nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Sort keys to ensure deterministic output
							 | 
						|
									keys := make([]string, 0, len(encryptionContext))
							 | 
						|
									for k := range encryptionContext {
							 | 
						|
										keys = append(keys, k)
							 | 
						|
									}
							 | 
						|
									sort.Strings(keys)
							 | 
						|
								
							 | 
						|
									// Build deterministic representation with proper JSON escaping
							 | 
						|
									var buf strings.Builder
							 | 
						|
									buf.WriteString("{")
							 | 
						|
									for i, k := range keys {
							 | 
						|
										if i > 0 {
							 | 
						|
											buf.WriteString(",")
							 | 
						|
										}
							 | 
						|
										// Marshal key and value to get proper JSON string escaping
							 | 
						|
										keyBytes, err := json.Marshal(k)
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("failed to marshal encryption context key '%s': %w", k, err)
							 | 
						|
										}
							 | 
						|
										valueBytes, err := json.Marshal(encryptionContext[k])
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("failed to marshal encryption context value for key '%s': %w", k, err)
							 | 
						|
										}
							 | 
						|
										buf.Write(keyBytes)
							 | 
						|
										buf.WriteString(":")
							 | 
						|
										buf.Write(valueBytes)
							 | 
						|
									}
							 | 
						|
									buf.WriteString("}")
							 | 
						|
								
							 | 
						|
									return []byte(buf.String()), nil
							 | 
						|
								}
							 |