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 | |
| }
 |