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.
984 lines
25 KiB
984 lines
25 KiB
package s3api
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
)
|
|
|
|
// TestSSES3EncryptionDecryption tests basic SSE-S3 encryption and decryption
|
|
func TestSSES3EncryptionDecryption(t *testing.T) {
|
|
// Generate SSE-S3 key
|
|
sseS3Key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key: %v", err)
|
|
}
|
|
|
|
// Test data
|
|
testData := []byte("Hello, World! This is a test of SSE-S3 encryption.")
|
|
|
|
// Create encrypted reader
|
|
dataReader := bytes.NewReader(testData)
|
|
encryptedReader, iv, err := CreateSSES3EncryptedReader(dataReader, sseS3Key)
|
|
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)
|
|
}
|
|
|
|
// Verify data is actually encrypted (different from original)
|
|
if bytes.Equal(encryptedData, testData) {
|
|
t.Error("Data doesn't appear to be encrypted")
|
|
}
|
|
|
|
// Create decrypted reader
|
|
encryptedReader2 := bytes.NewReader(encryptedData)
|
|
decryptedReader, err := CreateSSES3DecryptedReader(encryptedReader2, sseS3Key, iv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader: %v", err)
|
|
}
|
|
|
|
// Read decrypted data
|
|
decryptedData, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data: %v", err)
|
|
}
|
|
|
|
// Verify decrypted data matches original
|
|
if !bytes.Equal(decryptedData, testData) {
|
|
t.Errorf("Decrypted data doesn't match original.\nOriginal: %s\nDecrypted: %s", testData, decryptedData)
|
|
}
|
|
}
|
|
|
|
// TestSSES3IsRequestInternal tests detection of SSE-S3 requests
|
|
func TestSSES3IsRequestInternal(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
headers map[string]string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Valid SSE-S3 request",
|
|
headers: map[string]string{
|
|
s3_constants.AmzServerSideEncryption: "AES256",
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "No SSE headers",
|
|
headers: map[string]string{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "SSE-KMS request",
|
|
headers: map[string]string{
|
|
s3_constants.AmzServerSideEncryption: "aws:kms",
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "SSE-C request",
|
|
headers: map[string]string{
|
|
s3_constants.AmzServerSideEncryptionCustomerAlgorithm: "AES256",
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := &http.Request{Header: make(http.Header)}
|
|
for k, v := range tc.headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
result := IsSSES3RequestInternal(req)
|
|
if result != tc.expected {
|
|
t.Errorf("Expected %v, got %v", tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSSES3MetadataSerialization tests SSE-S3 metadata serialization and deserialization
|
|
func TestSSES3MetadataSerialization(t *testing.T) {
|
|
// Initialize global key manager
|
|
globalSSES3KeyManager = NewSSES3KeyManager()
|
|
defer func() {
|
|
globalSSES3KeyManager = NewSSES3KeyManager()
|
|
}()
|
|
|
|
// Set up the key manager with a super key for testing
|
|
keyManager := GetSSES3KeyManager()
|
|
keyManager.superKey = make([]byte, 32)
|
|
for i := range keyManager.superKey {
|
|
keyManager.superKey[i] = byte(i)
|
|
}
|
|
|
|
// Generate SSE-S3 key
|
|
sseS3Key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key: %v", err)
|
|
}
|
|
|
|
// Add IV to the key
|
|
sseS3Key.IV = make([]byte, 16)
|
|
for i := range sseS3Key.IV {
|
|
sseS3Key.IV[i] = byte(i * 2)
|
|
}
|
|
|
|
// Serialize metadata
|
|
serialized, err := SerializeSSES3Metadata(sseS3Key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize SSE-S3 metadata: %v", err)
|
|
}
|
|
|
|
if len(serialized) == 0 {
|
|
t.Error("Serialized metadata is empty")
|
|
}
|
|
|
|
// Deserialize metadata
|
|
deserializedKey, err := DeserializeSSES3Metadata(serialized, keyManager)
|
|
if err != nil {
|
|
t.Fatalf("Failed to deserialize SSE-S3 metadata: %v", err)
|
|
}
|
|
|
|
// Verify key matches
|
|
if !bytes.Equal(deserializedKey.Key, sseS3Key.Key) {
|
|
t.Error("Deserialized key doesn't match original key")
|
|
}
|
|
|
|
// Verify IV matches
|
|
if !bytes.Equal(deserializedKey.IV, sseS3Key.IV) {
|
|
t.Error("Deserialized IV doesn't match original IV")
|
|
}
|
|
|
|
// Verify algorithm matches
|
|
if deserializedKey.Algorithm != sseS3Key.Algorithm {
|
|
t.Errorf("Algorithm mismatch: expected %s, got %s", sseS3Key.Algorithm, deserializedKey.Algorithm)
|
|
}
|
|
|
|
// Verify key ID matches
|
|
if deserializedKey.KeyID != sseS3Key.KeyID {
|
|
t.Errorf("Key ID mismatch: expected %s, got %s", sseS3Key.KeyID, deserializedKey.KeyID)
|
|
}
|
|
}
|
|
|
|
// TestDetectPrimarySSETypeS3 tests detection of SSE-S3 as primary encryption type
|
|
func TestDetectPrimarySSETypeS3(t *testing.T) {
|
|
s3a := &S3ApiServer{}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
entry *filer_pb.Entry
|
|
expected string
|
|
}{
|
|
{
|
|
name: "Single SSE-S3 chunk",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{
|
|
FileId: "1,123",
|
|
Offset: 0,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_S3,
|
|
SseMetadata: []byte("metadata"),
|
|
},
|
|
},
|
|
},
|
|
expected: s3_constants.SSETypeS3,
|
|
},
|
|
{
|
|
name: "Multiple SSE-S3 chunks",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{
|
|
FileId: "1,123",
|
|
Offset: 0,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_S3,
|
|
SseMetadata: []byte("metadata1"),
|
|
},
|
|
{
|
|
FileId: "2,456",
|
|
Offset: 1024,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_S3,
|
|
SseMetadata: []byte("metadata2"),
|
|
},
|
|
},
|
|
},
|
|
expected: s3_constants.SSETypeS3,
|
|
},
|
|
{
|
|
name: "Mixed SSE-S3 and SSE-KMS chunks (SSE-S3 majority)",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{
|
|
FileId: "1,123",
|
|
Offset: 0,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_S3,
|
|
SseMetadata: []byte("metadata1"),
|
|
},
|
|
{
|
|
FileId: "2,456",
|
|
Offset: 1024,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_S3,
|
|
SseMetadata: []byte("metadata2"),
|
|
},
|
|
{
|
|
FileId: "3,789",
|
|
Offset: 2048,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_KMS,
|
|
SseMetadata: []byte("metadata3"),
|
|
},
|
|
},
|
|
},
|
|
expected: s3_constants.SSETypeS3,
|
|
},
|
|
{
|
|
name: "No chunks, SSE-S3 metadata without KMS key ID",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{},
|
|
},
|
|
expected: s3_constants.SSETypeS3,
|
|
},
|
|
{
|
|
name: "No chunks, SSE-KMS metadata with KMS key ID",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
s3_constants.AmzServerSideEncryptionAwsKmsKeyId: []byte("test-key-id"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{},
|
|
},
|
|
expected: s3_constants.SSETypeKMS,
|
|
},
|
|
{
|
|
name: "SSE-C chunks",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryptionCustomerAlgorithm: []byte("AES256"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{
|
|
FileId: "1,123",
|
|
Offset: 0,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_C,
|
|
SseMetadata: []byte("metadata"),
|
|
},
|
|
},
|
|
},
|
|
expected: s3_constants.SSETypeC,
|
|
},
|
|
{
|
|
name: "Unencrypted",
|
|
entry: &filer_pb.Entry{
|
|
Extended: map[string][]byte{},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{
|
|
FileId: "1,123",
|
|
Offset: 0,
|
|
Size: 1024,
|
|
},
|
|
},
|
|
},
|
|
expected: "None",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := s3a.detectPrimarySSEType(tc.entry)
|
|
if result != tc.expected {
|
|
t.Errorf("Expected %s, got %s", tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAddSSES3HeadersToResponse tests that SSE-S3 headers are added to responses
|
|
func TestAddSSES3HeadersToResponse(t *testing.T) {
|
|
s3a := &S3ApiServer{}
|
|
|
|
entry := &filer_pb.Entry{
|
|
Extended: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
},
|
|
Attributes: &filer_pb.FuseAttributes{},
|
|
Chunks: []*filer_pb.FileChunk{
|
|
{
|
|
FileId: "1,123",
|
|
Offset: 0,
|
|
Size: 1024,
|
|
SseType: filer_pb.SSEType_SSE_S3,
|
|
SseMetadata: []byte("metadata"),
|
|
},
|
|
},
|
|
}
|
|
|
|
proxyResponse := &http.Response{
|
|
Header: make(http.Header),
|
|
}
|
|
|
|
s3a.addSSEHeadersToResponse(proxyResponse, entry)
|
|
|
|
algorithm := proxyResponse.Header.Get(s3_constants.AmzServerSideEncryption)
|
|
if algorithm != "AES256" {
|
|
t.Errorf("Expected SSE algorithm AES256, got %s", algorithm)
|
|
}
|
|
|
|
// Should NOT have SSE-C or SSE-KMS specific headers
|
|
if proxyResponse.Header.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != "" {
|
|
t.Error("Should not have SSE-C customer algorithm header")
|
|
}
|
|
|
|
if proxyResponse.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId) != "" {
|
|
t.Error("Should not have SSE-KMS key ID header")
|
|
}
|
|
}
|
|
|
|
// TestSSES3EncryptionWithBaseIV tests multipart encryption with base IV
|
|
func TestSSES3EncryptionWithBaseIV(t *testing.T) {
|
|
// Generate SSE-S3 key
|
|
sseS3Key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key: %v", err)
|
|
}
|
|
|
|
// Generate base IV
|
|
baseIV := make([]byte, 16)
|
|
for i := range baseIV {
|
|
baseIV[i] = byte(i)
|
|
}
|
|
|
|
// Test data for two parts
|
|
testData1 := []byte("Part 1 of multipart upload test.")
|
|
testData2 := []byte("Part 2 of multipart upload test.")
|
|
|
|
// Encrypt part 1 at offset 0
|
|
dataReader1 := bytes.NewReader(testData1)
|
|
encryptedReader1, iv1, err := CreateSSES3EncryptedReaderWithBaseIV(dataReader1, sseS3Key, baseIV, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for part 1: %v", err)
|
|
}
|
|
|
|
encryptedData1, err := io.ReadAll(encryptedReader1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted data for part 1: %v", err)
|
|
}
|
|
|
|
// Encrypt part 2 at offset (simulating second part)
|
|
dataReader2 := bytes.NewReader(testData2)
|
|
offset2 := int64(len(testData1))
|
|
encryptedReader2, iv2, err := CreateSSES3EncryptedReaderWithBaseIV(dataReader2, sseS3Key, baseIV, offset2)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for part 2: %v", err)
|
|
}
|
|
|
|
encryptedData2, err := io.ReadAll(encryptedReader2)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted data for part 2: %v", err)
|
|
}
|
|
|
|
// IVs should be different (offset-based)
|
|
if bytes.Equal(iv1, iv2) {
|
|
t.Error("IVs should be different for different offsets")
|
|
}
|
|
|
|
// Decrypt part 1
|
|
decryptedReader1, err := CreateSSES3DecryptedReader(bytes.NewReader(encryptedData1), sseS3Key, iv1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for part 1: %v", err)
|
|
}
|
|
|
|
decryptedData1, err := io.ReadAll(decryptedReader1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data for part 1: %v", err)
|
|
}
|
|
|
|
// Decrypt part 2
|
|
decryptedReader2, err := CreateSSES3DecryptedReader(bytes.NewReader(encryptedData2), sseS3Key, iv2)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for part 2: %v", err)
|
|
}
|
|
|
|
decryptedData2, err := io.ReadAll(decryptedReader2)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data for part 2: %v", err)
|
|
}
|
|
|
|
// Verify decrypted data matches original
|
|
if !bytes.Equal(decryptedData1, testData1) {
|
|
t.Errorf("Decrypted part 1 doesn't match original.\nOriginal: %s\nDecrypted: %s", testData1, decryptedData1)
|
|
}
|
|
|
|
if !bytes.Equal(decryptedData2, testData2) {
|
|
t.Errorf("Decrypted part 2 doesn't match original.\nOriginal: %s\nDecrypted: %s", testData2, decryptedData2)
|
|
}
|
|
}
|
|
|
|
// TestSSES3WrongKeyDecryption tests that wrong key fails decryption
|
|
func TestSSES3WrongKeyDecryption(t *testing.T) {
|
|
// Generate two different keys
|
|
sseS3Key1, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key 1: %v", err)
|
|
}
|
|
|
|
sseS3Key2, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key 2: %v", err)
|
|
}
|
|
|
|
// Test data
|
|
testData := []byte("Secret data encrypted with key 1")
|
|
|
|
// Encrypt with key 1
|
|
dataReader := bytes.NewReader(testData)
|
|
encryptedReader, iv, err := CreateSSES3EncryptedReader(dataReader, sseS3Key1)
|
|
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)
|
|
}
|
|
|
|
// Try to decrypt with key 2 (wrong key)
|
|
decryptedReader, err := CreateSSES3DecryptedReader(bytes.NewReader(encryptedData), sseS3Key2, iv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader: %v", err)
|
|
}
|
|
|
|
decryptedData, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data: %v", err)
|
|
}
|
|
|
|
// Decrypted data should NOT match original (wrong key produces garbage)
|
|
if bytes.Equal(decryptedData, testData) {
|
|
t.Error("Decryption with wrong key should not produce correct plaintext")
|
|
}
|
|
}
|
|
|
|
// TestSSES3KeyGeneration tests SSE-S3 key generation
|
|
func TestSSES3KeyGeneration(t *testing.T) {
|
|
// Generate multiple keys
|
|
keys := make([]*SSES3Key, 10)
|
|
for i := range keys {
|
|
key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key %d: %v", i, err)
|
|
}
|
|
keys[i] = key
|
|
|
|
// Verify key properties
|
|
if len(key.Key) != SSES3KeySize {
|
|
t.Errorf("Key %d has wrong size: expected %d, got %d", i, SSES3KeySize, len(key.Key))
|
|
}
|
|
|
|
if key.Algorithm != SSES3Algorithm {
|
|
t.Errorf("Key %d has wrong algorithm: expected %s, got %s", i, SSES3Algorithm, key.Algorithm)
|
|
}
|
|
|
|
if key.KeyID == "" {
|
|
t.Errorf("Key %d has empty key ID", i)
|
|
}
|
|
}
|
|
|
|
// Verify keys are unique
|
|
for i := 0; i < len(keys); i++ {
|
|
for j := i + 1; j < len(keys); j++ {
|
|
if bytes.Equal(keys[i].Key, keys[j].Key) {
|
|
t.Errorf("Keys %d and %d are identical (should be unique)", i, j)
|
|
}
|
|
if keys[i].KeyID == keys[j].KeyID {
|
|
t.Errorf("Key IDs %d and %d are identical (should be unique)", i, j)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSSES3VariousSizes tests SSE-S3 encryption/decryption with various data sizes
|
|
func TestSSES3VariousSizes(t *testing.T) {
|
|
sizes := []int{1, 15, 16, 17, 100, 1024, 4096, 1048576}
|
|
|
|
for _, size := range sizes {
|
|
t.Run(fmt.Sprintf("size_%d", size), func(t *testing.T) {
|
|
// Generate test data
|
|
testData := make([]byte, size)
|
|
for i := range testData {
|
|
testData[i] = byte(i % 256)
|
|
}
|
|
|
|
// Generate key
|
|
sseS3Key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key: %v", err)
|
|
}
|
|
|
|
// Encrypt
|
|
dataReader := bytes.NewReader(testData)
|
|
encryptedReader, iv, err := CreateSSES3EncryptedReader(dataReader, sseS3Key)
|
|
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)
|
|
}
|
|
|
|
// Verify encrypted size matches original
|
|
if len(encryptedData) != size {
|
|
t.Errorf("Encrypted size mismatch: expected %d, got %d", size, len(encryptedData))
|
|
}
|
|
|
|
// Decrypt
|
|
decryptedReader, err := CreateSSES3DecryptedReader(bytes.NewReader(encryptedData), sseS3Key, iv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader: %v", err)
|
|
}
|
|
|
|
decryptedData, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data: %v", err)
|
|
}
|
|
|
|
// Verify
|
|
if !bytes.Equal(decryptedData, testData) {
|
|
t.Errorf("Decrypted data doesn't match original for size %d", size)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSSES3ResponseHeaders tests that SSE-S3 response headers are set correctly
|
|
func TestSSES3ResponseHeaders(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
// Simulate setting SSE-S3 response headers
|
|
w.Header().Set(s3_constants.AmzServerSideEncryption, SSES3Algorithm)
|
|
|
|
// Verify headers
|
|
algorithm := w.Header().Get(s3_constants.AmzServerSideEncryption)
|
|
if algorithm != "AES256" {
|
|
t.Errorf("Expected algorithm AES256, got %s", algorithm)
|
|
}
|
|
|
|
// Should NOT have customer key headers
|
|
if w.Header().Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != "" {
|
|
t.Error("Should not have SSE-C customer algorithm header")
|
|
}
|
|
|
|
if w.Header().Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5) != "" {
|
|
t.Error("Should not have SSE-C customer key MD5 header")
|
|
}
|
|
|
|
// Should NOT have KMS key ID
|
|
if w.Header().Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId) != "" {
|
|
t.Error("Should not have SSE-KMS key ID header")
|
|
}
|
|
}
|
|
|
|
// TestSSES3IsEncryptedInternal tests detection of SSE-S3 encryption from metadata
|
|
func TestSSES3IsEncryptedInternal(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
metadata map[string][]byte
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "Empty metadata",
|
|
metadata: map[string][]byte{},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "Valid SSE-S3 metadata",
|
|
metadata: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("AES256"),
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "SSE-KMS metadata",
|
|
metadata: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryption: []byte("aws:kms"),
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "SSE-C metadata",
|
|
metadata: map[string][]byte{
|
|
s3_constants.AmzServerSideEncryptionCustomerAlgorithm: []byte("AES256"),
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
result := IsSSES3EncryptedInternal(tc.metadata)
|
|
if result != tc.expected {
|
|
t.Errorf("Expected %v, got %v", tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSSES3InvalidMetadataDeserialization tests error handling for invalid metadata
|
|
func TestSSES3InvalidMetadataDeserialization(t *testing.T) {
|
|
keyManager := NewSSES3KeyManager()
|
|
keyManager.superKey = make([]byte, 32)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
metadata []byte
|
|
shouldError bool
|
|
}{
|
|
{
|
|
name: "Empty metadata",
|
|
metadata: []byte{},
|
|
shouldError: true,
|
|
},
|
|
{
|
|
name: "Invalid JSON",
|
|
metadata: []byte("not valid json"),
|
|
shouldError: true,
|
|
},
|
|
{
|
|
name: "Missing keyId",
|
|
metadata: []byte(`{"algorithm":"AES256"}`),
|
|
shouldError: true,
|
|
},
|
|
{
|
|
name: "Invalid base64 encrypted DEK",
|
|
metadata: []byte(`{"keyId":"test","algorithm":"AES256","encryptedDEK":"not-valid-base64!","nonce":"dGVzdA=="}`),
|
|
shouldError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := DeserializeSSES3Metadata(tc.metadata, keyManager)
|
|
if tc.shouldError && err == nil {
|
|
t.Error("Expected error but got none")
|
|
}
|
|
if !tc.shouldError && err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetSSES3Headers tests SSE-S3 header generation
|
|
func TestGetSSES3Headers(t *testing.T) {
|
|
headers := GetSSES3Headers()
|
|
|
|
if len(headers) == 0 {
|
|
t.Error("Expected headers to be non-empty")
|
|
}
|
|
|
|
algorithm, exists := headers[s3_constants.AmzServerSideEncryption]
|
|
if !exists {
|
|
t.Error("Expected AmzServerSideEncryption header to exist")
|
|
}
|
|
|
|
if algorithm != "AES256" {
|
|
t.Errorf("Expected algorithm AES256, got %s", algorithm)
|
|
}
|
|
}
|
|
|
|
// TestProcessSSES3Request tests processing of SSE-S3 requests
|
|
func TestProcessSSES3Request(t *testing.T) {
|
|
// Initialize global key manager
|
|
globalSSES3KeyManager = NewSSES3KeyManager()
|
|
defer func() {
|
|
globalSSES3KeyManager = NewSSES3KeyManager()
|
|
}()
|
|
|
|
// Set up the key manager with a super key for testing
|
|
keyManager := GetSSES3KeyManager()
|
|
keyManager.superKey = make([]byte, 32)
|
|
for i := range keyManager.superKey {
|
|
keyManager.superKey[i] = byte(i)
|
|
}
|
|
|
|
// Create SSE-S3 request
|
|
req := httptest.NewRequest("PUT", "/bucket/object", nil)
|
|
req.Header.Set(s3_constants.AmzServerSideEncryption, "AES256")
|
|
|
|
// Process request
|
|
metadata, err := ProcessSSES3Request(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to process SSE-S3 request: %v", err)
|
|
}
|
|
|
|
if metadata == nil {
|
|
t.Fatal("Expected metadata to be non-nil")
|
|
}
|
|
|
|
// Verify metadata contains SSE algorithm
|
|
if sseAlgo, exists := metadata[s3_constants.AmzServerSideEncryption]; !exists {
|
|
t.Error("Expected SSE algorithm in metadata")
|
|
} else if string(sseAlgo) != "AES256" {
|
|
t.Errorf("Expected AES256, got %s", string(sseAlgo))
|
|
}
|
|
|
|
// Verify metadata contains key data
|
|
if _, exists := metadata[s3_constants.SeaweedFSSSES3Key]; !exists {
|
|
t.Error("Expected SSE-S3 key data in metadata")
|
|
}
|
|
}
|
|
|
|
// TestGetSSES3KeyFromMetadata tests extraction of SSE-S3 key from metadata
|
|
func TestGetSSES3KeyFromMetadata(t *testing.T) {
|
|
// Initialize global key manager
|
|
globalSSES3KeyManager = NewSSES3KeyManager()
|
|
defer func() {
|
|
globalSSES3KeyManager = NewSSES3KeyManager()
|
|
}()
|
|
|
|
// Set up the key manager with a super key for testing
|
|
keyManager := GetSSES3KeyManager()
|
|
keyManager.superKey = make([]byte, 32)
|
|
for i := range keyManager.superKey {
|
|
keyManager.superKey[i] = byte(i)
|
|
}
|
|
|
|
// Generate and serialize key
|
|
sseS3Key, err := GenerateSSES3Key()
|
|
if err != nil {
|
|
t.Fatalf("Failed to generate SSE-S3 key: %v", err)
|
|
}
|
|
|
|
sseS3Key.IV = make([]byte, 16)
|
|
for i := range sseS3Key.IV {
|
|
sseS3Key.IV[i] = byte(i)
|
|
}
|
|
|
|
serialized, err := SerializeSSES3Metadata(sseS3Key)
|
|
if err != nil {
|
|
t.Fatalf("Failed to serialize SSE-S3 metadata: %v", err)
|
|
}
|
|
|
|
metadata := map[string][]byte{
|
|
s3_constants.SeaweedFSSSES3Key: serialized,
|
|
}
|
|
|
|
// Extract key
|
|
extractedKey, err := GetSSES3KeyFromMetadata(metadata, keyManager)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get SSE-S3 key from metadata: %v", err)
|
|
}
|
|
|
|
// Verify key matches
|
|
if !bytes.Equal(extractedKey.Key, sseS3Key.Key) {
|
|
t.Error("Extracted key doesn't match original key")
|
|
}
|
|
|
|
if !bytes.Equal(extractedKey.IV, sseS3Key.IV) {
|
|
t.Error("Extracted IV doesn't match original IV")
|
|
}
|
|
}
|
|
|
|
// TestSSES3EnvelopeEncryption tests that envelope encryption works correctly
|
|
func TestSSES3EnvelopeEncryption(t *testing.T) {
|
|
// Initialize key manager with a super key
|
|
keyManager := NewSSES3KeyManager()
|
|
keyManager.superKey = make([]byte, 32)
|
|
for i := range keyManager.superKey {
|
|
keyManager.superKey[i] = byte(i + 100)
|
|
}
|
|
|
|
// Generate a DEK
|
|
dek := make([]byte, 32)
|
|
for i := range dek {
|
|
dek[i] = byte(i)
|
|
}
|
|
|
|
// Encrypt DEK with super key
|
|
encryptedDEK, nonce, err := keyManager.encryptKeyWithSuperKey(dek)
|
|
if err != nil {
|
|
t.Fatalf("Failed to encrypt DEK: %v", err)
|
|
}
|
|
|
|
if len(encryptedDEK) == 0 {
|
|
t.Error("Encrypted DEK is empty")
|
|
}
|
|
|
|
if len(nonce) == 0 {
|
|
t.Error("Nonce is empty")
|
|
}
|
|
|
|
// Decrypt DEK with super key
|
|
decryptedDEK, err := keyManager.decryptKeyWithSuperKey(encryptedDEK, nonce)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decrypt DEK: %v", err)
|
|
}
|
|
|
|
// Verify DEK matches
|
|
if !bytes.Equal(decryptedDEK, dek) {
|
|
t.Error("Decrypted DEK doesn't match original DEK")
|
|
}
|
|
}
|
|
|
|
// TestValidateSSES3Key tests SSE-S3 key validation
|
|
func TestValidateSSES3Key(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
key *SSES3Key
|
|
shouldError bool
|
|
errorMsg string
|
|
}{
|
|
{
|
|
name: "Nil key",
|
|
key: nil,
|
|
shouldError: true,
|
|
errorMsg: "SSE-S3 key cannot be nil",
|
|
},
|
|
{
|
|
name: "Valid key",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 32),
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
},
|
|
shouldError: false,
|
|
},
|
|
{
|
|
name: "Valid key with IV",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 32),
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
IV: make([]byte, 16),
|
|
},
|
|
shouldError: false,
|
|
},
|
|
{
|
|
name: "Invalid key size (too small)",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 16),
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
},
|
|
shouldError: true,
|
|
errorMsg: "invalid SSE-S3 key size",
|
|
},
|
|
{
|
|
name: "Invalid key size (too large)",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 64),
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
},
|
|
shouldError: true,
|
|
errorMsg: "invalid SSE-S3 key size",
|
|
},
|
|
{
|
|
name: "Nil key bytes",
|
|
key: &SSES3Key{
|
|
Key: nil,
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
},
|
|
shouldError: true,
|
|
errorMsg: "SSE-S3 key bytes cannot be nil",
|
|
},
|
|
{
|
|
name: "Empty key ID",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 32),
|
|
KeyID: "",
|
|
Algorithm: "AES256",
|
|
},
|
|
shouldError: true,
|
|
errorMsg: "SSE-S3 key ID cannot be empty",
|
|
},
|
|
{
|
|
name: "Invalid algorithm",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 32),
|
|
KeyID: "test-key",
|
|
Algorithm: "INVALID",
|
|
},
|
|
shouldError: true,
|
|
errorMsg: "invalid SSE-S3 algorithm",
|
|
},
|
|
{
|
|
name: "Invalid IV length",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 32),
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
IV: make([]byte, 8), // Wrong size
|
|
},
|
|
shouldError: true,
|
|
errorMsg: "invalid SSE-S3 IV length",
|
|
},
|
|
{
|
|
name: "Empty IV is allowed (set during encryption)",
|
|
key: &SSES3Key{
|
|
Key: make([]byte, 32),
|
|
KeyID: "test-key",
|
|
Algorithm: "AES256",
|
|
IV: []byte{}, // Empty is OK
|
|
},
|
|
shouldError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateSSES3Key(tc.key)
|
|
if tc.shouldError {
|
|
if err == nil {
|
|
t.Error("Expected error but got none")
|
|
} else if tc.errorMsg != "" && !strings.Contains(err.Error(), tc.errorMsg) {
|
|
t.Errorf("Expected error containing %q, got: %v", tc.errorMsg, err)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|