* fix: honor SSE-C chunk offsets in decryption for large chunked uploads Fixes issue #8215 where SSE-C decryption for large objects could corrupt data by ignoring per-chunk PartOffset values. Changes: - Add TestSSECLargeObjectChunkReassembly unit test to verify correct decryption of 19MB object split into 8MB chunks using PartOffset - Update decryptSSECChunkView and createMultipartSSECDecryptedReaderDirect to extract PartOffset from SSE-C metadata and pass to CreateSSECDecryptedReaderWithOffset for offset-aware decryption - Fix createCTRStreamWithOffset to use calculateIVWithOffset for proper block-aligned counter advancement, matching SSE-KMS/S3 behavior - Update comments to clarify SSE-C IV handling uses per-chunk offsets (unlike base IV approach used by KMS/S3) All tests pass: go test ./weed/s3api ✓ * fix: close chunkReader on error paths in createMultipartSSECDecryptedReader Address resource leak issue reported in PR #8216: ensure chunkReader is properly closed before returning on all error paths, including: - DeserializeSSECMetadata failures - IV decoding errors - Invalid PartOffset values - SSE-C reader creation failures - Missing per-chunk metadata This prevents leaking network connections and file handles during SSE-C multipart decryption error scenarios. * docs: clarify SSE-C IV handling in decryptSSECChunkView comment Replace misleading warning 'Do NOT call calculateIVWithOffset' with accurate explanation that: - CreateSSECDecryptedReaderWithOffset internally uses calculateIVWithOffset to advance the CTR counter to reach PartOffset - calculateIVWithOffset is applied only to the per-part IV, NOT to derive a global base IV for all parts - This differs fundamentally from SSE-KMS/SSE-S3 which use base IV + calculateIVWithOffset(ChunkOffset) This clarifies the IV advancement mechanism while contrasting it with the base IV approach used by other encryption schemes.
570 lines
17 KiB
Go
570 lines
17 KiB
Go
package s3api
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
)
|
|
|
|
// TestSSECMultipartUpload tests SSE-C with multipart uploads
|
|
func TestSSECMultipartUpload(t *testing.T) {
|
|
keyPair := GenerateTestSSECKey(1)
|
|
customerKey := &SSECustomerKey{
|
|
Algorithm: "AES256",
|
|
Key: keyPair.Key,
|
|
KeyMD5: keyPair.KeyMD5,
|
|
}
|
|
|
|
// Test data larger than typical part size
|
|
testData := strings.Repeat("Hello, SSE-C multipart world! ", 1000) // ~30KB
|
|
|
|
t.Run("Single part encryption/decryption", func(t *testing.T) {
|
|
// Encrypt the data
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(testData), customerKey)
|
|
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)
|
|
}
|
|
|
|
// Decrypt the data
|
|
decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), customerKey, 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)
|
|
}
|
|
|
|
if string(decryptedData) != testData {
|
|
t.Error("Decrypted data doesn't match original")
|
|
}
|
|
})
|
|
|
|
t.Run("Simulated multipart upload parts", func(t *testing.T) {
|
|
// Simulate multiple parts (each part gets encrypted separately)
|
|
partSize := 5 * 1024 // 5KB parts
|
|
var encryptedParts [][]byte
|
|
var partIVs [][]byte
|
|
|
|
for i := 0; i < len(testData); i += partSize {
|
|
end := i + partSize
|
|
if end > len(testData) {
|
|
end = len(testData)
|
|
}
|
|
|
|
partData := testData[i:end]
|
|
|
|
// Each part is encrypted separately in multipart uploads
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(partData), customerKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for part %d: %v", i/partSize, err)
|
|
}
|
|
|
|
encryptedPart, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted part %d: %v", i/partSize, err)
|
|
}
|
|
|
|
encryptedParts = append(encryptedParts, encryptedPart)
|
|
partIVs = append(partIVs, iv)
|
|
}
|
|
|
|
// Simulate reading back the multipart object
|
|
var reconstructedData strings.Builder
|
|
|
|
for i, encryptedPart := range encryptedParts {
|
|
decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedPart), customerKey, partIVs[i])
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for part %d: %v", i, err)
|
|
}
|
|
|
|
decryptedPart, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted part %d: %v", i, err)
|
|
}
|
|
|
|
reconstructedData.Write(decryptedPart)
|
|
}
|
|
|
|
if reconstructedData.String() != testData {
|
|
t.Error("Reconstructed multipart data doesn't match original")
|
|
}
|
|
})
|
|
|
|
t.Run("Multipart with different part sizes", func(t *testing.T) {
|
|
partSizes := []int{1024, 2048, 4096, 8192} // Various part sizes
|
|
|
|
for _, partSize := range partSizes {
|
|
t.Run(fmt.Sprintf("PartSize_%d", partSize), func(t *testing.T) {
|
|
var encryptedParts [][]byte
|
|
var partIVs [][]byte
|
|
|
|
for i := 0; i < len(testData); i += partSize {
|
|
end := i + partSize
|
|
if end > len(testData) {
|
|
end = len(testData)
|
|
}
|
|
|
|
partData := testData[i:end]
|
|
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(partData), customerKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader: %v", err)
|
|
}
|
|
|
|
encryptedPart, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted part: %v", err)
|
|
}
|
|
|
|
encryptedParts = append(encryptedParts, encryptedPart)
|
|
partIVs = append(partIVs, iv)
|
|
}
|
|
|
|
// Verify reconstruction
|
|
var reconstructedData strings.Builder
|
|
|
|
for j, encryptedPart := range encryptedParts {
|
|
decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedPart), customerKey, partIVs[j])
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader: %v", err)
|
|
}
|
|
|
|
decryptedPart, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted part: %v", err)
|
|
}
|
|
|
|
reconstructedData.Write(decryptedPart)
|
|
}
|
|
|
|
if reconstructedData.String() != testData {
|
|
t.Errorf("Reconstructed data doesn't match original for part size %d", partSize)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestSSEKMSMultipartUpload tests SSE-KMS with multipart uploads
|
|
func TestSSEKMSMultipartUpload(t *testing.T) {
|
|
kmsKey := SetupTestKMS(t)
|
|
defer kmsKey.Cleanup()
|
|
|
|
// Test data larger than typical part size
|
|
testData := strings.Repeat("Hello, SSE-KMS multipart world! ", 1000) // ~30KB
|
|
encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
|
|
|
|
t.Run("Single part encryption/decryption", func(t *testing.T) {
|
|
// Encrypt the data
|
|
encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(strings.NewReader(testData), kmsKey.KeyID, encryptionContext)
|
|
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)
|
|
}
|
|
|
|
// Decrypt the data
|
|
decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKey)
|
|
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)
|
|
}
|
|
|
|
if string(decryptedData) != testData {
|
|
t.Error("Decrypted data doesn't match original")
|
|
}
|
|
})
|
|
|
|
t.Run("Simulated multipart upload parts", func(t *testing.T) {
|
|
// Simulate multiple parts (each part might use the same or different KMS operations)
|
|
partSize := 5 * 1024 // 5KB parts
|
|
var encryptedParts [][]byte
|
|
var sseKeys []*SSEKMSKey
|
|
|
|
for i := 0; i < len(testData); i += partSize {
|
|
end := i + partSize
|
|
if end > len(testData) {
|
|
end = len(testData)
|
|
}
|
|
|
|
partData := testData[i:end]
|
|
|
|
// Each part might get its own data key in KMS multipart uploads
|
|
encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(strings.NewReader(partData), kmsKey.KeyID, encryptionContext)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for part %d: %v", i/partSize, err)
|
|
}
|
|
|
|
encryptedPart, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted part %d: %v", i/partSize, err)
|
|
}
|
|
|
|
encryptedParts = append(encryptedParts, encryptedPart)
|
|
sseKeys = append(sseKeys, sseKey)
|
|
}
|
|
|
|
// Simulate reading back the multipart object
|
|
var reconstructedData strings.Builder
|
|
|
|
for i, encryptedPart := range encryptedParts {
|
|
decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedPart), sseKeys[i])
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for part %d: %v", i, err)
|
|
}
|
|
|
|
decryptedPart, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted part %d: %v", i, err)
|
|
}
|
|
|
|
reconstructedData.Write(decryptedPart)
|
|
}
|
|
|
|
if reconstructedData.String() != testData {
|
|
t.Error("Reconstructed multipart data doesn't match original")
|
|
}
|
|
})
|
|
|
|
t.Run("Multipart consistency checks", func(t *testing.T) {
|
|
// Test that all parts use the same KMS key ID but different data keys
|
|
partSize := 5 * 1024
|
|
var sseKeys []*SSEKMSKey
|
|
|
|
for i := 0; i < len(testData); i += partSize {
|
|
end := i + partSize
|
|
if end > len(testData) {
|
|
end = len(testData)
|
|
}
|
|
|
|
partData := testData[i:end]
|
|
|
|
_, sseKey, err := CreateSSEKMSEncryptedReader(strings.NewReader(partData), kmsKey.KeyID, encryptionContext)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader: %v", err)
|
|
}
|
|
|
|
sseKeys = append(sseKeys, sseKey)
|
|
}
|
|
|
|
// Verify all parts use the same KMS key ID
|
|
for i, sseKey := range sseKeys {
|
|
if sseKey.KeyID != kmsKey.KeyID {
|
|
t.Errorf("Part %d has wrong KMS key ID: expected %s, got %s", i, kmsKey.KeyID, sseKey.KeyID)
|
|
}
|
|
}
|
|
|
|
// Verify each part has different encrypted data keys (they should be unique)
|
|
for i := 0; i < len(sseKeys); i++ {
|
|
for j := i + 1; j < len(sseKeys); j++ {
|
|
if bytes.Equal(sseKeys[i].EncryptedDataKey, sseKeys[j].EncryptedDataKey) {
|
|
t.Errorf("Parts %d and %d have identical encrypted data keys (should be unique)", i, j)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestMultipartSSEMixedScenarios tests edge cases with multipart and SSE
|
|
func TestMultipartSSEMixedScenarios(t *testing.T) {
|
|
t.Run("Empty parts handling", func(t *testing.T) {
|
|
keyPair := GenerateTestSSECKey(1)
|
|
customerKey := &SSECustomerKey{
|
|
Algorithm: "AES256",
|
|
Key: keyPair.Key,
|
|
KeyMD5: keyPair.KeyMD5,
|
|
}
|
|
|
|
// Test empty part
|
|
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)
|
|
}
|
|
|
|
// Empty part should produce empty encrypted data, but still have a valid IV
|
|
if len(encryptedData) != 0 {
|
|
t.Errorf("Expected empty encrypted data for empty part, got %d bytes", len(encryptedData))
|
|
}
|
|
if len(iv) != s3_constants.AESBlockSize {
|
|
t.Errorf("Expected IV of size %d, got %d", s3_constants.AESBlockSize, len(iv))
|
|
}
|
|
|
|
// 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("Single byte parts", func(t *testing.T) {
|
|
keyPair := GenerateTestSSECKey(1)
|
|
customerKey := &SSECustomerKey{
|
|
Algorithm: "AES256",
|
|
Key: keyPair.Key,
|
|
KeyMD5: keyPair.KeyMD5,
|
|
}
|
|
|
|
testData := "ABCDEFGHIJ"
|
|
var encryptedParts [][]byte
|
|
var partIVs [][]byte
|
|
|
|
// Encrypt each byte as a separate part
|
|
for i, b := range []byte(testData) {
|
|
partData := string(b)
|
|
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(strings.NewReader(partData), customerKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for byte %d: %v", i, err)
|
|
}
|
|
|
|
encryptedPart, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted byte %d: %v", i, err)
|
|
}
|
|
|
|
encryptedParts = append(encryptedParts, encryptedPart)
|
|
partIVs = append(partIVs, iv)
|
|
}
|
|
|
|
// Reconstruct
|
|
var reconstructedData strings.Builder
|
|
|
|
for i, encryptedPart := range encryptedParts {
|
|
decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedPart), customerKey, partIVs[i])
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for byte %d: %v", i, err)
|
|
}
|
|
|
|
decryptedPart, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted byte %d: %v", i, err)
|
|
}
|
|
|
|
reconstructedData.Write(decryptedPart)
|
|
}
|
|
|
|
if reconstructedData.String() != testData {
|
|
t.Errorf("Expected %s, got %s", testData, reconstructedData.String())
|
|
}
|
|
})
|
|
|
|
t.Run("Very large parts", func(t *testing.T) {
|
|
keyPair := GenerateTestSSECKey(1)
|
|
customerKey := &SSECustomerKey{
|
|
Algorithm: "AES256",
|
|
Key: keyPair.Key,
|
|
KeyMD5: keyPair.KeyMD5,
|
|
}
|
|
|
|
// Create a large part (1MB)
|
|
largeData := make([]byte, 1024*1024)
|
|
for i := range largeData {
|
|
largeData[i] = byte(i % 256)
|
|
}
|
|
|
|
// Encrypt
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(bytes.NewReader(largeData), customerKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for large data: %v", err)
|
|
}
|
|
|
|
encryptedData, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted large data: %v", err)
|
|
}
|
|
|
|
// Decrypt
|
|
decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), customerKey, iv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for large data: %v", err)
|
|
}
|
|
|
|
decryptedData, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted large data: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(decryptedData, largeData) {
|
|
t.Error("Large data doesn't match after encryption/decryption")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSSECLargeObjectChunkReassembly(t *testing.T) {
|
|
keyPair := GenerateTestSSECKey(1)
|
|
customerKey := &SSECustomerKey{
|
|
Algorithm: "AES256",
|
|
Key: keyPair.Key,
|
|
KeyMD5: keyPair.KeyMD5,
|
|
}
|
|
|
|
const chunkSize = 8 * 1024 * 1024 // matches putToFiler chunk size
|
|
totalSize := chunkSize*2 + 3*1024*1024
|
|
plaintext := make([]byte, totalSize)
|
|
for i := range plaintext {
|
|
plaintext[i] = byte(i % 251)
|
|
}
|
|
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(bytes.NewReader(plaintext), customerKey)
|
|
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)
|
|
}
|
|
|
|
var reconstructed bytes.Buffer
|
|
offset := int64(0)
|
|
for offset < int64(len(encryptedData)) {
|
|
end := offset + chunkSize
|
|
if end > int64(len(encryptedData)) {
|
|
end = int64(len(encryptedData))
|
|
}
|
|
|
|
chunkIV := make([]byte, len(iv))
|
|
copy(chunkIV, iv)
|
|
chunkReader := bytes.NewReader(encryptedData[offset:end])
|
|
decryptedReader, decErr := CreateSSECDecryptedReaderWithOffset(chunkReader, customerKey, chunkIV, uint64(offset))
|
|
if decErr != nil {
|
|
t.Fatalf("Failed to create decrypted reader for offset %d: %v", offset, decErr)
|
|
}
|
|
decryptedChunk, decErr := io.ReadAll(decryptedReader)
|
|
if decErr != nil {
|
|
t.Fatalf("Failed to read decrypted chunk at offset %d: %v", offset, decErr)
|
|
}
|
|
reconstructed.Write(decryptedChunk)
|
|
offset = end
|
|
}
|
|
|
|
if !bytes.Equal(reconstructed.Bytes(), plaintext) {
|
|
t.Fatalf("Reconstructed data mismatch: expected %d bytes, got %d", len(plaintext), reconstructed.Len())
|
|
}
|
|
}
|
|
|
|
// TestMultipartSSEPerformance tests performance characteristics of SSE with multipart
|
|
func TestMultipartSSEPerformance(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping performance test in short mode")
|
|
}
|
|
|
|
t.Run("SSE-C performance with multiple parts", func(t *testing.T) {
|
|
keyPair := GenerateTestSSECKey(1)
|
|
customerKey := &SSECustomerKey{
|
|
Algorithm: "AES256",
|
|
Key: keyPair.Key,
|
|
KeyMD5: keyPair.KeyMD5,
|
|
}
|
|
|
|
partSize := 64 * 1024 // 64KB parts
|
|
numParts := 10
|
|
|
|
for partNum := 0; partNum < numParts; partNum++ {
|
|
partData := make([]byte, partSize)
|
|
for i := range partData {
|
|
partData[i] = byte((partNum + i) % 256)
|
|
}
|
|
|
|
// Encrypt
|
|
encryptedReader, iv, err := CreateSSECEncryptedReader(bytes.NewReader(partData), customerKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for part %d: %v", partNum, err)
|
|
}
|
|
|
|
encryptedData, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted data for part %d: %v", partNum, err)
|
|
}
|
|
|
|
// Decrypt
|
|
decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), customerKey, iv)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for part %d: %v", partNum, err)
|
|
}
|
|
|
|
decryptedData, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data for part %d: %v", partNum, err)
|
|
}
|
|
|
|
if !bytes.Equal(decryptedData, partData) {
|
|
t.Errorf("Data mismatch for part %d", partNum)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("SSE-KMS performance with multiple parts", func(t *testing.T) {
|
|
kmsKey := SetupTestKMS(t)
|
|
defer kmsKey.Cleanup()
|
|
|
|
partSize := 64 * 1024 // 64KB parts
|
|
numParts := 5 // Fewer parts for KMS due to overhead
|
|
encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
|
|
|
|
for partNum := 0; partNum < numParts; partNum++ {
|
|
partData := make([]byte, partSize)
|
|
for i := range partData {
|
|
partData[i] = byte((partNum + i) % 256)
|
|
}
|
|
|
|
// Encrypt
|
|
encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(bytes.NewReader(partData), kmsKey.KeyID, encryptionContext)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create encrypted reader for part %d: %v", partNum, err)
|
|
}
|
|
|
|
encryptedData, err := io.ReadAll(encryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read encrypted data for part %d: %v", partNum, err)
|
|
}
|
|
|
|
// Decrypt
|
|
decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create decrypted reader for part %d: %v", partNum, err)
|
|
}
|
|
|
|
decryptedData, err := io.ReadAll(decryptedReader)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read decrypted data for part %d: %v", partNum, err)
|
|
}
|
|
|
|
if !bytes.Equal(decryptedData, partData) {
|
|
t.Errorf("Data mismatch for part %d", partNum)
|
|
}
|
|
}
|
|
})
|
|
}
|