aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3_sse_error_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3_sse_error_test.go')
-rw-r--r--weed/s3api/s3_sse_error_test.go400
1 files changed, 400 insertions, 0 deletions
diff --git a/weed/s3api/s3_sse_error_test.go b/weed/s3api/s3_sse_error_test.go
new file mode 100644
index 000000000..4b062faa6
--- /dev/null
+++ b/weed/s3api/s3_sse_error_test.go
@@ -0,0 +1,400 @@
+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) != 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)
+ }
+}