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