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.
		
		
		
		
		
			
		
			
				
					
					
						
							628 lines
						
					
					
						
							18 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							628 lines
						
					
					
						
							18 KiB
						
					
					
				
								package s3api
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"bytes"
							 | 
						|
									"io"
							 | 
						|
									"net/http"
							 | 
						|
									"strings"
							 | 
						|
									"testing"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestSSECObjectCopy tests copying SSE-C encrypted objects with different keys
							 | 
						|
								func TestSSECObjectCopy(t *testing.T) {
							 | 
						|
									// Original key for source object
							 | 
						|
									sourceKey := GenerateTestSSECKey(1)
							 | 
						|
									sourceCustomerKey := &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       sourceKey.Key,
							 | 
						|
										KeyMD5:    sourceKey.KeyMD5,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Destination key for target object
							 | 
						|
									destKey := GenerateTestSSECKey(2)
							 | 
						|
									destCustomerKey := &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       destKey.Key,
							 | 
						|
										KeyMD5:    destKey.KeyMD5,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									testData := "Hello, SSE-C copy world!"
							 | 
						|
								
							 | 
						|
									// Encrypt with source key
							 | 
						|
									encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(testData), sourceCustomerKey)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Test copy strategy determination
							 | 
						|
									sourceMetadata := make(map[string][]byte)
							 | 
						|
									StoreIVInMetadata(sourceMetadata, iv)
							 | 
						|
									sourceMetadata[s3_constants.AmzServerSideEncryptionCustomerAlgorithm] = []byte("AES256")
							 | 
						|
									sourceMetadata[s3_constants.AmzServerSideEncryptionCustomerKeyMD5] = []byte(sourceKey.KeyMD5)
							 | 
						|
								
							 | 
						|
									t.Run("Same key copy (direct copy)", func(t *testing.T) {
							 | 
						|
										strategy, err := DetermineSSECCopyStrategy(sourceMetadata, sourceCustomerKey, sourceCustomerKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to determine copy strategy: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if strategy != SSECCopyStrategyDirect {
							 | 
						|
											t.Errorf("Expected direct copy strategy for same key, got %v", strategy)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Different key copy (decrypt-encrypt)", func(t *testing.T) {
							 | 
						|
										strategy, err := DetermineSSECCopyStrategy(sourceMetadata, sourceCustomerKey, destCustomerKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to determine copy strategy: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if strategy != SSECCopyStrategyDecryptEncrypt {
							 | 
						|
											t.Errorf("Expected decrypt-encrypt copy strategy for different keys, got %v", strategy)
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Can direct copy check", func(t *testing.T) {
							 | 
						|
										// Same key should allow direct copy
							 | 
						|
										canDirect := CanDirectCopySSEC(sourceMetadata, sourceCustomerKey, sourceCustomerKey)
							 | 
						|
										if !canDirect {
							 | 
						|
											t.Error("Should allow direct copy with same key")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Different key should not allow direct copy
							 | 
						|
										canDirect = CanDirectCopySSEC(sourceMetadata, sourceCustomerKey, destCustomerKey)
							 | 
						|
										if canDirect {
							 | 
						|
											t.Error("Should not allow direct copy with different keys")
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test actual copy operation (decrypt with source key, encrypt with dest key)
							 | 
						|
									t.Run("Full copy operation", func(t *testing.T) {
							 | 
						|
										// Decrypt with source key
							 | 
						|
										decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), sourceCustomerKey, iv)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create decrypted reader: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Re-encrypt with destination key
							 | 
						|
										reEncryptedReader, destIV, err := CreateSSECEncryptedReader(decryptedReader, destCustomerKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create re-encrypted reader: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										reEncryptedData, err := io.ReadAll(reEncryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read re-encrypted data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify we can decrypt with destination key
							 | 
						|
										finalDecryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(reEncryptedData), destCustomerKey, destIV)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create final decrypted reader: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										finalData, err := io.ReadAll(finalDecryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read final decrypted data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if string(finalData) != testData {
							 | 
						|
											t.Errorf("Expected %s, got %s", testData, string(finalData))
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSObjectCopy tests copying SSE-KMS encrypted objects
							 | 
						|
								func TestSSEKMSObjectCopy(t *testing.T) {
							 | 
						|
									kmsKey := SetupTestKMS(t)
							 | 
						|
									defer kmsKey.Cleanup()
							 | 
						|
								
							 | 
						|
									testData := "Hello, SSE-KMS copy world!"
							 | 
						|
									encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
							 | 
						|
								
							 | 
						|
									// Encrypt with SSE-KMS
							 | 
						|
									encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(strings.NewReader(testData), kmsKey.KeyID, encryptionContext)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									t.Run("Same KMS key copy", func(t *testing.T) {
							 | 
						|
										// Decrypt with original key
							 | 
						|
										decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create decrypted reader: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Re-encrypt with same KMS key
							 | 
						|
										reEncryptedReader, newSseKey, err := CreateSSEKMSEncryptedReader(decryptedReader, kmsKey.KeyID, encryptionContext)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create re-encrypted reader: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										reEncryptedData, err := io.ReadAll(reEncryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read re-encrypted data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Verify we can decrypt with new key
							 | 
						|
										finalDecryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(reEncryptedData), newSseKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create final decrypted reader: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										finalData, err := io.ReadAll(finalDecryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read final decrypted data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if string(finalData) != testData {
							 | 
						|
											t.Errorf("Expected %s, got %s", testData, string(finalData))
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSECToSSEKMSCopy tests cross-encryption copy (SSE-C to SSE-KMS)
							 | 
						|
								func TestSSECToSSEKMSCopy(t *testing.T) {
							 | 
						|
									// Setup SSE-C key
							 | 
						|
									ssecKey := GenerateTestSSECKey(1)
							 | 
						|
									ssecCustomerKey := &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       ssecKey.Key,
							 | 
						|
										KeyMD5:    ssecKey.KeyMD5,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Setup SSE-KMS
							 | 
						|
									kmsKey := SetupTestKMS(t)
							 | 
						|
									defer kmsKey.Cleanup()
							 | 
						|
								
							 | 
						|
									testData := "Hello, cross-encryption copy world!"
							 | 
						|
								
							 | 
						|
									// Encrypt with SSE-C
							 | 
						|
									encryptedReader, ssecIV, err := CreateSSECEncryptedReader(strings.NewReader(testData), ssecCustomerKey)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-C encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read SSE-C encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Decrypt SSE-C data
							 | 
						|
									decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), ssecCustomerKey, ssecIV)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-C decrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Re-encrypt with SSE-KMS
							 | 
						|
									encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
							 | 
						|
									reEncryptedReader, sseKmsKey, err := CreateSSEKMSEncryptedReader(decryptedReader, kmsKey.KeyID, encryptionContext)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-KMS encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									reEncryptedData, err := io.ReadAll(reEncryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read SSE-KMS encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Decrypt with SSE-KMS
							 | 
						|
									finalDecryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(reEncryptedData), sseKmsKey)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-KMS decrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									finalData, err := io.ReadAll(finalDecryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read final decrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if string(finalData) != testData {
							 | 
						|
										t.Errorf("Expected %s, got %s", testData, string(finalData))
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSToSSECCopy tests cross-encryption copy (SSE-KMS to SSE-C)
							 | 
						|
								func TestSSEKMSToSSECCopy(t *testing.T) {
							 | 
						|
									// Setup SSE-KMS
							 | 
						|
									kmsKey := SetupTestKMS(t)
							 | 
						|
									defer kmsKey.Cleanup()
							 | 
						|
								
							 | 
						|
									// Setup SSE-C key
							 | 
						|
									ssecKey := GenerateTestSSECKey(1)
							 | 
						|
									ssecCustomerKey := &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       ssecKey.Key,
							 | 
						|
										KeyMD5:    ssecKey.KeyMD5,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									testData := "Hello, reverse cross-encryption copy world!"
							 | 
						|
									encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
							 | 
						|
								
							 | 
						|
									// Encrypt with SSE-KMS
							 | 
						|
									encryptedReader, sseKmsKey, err := CreateSSEKMSEncryptedReader(strings.NewReader(testData), kmsKey.KeyID, encryptionContext)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-KMS encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read SSE-KMS encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Decrypt SSE-KMS data
							 | 
						|
									decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKmsKey)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-KMS decrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Re-encrypt with SSE-C
							 | 
						|
									reEncryptedReader, reEncryptedIV, err := CreateSSECEncryptedReader(decryptedReader, ssecCustomerKey)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-C encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									reEncryptedData, err := io.ReadAll(reEncryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read SSE-C encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Decrypt with SSE-C
							 | 
						|
									finalDecryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(reEncryptedData), ssecCustomerKey, reEncryptedIV)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create SSE-C decrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									finalData, err := io.ReadAll(finalDecryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read final decrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if string(finalData) != testData {
							 | 
						|
										t.Errorf("Expected %s, got %s", testData, string(finalData))
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSECopyWithCorruptedSource tests copy operations with corrupted source data
							 | 
						|
								func TestSSECopyWithCorruptedSource(t *testing.T) {
							 | 
						|
									ssecKey := GenerateTestSSECKey(1)
							 | 
						|
									ssecCustomerKey := &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       ssecKey.Key,
							 | 
						|
										KeyMD5:    ssecKey.KeyMD5,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									testData := "Hello, corruption test!"
							 | 
						|
								
							 | 
						|
									// Encrypt data
							 | 
						|
									encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(testData), ssecCustomerKey)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Corrupt the encrypted data
							 | 
						|
									corruptedData := make([]byte, len(encryptedData))
							 | 
						|
									copy(corruptedData, encryptedData)
							 | 
						|
									if len(corruptedData) > s3_constants.AESBlockSize {
							 | 
						|
										// Corrupt a byte after the IV
							 | 
						|
										corruptedData[s3_constants.AESBlockSize] ^= 0xFF
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Try to decrypt corrupted data
							 | 
						|
									decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(corruptedData), ssecCustomerKey, iv)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create decrypted reader for corrupted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									decryptedData, err := io.ReadAll(decryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										// This is okay - corrupted data might cause read errors
							 | 
						|
										t.Logf("Read error for corrupted data (expected): %v", err)
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// If we can read it, the data should be different from original
							 | 
						|
									if string(decryptedData) == testData {
							 | 
						|
										t.Error("Decrypted corrupted data should not match original")
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSCopyStrategy tests SSE-KMS copy strategy determination
							 | 
						|
								func TestSSEKMSCopyStrategy(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name             string
							 | 
						|
										srcMetadata      map[string][]byte
							 | 
						|
										destKeyID        string
							 | 
						|
										expectedStrategy SSEKMSCopyStrategy
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:             "Unencrypted to unencrypted",
							 | 
						|
											srcMetadata:      map[string][]byte{},
							 | 
						|
											destKeyID:        "",
							 | 
						|
											expectedStrategy: SSEKMSCopyStrategyDirect,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Same KMS key",
							 | 
						|
											srcMetadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											destKeyID:        "test-key-123",
							 | 
						|
											expectedStrategy: SSEKMSCopyStrategyDirect,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Different KMS keys",
							 | 
						|
											srcMetadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											destKeyID:        "test-key-456",
							 | 
						|
											expectedStrategy: SSEKMSCopyStrategyDecryptEncrypt,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Encrypted to unencrypted",
							 | 
						|
											srcMetadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											destKeyID:        "",
							 | 
						|
											expectedStrategy: SSEKMSCopyStrategyDecryptEncrypt,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:             "Unencrypted to encrypted",
							 | 
						|
											srcMetadata:      map[string][]byte{},
							 | 
						|
											destKeyID:        "test-key-123",
							 | 
						|
											expectedStrategy: SSEKMSCopyStrategyDecryptEncrypt,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											strategy, err := DetermineSSEKMSCopyStrategy(tt.srcMetadata, tt.destKeyID)
							 | 
						|
											if err != nil {
							 | 
						|
												t.Fatalf("DetermineSSEKMSCopyStrategy failed: %v", err)
							 | 
						|
											}
							 | 
						|
											if strategy != tt.expectedStrategy {
							 | 
						|
												t.Errorf("Expected strategy %v, got %v", tt.expectedStrategy, strategy)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSCopyHeaders tests SSE-KMS copy header parsing
							 | 
						|
								func TestSSEKMSCopyHeaders(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name              string
							 | 
						|
										headers           map[string]string
							 | 
						|
										expectedKeyID     string
							 | 
						|
										expectedContext   map[string]string
							 | 
						|
										expectedBucketKey bool
							 | 
						|
										expectError       bool
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:              "No SSE-KMS headers",
							 | 
						|
											headers:           map[string]string{},
							 | 
						|
											expectedKeyID:     "",
							 | 
						|
											expectedContext:   nil,
							 | 
						|
											expectedBucketKey: false,
							 | 
						|
											expectError:       false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "SSE-KMS with key ID",
							 | 
						|
											headers: map[string]string{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            "aws:kms",
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: "test-key-123",
							 | 
						|
											},
							 | 
						|
											expectedKeyID:     "test-key-123",
							 | 
						|
											expectedContext:   nil,
							 | 
						|
											expectedBucketKey: false,
							 | 
						|
											expectError:       false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "SSE-KMS with all options",
							 | 
						|
											headers: map[string]string{
							 | 
						|
												s3_constants.AmzServerSideEncryption:                 "aws:kms",
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId:      "test-key-123",
							 | 
						|
												s3_constants.AmzServerSideEncryptionContext:          "eyJ0ZXN0IjoidmFsdWUifQ==", // base64 of {"test":"value"}
							 | 
						|
												s3_constants.AmzServerSideEncryptionBucketKeyEnabled: "true",
							 | 
						|
											},
							 | 
						|
											expectedKeyID:     "test-key-123",
							 | 
						|
											expectedContext:   map[string]string{"test": "value"},
							 | 
						|
											expectedBucketKey: true,
							 | 
						|
											expectError:       false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Invalid key ID",
							 | 
						|
											headers: map[string]string{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            "aws:kms",
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: "invalid key id",
							 | 
						|
											},
							 | 
						|
											expectError: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Invalid encryption context",
							 | 
						|
											headers: map[string]string{
							 | 
						|
												s3_constants.AmzServerSideEncryption:        "aws:kms",
							 | 
						|
												s3_constants.AmzServerSideEncryptionContext: "invalid-base64!",
							 | 
						|
											},
							 | 
						|
											expectError: true,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											req, _ := http.NewRequest("PUT", "/test", nil)
							 | 
						|
											for k, v := range tt.headers {
							 | 
						|
												req.Header.Set(k, v)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											keyID, context, bucketKey, err := ParseSSEKMSCopyHeaders(req)
							 | 
						|
								
							 | 
						|
											if tt.expectError {
							 | 
						|
												if err == nil {
							 | 
						|
													t.Error("Expected error but got none")
							 | 
						|
												}
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if err != nil {
							 | 
						|
												t.Fatalf("Unexpected error: %v", err)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if keyID != tt.expectedKeyID {
							 | 
						|
												t.Errorf("Expected keyID %s, got %s", tt.expectedKeyID, keyID)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if !mapsEqual(context, tt.expectedContext) {
							 | 
						|
												t.Errorf("Expected context %v, got %v", tt.expectedContext, context)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if bucketKey != tt.expectedBucketKey {
							 | 
						|
												t.Errorf("Expected bucketKey %v, got %v", tt.expectedBucketKey, bucketKey)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSDirectCopy tests direct copy scenarios
							 | 
						|
								func TestSSEKMSDirectCopy(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name        string
							 | 
						|
										srcMetadata map[string][]byte
							 | 
						|
										destKeyID   string
							 | 
						|
										canDirect   bool
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:        "Both unencrypted",
							 | 
						|
											srcMetadata: map[string][]byte{},
							 | 
						|
											destKeyID:   "",
							 | 
						|
											canDirect:   true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Same key ID",
							 | 
						|
											srcMetadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											destKeyID: "test-key-123",
							 | 
						|
											canDirect: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Different key IDs",
							 | 
						|
											srcMetadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											destKeyID: "test-key-456",
							 | 
						|
											canDirect: false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Source encrypted, dest unencrypted",
							 | 
						|
											srcMetadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											destKeyID: "",
							 | 
						|
											canDirect: false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Source unencrypted, dest encrypted",
							 | 
						|
											srcMetadata: map[string][]byte{},
							 | 
						|
											destKeyID:   "test-key-123",
							 | 
						|
											canDirect:   false,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											canDirect := CanDirectCopySSEKMS(tt.srcMetadata, tt.destKeyID)
							 | 
						|
											if canDirect != tt.canDirect {
							 | 
						|
												t.Errorf("Expected canDirect %v, got %v", tt.canDirect, canDirect)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestGetSourceSSEKMSInfo tests extraction of SSE-KMS info from metadata
							 | 
						|
								func TestGetSourceSSEKMSInfo(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name              string
							 | 
						|
										metadata          map[string][]byte
							 | 
						|
										expectedKeyID     string
							 | 
						|
										expectedEncrypted bool
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:              "No encryption",
							 | 
						|
											metadata:          map[string][]byte{},
							 | 
						|
											expectedKeyID:     "",
							 | 
						|
											expectedEncrypted: false,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "SSE-KMS with key ID",
							 | 
						|
											metadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption:            []byte("aws:kms"),
							 | 
						|
												s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-123"),
							 | 
						|
											},
							 | 
						|
											expectedKeyID:     "test-key-123",
							 | 
						|
											expectedEncrypted: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "SSE-KMS without key ID (default key)",
							 | 
						|
											metadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption: []byte("aws:kms"),
							 | 
						|
											},
							 | 
						|
											expectedKeyID:     "",
							 | 
						|
											expectedEncrypted: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Non-KMS encryption",
							 | 
						|
											metadata: map[string][]byte{
							 | 
						|
												s3_constants.AmzServerSideEncryption: []byte("AES256"),
							 | 
						|
											},
							 | 
						|
											expectedKeyID:     "",
							 | 
						|
											expectedEncrypted: false,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											keyID, encrypted := GetSourceSSEKMSInfo(tt.metadata)
							 | 
						|
											if keyID != tt.expectedKeyID {
							 | 
						|
												t.Errorf("Expected keyID %s, got %s", tt.expectedKeyID, keyID)
							 | 
						|
											}
							 | 
						|
											if encrypted != tt.expectedEncrypted {
							 | 
						|
												t.Errorf("Expected encrypted %v, got %v", tt.expectedEncrypted, encrypted)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Helper function to compare maps
							 | 
						|
								func mapsEqual(a, b map[string]string) bool {
							 | 
						|
									if len(a) != len(b) {
							 | 
						|
										return false
							 | 
						|
									}
							 | 
						|
									for k, v := range a {
							 | 
						|
										if b[k] != v {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return true
							 | 
						|
								}
							 |