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