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

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