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.
		
		
		
		
		
			
		
			
				
					
					
						
							373 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							373 lines
						
					
					
						
							14 KiB
						
					
					
				
								package sse_test
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"bytes"
							 | 
						|
									"context"
							 | 
						|
									"crypto/md5"
							 | 
						|
									"fmt"
							 | 
						|
									"io"
							 | 
						|
									"testing"
							 | 
						|
								
							 | 
						|
									"github.com/aws/aws-sdk-go-v2/aws"
							 | 
						|
									"github.com/aws/aws-sdk-go-v2/service/s3"
							 | 
						|
									"github.com/aws/aws-sdk-go-v2/service/s3/types"
							 | 
						|
									"github.com/stretchr/testify/require"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestSSEMultipartCopy tests copying multipart encrypted objects
							 | 
						|
								func TestSSEMultipartCopy(t *testing.T) {
							 | 
						|
									ctx := context.Background()
							 | 
						|
									client, err := createS3Client(ctx, defaultConfig)
							 | 
						|
									require.NoError(t, err, "Failed to create S3 client")
							 | 
						|
								
							 | 
						|
									bucketName, err := createTestBucket(ctx, client, defaultConfig.BucketPrefix+"sse-multipart-copy-")
							 | 
						|
									require.NoError(t, err, "Failed to create test bucket")
							 | 
						|
									defer cleanupTestBucket(ctx, client, bucketName)
							 | 
						|
								
							 | 
						|
									// Generate test data for multipart upload (7.5MB)
							 | 
						|
									originalData := generateTestData(7*1024*1024 + 512*1024)
							 | 
						|
									originalMD5 := fmt.Sprintf("%x", md5.Sum(originalData))
							 | 
						|
								
							 | 
						|
									t.Run("Copy SSE-C Multipart Object", func(t *testing.T) {
							 | 
						|
										testSSECMultipartCopy(t, ctx, client, bucketName, originalData, originalMD5)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Copy SSE-KMS Multipart Object", func(t *testing.T) {
							 | 
						|
										testSSEKMSMultipartCopy(t, ctx, client, bucketName, originalData, originalMD5)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Copy SSE-C to SSE-KMS", func(t *testing.T) {
							 | 
						|
										testSSECToSSEKMSCopy(t, ctx, client, bucketName, originalData, originalMD5)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Copy SSE-KMS to SSE-C", func(t *testing.T) {
							 | 
						|
										testSSEKMSToSSECCopy(t, ctx, client, bucketName, originalData, originalMD5)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Copy SSE-C to Unencrypted", func(t *testing.T) {
							 | 
						|
										testSSECToUnencryptedCopy(t, ctx, client, bucketName, originalData, originalMD5)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Copy SSE-KMS to Unencrypted", func(t *testing.T) {
							 | 
						|
										testSSEKMSToUnencryptedCopy(t, ctx, client, bucketName, originalData, originalMD5)
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// testSSECMultipartCopy tests copying SSE-C multipart objects with same key
							 | 
						|
								func testSSECMultipartCopy(t *testing.T, ctx context.Context, client *s3.Client, bucketName string, originalData []byte, originalMD5 string) {
							 | 
						|
									sseKey := generateSSECKey()
							 | 
						|
								
							 | 
						|
									// Upload original multipart SSE-C object
							 | 
						|
									sourceKey := "source-ssec-multipart-object"
							 | 
						|
									err := uploadMultipartSSECObject(ctx, client, bucketName, sourceKey, originalData, *sseKey)
							 | 
						|
									require.NoError(t, err, "Failed to upload source SSE-C multipart object")
							 | 
						|
								
							 | 
						|
									// Copy with same SSE-C key
							 | 
						|
									destKey := "dest-ssec-multipart-object"
							 | 
						|
									_, err = client.CopyObject(ctx, &s3.CopyObjectInput{
							 | 
						|
										Bucket:     aws.String(bucketName),
							 | 
						|
										Key:        aws.String(destKey),
							 | 
						|
										CopySource: aws.String(fmt.Sprintf("%s/%s", bucketName, sourceKey)),
							 | 
						|
										// Copy source SSE-C headers
							 | 
						|
										CopySourceSSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
										CopySourceSSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
										CopySourceSSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
										// Destination SSE-C headers (same key)
							 | 
						|
										SSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
										SSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
										SSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
									})
							 | 
						|
									require.NoError(t, err, "Failed to copy SSE-C multipart object")
							 | 
						|
								
							 | 
						|
									// Verify copied object
							 | 
						|
									verifyEncryptedObject(t, ctx, client, bucketName, destKey, originalData, originalMD5, sseKey, nil)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// testSSEKMSMultipartCopy tests copying SSE-KMS multipart objects with same key
							 | 
						|
								func testSSEKMSMultipartCopy(t *testing.T, ctx context.Context, client *s3.Client, bucketName string, originalData []byte, originalMD5 string) {
							 | 
						|
									// Upload original multipart SSE-KMS object
							 | 
						|
									sourceKey := "source-ssekms-multipart-object"
							 | 
						|
									err := uploadMultipartSSEKMSObject(ctx, client, bucketName, sourceKey, "test-multipart-key", originalData)
							 | 
						|
									require.NoError(t, err, "Failed to upload source SSE-KMS multipart object")
							 | 
						|
								
							 | 
						|
									// Copy with same SSE-KMS key
							 | 
						|
									destKey := "dest-ssekms-multipart-object"
							 | 
						|
									_, err = client.CopyObject(ctx, &s3.CopyObjectInput{
							 | 
						|
										Bucket:               aws.String(bucketName),
							 | 
						|
										Key:                  aws.String(destKey),
							 | 
						|
										CopySource:           aws.String(fmt.Sprintf("%s/%s", bucketName, sourceKey)),
							 | 
						|
										ServerSideEncryption: types.ServerSideEncryptionAwsKms,
							 | 
						|
										SSEKMSKeyId:          aws.String("test-multipart-key"),
							 | 
						|
										BucketKeyEnabled:     aws.Bool(false),
							 | 
						|
									})
							 | 
						|
									require.NoError(t, err, "Failed to copy SSE-KMS multipart object")
							 | 
						|
								
							 | 
						|
									// Verify copied object
							 | 
						|
									verifyEncryptedObject(t, ctx, client, bucketName, destKey, originalData, originalMD5, nil, aws.String("test-multipart-key"))
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// testSSECToSSEKMSCopy tests copying SSE-C multipart objects to SSE-KMS
							 | 
						|
								func testSSECToSSEKMSCopy(t *testing.T, ctx context.Context, client *s3.Client, bucketName string, originalData []byte, originalMD5 string) {
							 | 
						|
									sseKey := generateSSECKey()
							 | 
						|
								
							 | 
						|
									// Upload original multipart SSE-C object
							 | 
						|
									sourceKey := "source-ssec-multipart-for-kms"
							 | 
						|
									err := uploadMultipartSSECObject(ctx, client, bucketName, sourceKey, originalData, *sseKey)
							 | 
						|
									require.NoError(t, err, "Failed to upload source SSE-C multipart object")
							 | 
						|
								
							 | 
						|
									// Copy to SSE-KMS
							 | 
						|
									destKey := "dest-ssekms-from-ssec"
							 | 
						|
									_, err = client.CopyObject(ctx, &s3.CopyObjectInput{
							 | 
						|
										Bucket:     aws.String(bucketName),
							 | 
						|
										Key:        aws.String(destKey),
							 | 
						|
										CopySource: aws.String(fmt.Sprintf("%s/%s", bucketName, sourceKey)),
							 | 
						|
										// Copy source SSE-C headers
							 | 
						|
										CopySourceSSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
										CopySourceSSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
										CopySourceSSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
										// Destination SSE-KMS headers
							 | 
						|
										ServerSideEncryption: types.ServerSideEncryptionAwsKms,
							 | 
						|
										SSEKMSKeyId:          aws.String("test-multipart-key"),
							 | 
						|
										BucketKeyEnabled:     aws.Bool(false),
							 | 
						|
									})
							 | 
						|
									require.NoError(t, err, "Failed to copy SSE-C to SSE-KMS")
							 | 
						|
								
							 | 
						|
									// Verify copied object as SSE-KMS
							 | 
						|
									verifyEncryptedObject(t, ctx, client, bucketName, destKey, originalData, originalMD5, nil, aws.String("test-multipart-key"))
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// testSSEKMSToSSECCopy tests copying SSE-KMS multipart objects to SSE-C
							 | 
						|
								func testSSEKMSToSSECCopy(t *testing.T, ctx context.Context, client *s3.Client, bucketName string, originalData []byte, originalMD5 string) {
							 | 
						|
									sseKey := generateSSECKey()
							 | 
						|
								
							 | 
						|
									// Upload original multipart SSE-KMS object
							 | 
						|
									sourceKey := "source-ssekms-multipart-for-ssec"
							 | 
						|
									err := uploadMultipartSSEKMSObject(ctx, client, bucketName, sourceKey, "test-multipart-key", originalData)
							 | 
						|
									require.NoError(t, err, "Failed to upload source SSE-KMS multipart object")
							 | 
						|
								
							 | 
						|
									// Copy to SSE-C
							 | 
						|
									destKey := "dest-ssec-from-ssekms"
							 | 
						|
									_, err = client.CopyObject(ctx, &s3.CopyObjectInput{
							 | 
						|
										Bucket:     aws.String(bucketName),
							 | 
						|
										Key:        aws.String(destKey),
							 | 
						|
										CopySource: aws.String(fmt.Sprintf("%s/%s", bucketName, sourceKey)),
							 | 
						|
										// Destination SSE-C headers
							 | 
						|
										SSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
										SSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
										SSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
									})
							 | 
						|
									require.NoError(t, err, "Failed to copy SSE-KMS to SSE-C")
							 | 
						|
								
							 | 
						|
									// Verify copied object as SSE-C
							 | 
						|
									verifyEncryptedObject(t, ctx, client, bucketName, destKey, originalData, originalMD5, sseKey, nil)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// testSSECToUnencryptedCopy tests copying SSE-C multipart objects to unencrypted
							 | 
						|
								func testSSECToUnencryptedCopy(t *testing.T, ctx context.Context, client *s3.Client, bucketName string, originalData []byte, originalMD5 string) {
							 | 
						|
									sseKey := generateSSECKey()
							 | 
						|
								
							 | 
						|
									// Upload original multipart SSE-C object
							 | 
						|
									sourceKey := "source-ssec-multipart-for-plain"
							 | 
						|
									err := uploadMultipartSSECObject(ctx, client, bucketName, sourceKey, originalData, *sseKey)
							 | 
						|
									require.NoError(t, err, "Failed to upload source SSE-C multipart object")
							 | 
						|
								
							 | 
						|
									// Copy to unencrypted
							 | 
						|
									destKey := "dest-plain-from-ssec"
							 | 
						|
									_, err = client.CopyObject(ctx, &s3.CopyObjectInput{
							 | 
						|
										Bucket:     aws.String(bucketName),
							 | 
						|
										Key:        aws.String(destKey),
							 | 
						|
										CopySource: aws.String(fmt.Sprintf("%s/%s", bucketName, sourceKey)),
							 | 
						|
										// Copy source SSE-C headers
							 | 
						|
										CopySourceSSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
										CopySourceSSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
										CopySourceSSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
										// No destination encryption headers
							 | 
						|
									})
							 | 
						|
									require.NoError(t, err, "Failed to copy SSE-C to unencrypted")
							 | 
						|
								
							 | 
						|
									// Verify copied object as unencrypted
							 | 
						|
									verifyEncryptedObject(t, ctx, client, bucketName, destKey, originalData, originalMD5, nil, nil)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// testSSEKMSToUnencryptedCopy tests copying SSE-KMS multipart objects to unencrypted
							 | 
						|
								func testSSEKMSToUnencryptedCopy(t *testing.T, ctx context.Context, client *s3.Client, bucketName string, originalData []byte, originalMD5 string) {
							 | 
						|
									// Upload original multipart SSE-KMS object
							 | 
						|
									sourceKey := "source-ssekms-multipart-for-plain"
							 | 
						|
									err := uploadMultipartSSEKMSObject(ctx, client, bucketName, sourceKey, "test-multipart-key", originalData)
							 | 
						|
									require.NoError(t, err, "Failed to upload source SSE-KMS multipart object")
							 | 
						|
								
							 | 
						|
									// Copy to unencrypted
							 | 
						|
									destKey := "dest-plain-from-ssekms"
							 | 
						|
									_, err = client.CopyObject(ctx, &s3.CopyObjectInput{
							 | 
						|
										Bucket:     aws.String(bucketName),
							 | 
						|
										Key:        aws.String(destKey),
							 | 
						|
										CopySource: aws.String(fmt.Sprintf("%s/%s", bucketName, sourceKey)),
							 | 
						|
										// No destination encryption headers
							 | 
						|
									})
							 | 
						|
									require.NoError(t, err, "Failed to copy SSE-KMS to unencrypted")
							 | 
						|
								
							 | 
						|
									// Verify copied object as unencrypted
							 | 
						|
									verifyEncryptedObject(t, ctx, client, bucketName, destKey, originalData, originalMD5, nil, nil)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// uploadMultipartSSECObject uploads a multipart SSE-C object
							 | 
						|
								func uploadMultipartSSECObject(ctx context.Context, client *s3.Client, bucketName, objectKey string, data []byte, sseKey SSECKey) error {
							 | 
						|
									// Create multipart upload
							 | 
						|
									createResp, err := client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
							 | 
						|
										Bucket:               aws.String(bucketName),
							 | 
						|
										Key:                  aws.String(objectKey),
							 | 
						|
										SSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
										SSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
										SSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
									uploadID := aws.ToString(createResp.UploadId)
							 | 
						|
								
							 | 
						|
									// Upload parts
							 | 
						|
									partSize := 5 * 1024 * 1024 // 5MB
							 | 
						|
									var completedParts []types.CompletedPart
							 | 
						|
								
							 | 
						|
									for i := 0; i < len(data); i += partSize {
							 | 
						|
										end := i + partSize
							 | 
						|
										if end > len(data) {
							 | 
						|
											end = len(data)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										partNumber := int32(len(completedParts) + 1)
							 | 
						|
										partResp, err := client.UploadPart(ctx, &s3.UploadPartInput{
							 | 
						|
											Bucket:               aws.String(bucketName),
							 | 
						|
											Key:                  aws.String(objectKey),
							 | 
						|
											PartNumber:           aws.Int32(partNumber),
							 | 
						|
											UploadId:             aws.String(uploadID),
							 | 
						|
											Body:                 bytes.NewReader(data[i:end]),
							 | 
						|
											SSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
											SSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
											SSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
										})
							 | 
						|
										if err != nil {
							 | 
						|
											return err
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										completedParts = append(completedParts, types.CompletedPart{
							 | 
						|
											ETag:       partResp.ETag,
							 | 
						|
											PartNumber: aws.Int32(partNumber),
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Complete multipart upload
							 | 
						|
									_, err = client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
							 | 
						|
										Bucket:   aws.String(bucketName),
							 | 
						|
										Key:      aws.String(objectKey),
							 | 
						|
										UploadId: aws.String(uploadID),
							 | 
						|
										MultipartUpload: &types.CompletedMultipartUpload{
							 | 
						|
											Parts: completedParts,
							 | 
						|
										},
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									return err
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// uploadMultipartSSEKMSObject uploads a multipart SSE-KMS object
							 | 
						|
								func uploadMultipartSSEKMSObject(ctx context.Context, client *s3.Client, bucketName, objectKey, keyID string, data []byte) error {
							 | 
						|
									// Create multipart upload
							 | 
						|
									createResp, err := client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{
							 | 
						|
										Bucket:               aws.String(bucketName),
							 | 
						|
										Key:                  aws.String(objectKey),
							 | 
						|
										ServerSideEncryption: types.ServerSideEncryptionAwsKms,
							 | 
						|
										SSEKMSKeyId:          aws.String(keyID),
							 | 
						|
										BucketKeyEnabled:     aws.Bool(false),
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
									uploadID := aws.ToString(createResp.UploadId)
							 | 
						|
								
							 | 
						|
									// Upload parts
							 | 
						|
									partSize := 5 * 1024 * 1024 // 5MB
							 | 
						|
									var completedParts []types.CompletedPart
							 | 
						|
								
							 | 
						|
									for i := 0; i < len(data); i += partSize {
							 | 
						|
										end := i + partSize
							 | 
						|
										if end > len(data) {
							 | 
						|
											end = len(data)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										partNumber := int32(len(completedParts) + 1)
							 | 
						|
										partResp, err := client.UploadPart(ctx, &s3.UploadPartInput{
							 | 
						|
											Bucket:     aws.String(bucketName),
							 | 
						|
											Key:        aws.String(objectKey),
							 | 
						|
											PartNumber: aws.Int32(partNumber),
							 | 
						|
											UploadId:   aws.String(uploadID),
							 | 
						|
											Body:       bytes.NewReader(data[i:end]),
							 | 
						|
										})
							 | 
						|
										if err != nil {
							 | 
						|
											return err
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										completedParts = append(completedParts, types.CompletedPart{
							 | 
						|
											ETag:       partResp.ETag,
							 | 
						|
											PartNumber: aws.Int32(partNumber),
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Complete multipart upload
							 | 
						|
									_, err = client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{
							 | 
						|
										Bucket:   aws.String(bucketName),
							 | 
						|
										Key:      aws.String(objectKey),
							 | 
						|
										UploadId: aws.String(uploadID),
							 | 
						|
										MultipartUpload: &types.CompletedMultipartUpload{
							 | 
						|
											Parts: completedParts,
							 | 
						|
										},
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									return err
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// verifyEncryptedObject verifies that a copied object can be retrieved and matches the original data
							 | 
						|
								func verifyEncryptedObject(t *testing.T, ctx context.Context, client *s3.Client, bucketName, objectKey string, expectedData []byte, expectedMD5 string, sseKey *SSECKey, kmsKeyID *string) {
							 | 
						|
									var getInput *s3.GetObjectInput
							 | 
						|
								
							 | 
						|
									if sseKey != nil {
							 | 
						|
										// SSE-C object
							 | 
						|
										getInput = &s3.GetObjectInput{
							 | 
						|
											Bucket:               aws.String(bucketName),
							 | 
						|
											Key:                  aws.String(objectKey),
							 | 
						|
											SSECustomerAlgorithm: aws.String("AES256"),
							 | 
						|
											SSECustomerKey:       aws.String(sseKey.KeyB64),
							 | 
						|
											SSECustomerKeyMD5:    aws.String(sseKey.KeyMD5),
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										// SSE-KMS or unencrypted object
							 | 
						|
										getInput = &s3.GetObjectInput{
							 | 
						|
											Bucket: aws.String(bucketName),
							 | 
						|
											Key:    aws.String(objectKey),
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									getResp, err := client.GetObject(ctx, getInput)
							 | 
						|
									require.NoError(t, err, "Failed to retrieve copied object %s", objectKey)
							 | 
						|
									defer getResp.Body.Close()
							 | 
						|
								
							 | 
						|
									// Read and verify data
							 | 
						|
									retrievedData, err := io.ReadAll(getResp.Body)
							 | 
						|
									require.NoError(t, err, "Failed to read copied object data")
							 | 
						|
								
							 | 
						|
									require.Equal(t, len(expectedData), len(retrievedData), "Data size mismatch for object %s", objectKey)
							 | 
						|
								
							 | 
						|
									// Verify data using MD5
							 | 
						|
									retrievedMD5 := fmt.Sprintf("%x", md5.Sum(retrievedData))
							 | 
						|
									require.Equal(t, expectedMD5, retrievedMD5, "Data MD5 mismatch for object %s", objectKey)
							 | 
						|
								
							 | 
						|
									// Verify encryption headers
							 | 
						|
									if sseKey != nil {
							 | 
						|
										require.Equal(t, "AES256", aws.ToString(getResp.SSECustomerAlgorithm), "SSE-C algorithm mismatch")
							 | 
						|
										require.Equal(t, sseKey.KeyMD5, aws.ToString(getResp.SSECustomerKeyMD5), "SSE-C key MD5 mismatch")
							 | 
						|
									} else if kmsKeyID != nil {
							 | 
						|
										require.Equal(t, types.ServerSideEncryptionAwsKms, getResp.ServerSideEncryption, "SSE-KMS encryption mismatch")
							 | 
						|
										require.Contains(t, aws.ToString(getResp.SSEKMSKeyId), *kmsKeyID, "SSE-KMS key ID mismatch")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									t.Logf("Successfully verified copied object %s: %d bytes, MD5=%s", objectKey, len(retrievedData), retrievedMD5)
							 | 
						|
								}
							 |