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.
		
		
		
		
		
			
		
			
				
					
					
						
							400 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							400 lines
						
					
					
						
							12 KiB
						
					
					
				
								package s3api
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"bytes"
							 | 
						|
									"fmt"
							 | 
						|
									"io"
							 | 
						|
									"net/http"
							 | 
						|
									"strings"
							 | 
						|
									"testing"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestSSECWrongKeyDecryption tests decryption with wrong SSE-C key
							 | 
						|
								func TestSSECWrongKeyDecryption(t *testing.T) {
							 | 
						|
									// Setup original key and encrypt data
							 | 
						|
									originalKey := GenerateTestSSECKey(1)
							 | 
						|
									testData := "Hello, SSE-C world!"
							 | 
						|
								
							 | 
						|
									encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(testData), &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       originalKey.Key,
							 | 
						|
										KeyMD5:    originalKey.KeyMD5,
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create encrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Read encrypted data
							 | 
						|
									encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read encrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Try to decrypt with wrong key
							 | 
						|
									wrongKey := GenerateTestSSECKey(2) // Different seed = different key
							 | 
						|
									decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       wrongKey.Key,
							 | 
						|
										KeyMD5:    wrongKey.KeyMD5,
							 | 
						|
									}, iv)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to create decrypted reader: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Read decrypted data - should be garbage/different from original
							 | 
						|
									decryptedData, err := io.ReadAll(decryptedReader)
							 | 
						|
									if err != nil {
							 | 
						|
										t.Fatalf("Failed to read decrypted data: %v", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Verify the decrypted data is NOT the same as original (wrong key used)
							 | 
						|
									if string(decryptedData) == testData {
							 | 
						|
										t.Error("Decryption with wrong key should not produce original data")
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSKeyNotFound tests handling of missing KMS key
							 | 
						|
								func TestSSEKMSKeyNotFound(t *testing.T) {
							 | 
						|
									// Note: The local KMS provider creates keys on-demand by design.
							 | 
						|
									// This test validates that when on-demand creation fails or is disabled,
							 | 
						|
									// appropriate errors are returned.
							 | 
						|
								
							 | 
						|
									// Test with an invalid key ID that would fail even on-demand creation
							 | 
						|
									invalidKeyID := "" // Empty key ID should fail
							 | 
						|
									encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
							 | 
						|
								
							 | 
						|
									_, _, err := CreateSSEKMSEncryptedReader(strings.NewReader("test data"), invalidKeyID, encryptionContext)
							 | 
						|
								
							 | 
						|
									// Should get an error for invalid/empty key
							 | 
						|
									if err == nil {
							 | 
						|
										t.Error("Expected error for empty KMS key ID, got none")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// For local KMS with on-demand creation, we test what we can realistically test
							 | 
						|
									if err != nil {
							 | 
						|
										t.Logf("Got expected error for empty key ID: %v", err)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEHeadersWithoutEncryption tests inconsistent state where headers are present but no encryption
							 | 
						|
								func TestSSEHeadersWithoutEncryption(t *testing.T) {
							 | 
						|
									testCases := []struct {
							 | 
						|
										name     string
							 | 
						|
										setupReq func() *http.Request
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name: "SSE-C algorithm without key",
							 | 
						|
											setupReq: func() *http.Request {
							 | 
						|
												req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, "AES256")
							 | 
						|
												// Missing key and MD5
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "SSE-C key without algorithm",
							 | 
						|
											setupReq: func() *http.Request {
							 | 
						|
												req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
												keyPair := GenerateTestSSECKey(1)
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerKey, keyPair.KeyB64)
							 | 
						|
												// Missing algorithm
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "SSE-KMS key ID without algorithm",
							 | 
						|
											setupReq: func() *http.Request {
							 | 
						|
												req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryptionAwsKmsKeyId, "test-key-id")
							 | 
						|
												// Missing algorithm
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tc := range testCases {
							 | 
						|
										t.Run(tc.name, func(t *testing.T) {
							 | 
						|
											req := tc.setupReq()
							 | 
						|
								
							 | 
						|
											// Validate headers - should catch incomplete configurations
							 | 
						|
											if strings.Contains(tc.name, "SSE-C") {
							 | 
						|
												err := ValidateSSECHeaders(req)
							 | 
						|
												if err == nil {
							 | 
						|
													t.Error("Expected validation error for incomplete SSE-C headers")
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSECInvalidKeyFormats tests various invalid SSE-C key formats
							 | 
						|
								func TestSSECInvalidKeyFormats(t *testing.T) {
							 | 
						|
									testCases := []struct {
							 | 
						|
										name      string
							 | 
						|
										algorithm string
							 | 
						|
										key       string
							 | 
						|
										keyMD5    string
							 | 
						|
										expectErr bool
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:      "Invalid algorithm",
							 | 
						|
											algorithm: "AES128",
							 | 
						|
											key:       "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3RrZXk=", // 32 bytes base64
							 | 
						|
											keyMD5:    "valid-md5-hash",
							 | 
						|
											expectErr: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "Invalid key length (too short)",
							 | 
						|
											algorithm: "AES256",
							 | 
						|
											key:       "c2hvcnRrZXk=", // "shortkey" base64 - too short
							 | 
						|
											keyMD5:    "valid-md5-hash",
							 | 
						|
											expectErr: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "Invalid key length (too long)",
							 | 
						|
											algorithm: "AES256",
							 | 
						|
											key:       "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleQ==", // too long
							 | 
						|
											keyMD5:    "valid-md5-hash",
							 | 
						|
											expectErr: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "Invalid base64 key",
							 | 
						|
											algorithm: "AES256",
							 | 
						|
											key:       "invalid-base64!",
							 | 
						|
											keyMD5:    "valid-md5-hash",
							 | 
						|
											expectErr: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "Invalid base64 MD5",
							 | 
						|
											algorithm: "AES256",
							 | 
						|
											key:       "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3RrZXk=",
							 | 
						|
											keyMD5:    "invalid-base64!",
							 | 
						|
											expectErr: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "Mismatched MD5",
							 | 
						|
											algorithm: "AES256",
							 | 
						|
											key:       "dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3RrZXk=",
							 | 
						|
											keyMD5:    "d29uZy1tZDUtaGFzaA==", // "wrong-md5-hash" base64
							 | 
						|
											expectErr: true,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tc := range testCases {
							 | 
						|
										t.Run(tc.name, func(t *testing.T) {
							 | 
						|
											req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
											req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, tc.algorithm)
							 | 
						|
											req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerKey, tc.key)
							 | 
						|
											req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerKeyMD5, tc.keyMD5)
							 | 
						|
								
							 | 
						|
											err := ValidateSSECHeaders(req)
							 | 
						|
											if tc.expectErr && err == nil {
							 | 
						|
												t.Errorf("Expected error for %s, but got none", tc.name)
							 | 
						|
											}
							 | 
						|
											if !tc.expectErr && err != nil {
							 | 
						|
												t.Errorf("Expected no error for %s, but got: %v", tc.name, err)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEKMSInvalidConfigurations tests various invalid SSE-KMS configurations
							 | 
						|
								func TestSSEKMSInvalidConfigurations(t *testing.T) {
							 | 
						|
									testCases := []struct {
							 | 
						|
										name         string
							 | 
						|
										setupRequest func() *http.Request
							 | 
						|
										expectError  bool
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name: "Invalid algorithm",
							 | 
						|
											setupRequest: func() *http.Request {
							 | 
						|
												req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryption, "invalid-algorithm")
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
											expectError: true,
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Empty key ID",
							 | 
						|
											setupRequest: func() *http.Request {
							 | 
						|
												req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryption, "aws:kms")
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryptionAwsKmsKeyId, "")
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
											expectError: false, // Empty key ID might be valid (use default)
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "Invalid key ID format",
							 | 
						|
											setupRequest: func() *http.Request {
							 | 
						|
												req := CreateTestHTTPRequest("PUT", "/bucket/object", nil)
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryption, "aws:kms")
							 | 
						|
												req.Header.Set(s3_constants.AmzServerSideEncryptionAwsKmsKeyId, "invalid key id with spaces")
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
											expectError: true,
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tc := range testCases {
							 | 
						|
										t.Run(tc.name, func(t *testing.T) {
							 | 
						|
											req := tc.setupRequest()
							 | 
						|
								
							 | 
						|
											_, err := ParseSSEKMSHeaders(req)
							 | 
						|
											if tc.expectError && err == nil {
							 | 
						|
												t.Errorf("Expected error for %s, but got none", tc.name)
							 | 
						|
											}
							 | 
						|
											if !tc.expectError && err != nil {
							 | 
						|
												t.Errorf("Expected no error for %s, but got: %v", tc.name, err)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEEmptyDataHandling tests handling of empty data with SSE
							 | 
						|
								func TestSSEEmptyDataHandling(t *testing.T) {
							 | 
						|
									t.Run("SSE-C with empty data", func(t *testing.T) {
							 | 
						|
										keyPair := GenerateTestSSECKey(1)
							 | 
						|
										customerKey := &SSECustomerKey{
							 | 
						|
											Algorithm: "AES256",
							 | 
						|
											Key:       keyPair.Key,
							 | 
						|
											KeyMD5:    keyPair.KeyMD5,
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Encrypt empty data
							 | 
						|
										encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(""), customerKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create encrypted reader for empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read encrypted empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Should have IV for empty data
							 | 
						|
										if len(iv) != s3_constants.AESBlockSize {
							 | 
						|
											t.Error("IV should be present even for empty data")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Decrypt and verify
							 | 
						|
										decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), customerKey, iv)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create decrypted reader for empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										decryptedData, err := io.ReadAll(decryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read decrypted empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if len(decryptedData) != 0 {
							 | 
						|
											t.Errorf("Expected empty decrypted data, got %d bytes", len(decryptedData))
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("SSE-KMS with empty data", func(t *testing.T) {
							 | 
						|
										kmsKey := SetupTestKMS(t)
							 | 
						|
										defer kmsKey.Cleanup()
							 | 
						|
								
							 | 
						|
										encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
							 | 
						|
								
							 | 
						|
										// Encrypt empty data
							 | 
						|
										encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(strings.NewReader(""), kmsKey.KeyID, encryptionContext)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create encrypted reader for empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read encrypted empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Empty data should produce empty encrypted data (IV is stored in metadata)
							 | 
						|
										if len(encryptedData) != 0 {
							 | 
						|
											t.Errorf("Encrypted empty data should be empty, got %d bytes", len(encryptedData))
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Decrypt and verify
							 | 
						|
										decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKey)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to create decrypted reader for empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										decryptedData, err := io.ReadAll(decryptedReader)
							 | 
						|
										if err != nil {
							 | 
						|
											t.Fatalf("Failed to read decrypted empty data: %v", err)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if len(decryptedData) != 0 {
							 | 
						|
											t.Errorf("Expected empty decrypted data, got %d bytes", len(decryptedData))
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSSEConcurrentAccess tests SSE operations under concurrent access
							 | 
						|
								func TestSSEConcurrentAccess(t *testing.T) {
							 | 
						|
									keyPair := GenerateTestSSECKey(1)
							 | 
						|
									customerKey := &SSECustomerKey{
							 | 
						|
										Algorithm: "AES256",
							 | 
						|
										Key:       keyPair.Key,
							 | 
						|
										KeyMD5:    keyPair.KeyMD5,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									const numGoroutines = 10
							 | 
						|
									done := make(chan bool, numGoroutines)
							 | 
						|
									errors := make(chan error, numGoroutines)
							 | 
						|
								
							 | 
						|
									// Run multiple encryption/decryption operations concurrently
							 | 
						|
									for i := 0; i < numGoroutines; i++ {
							 | 
						|
										go func(id int) {
							 | 
						|
											defer func() { done <- true }()
							 | 
						|
								
							 | 
						|
											testData := fmt.Sprintf("test data %d", id)
							 | 
						|
								
							 | 
						|
											// Encrypt
							 | 
						|
											encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(testData), customerKey)
							 | 
						|
											if err != nil {
							 | 
						|
												errors <- fmt.Errorf("goroutine %d encrypt error: %v", id, err)
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											encryptedData, err := io.ReadAll(encryptedReader)
							 | 
						|
											if err != nil {
							 | 
						|
												errors <- fmt.Errorf("goroutine %d read encrypted error: %v", id, err)
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											// Decrypt
							 | 
						|
											decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), customerKey, iv)
							 | 
						|
											if err != nil {
							 | 
						|
												errors <- fmt.Errorf("goroutine %d decrypt error: %v", id, err)
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											decryptedData, err := io.ReadAll(decryptedReader)
							 | 
						|
											if err != nil {
							 | 
						|
												errors <- fmt.Errorf("goroutine %d read decrypted error: %v", id, err)
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											if string(decryptedData) != testData {
							 | 
						|
												errors <- fmt.Errorf("goroutine %d data mismatch: expected %s, got %s", id, testData, string(decryptedData))
							 | 
						|
												return
							 | 
						|
											}
							 | 
						|
										}(i)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Wait for all goroutines to complete
							 | 
						|
									for i := 0; i < numGoroutines; i++ {
							 | 
						|
										<-done
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for errors
							 | 
						|
									close(errors)
							 | 
						|
									for err := range errors {
							 | 
						|
										t.Error(err)
							 | 
						|
									}
							 | 
						|
								}
							 |