diff options
Diffstat (limited to 'weed/s3api/s3api_sse_decrypt_test.go')
| -rw-r--r-- | weed/s3api/s3api_sse_decrypt_test.go | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/weed/s3api/s3api_sse_decrypt_test.go b/weed/s3api/s3api_sse_decrypt_test.go new file mode 100644 index 000000000..f66a89ebd --- /dev/null +++ b/weed/s3api/s3api_sse_decrypt_test.go @@ -0,0 +1,189 @@ +package s3api + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" + "testing" +) + +// TestSSECDecryptChunkView_NoOffsetAdjustment verifies that SSE-C decryption +// does NOT apply calculateIVWithOffset, preventing the critical bug where +// offset adjustment would cause CTR stream misalignment and data corruption. +func TestSSECDecryptChunkView_NoOffsetAdjustment(t *testing.T) { + // Setup: Create test data + plaintext := []byte("This is a test message for SSE-C decryption without offset adjustment") + customerKey := &SSECustomerKey{ + Key: make([]byte, 32), // 256-bit key + KeyMD5: "test-key-md5", + } + // Generate random AES key + if _, err := rand.Read(customerKey.Key); err != nil { + t.Fatalf("Failed to generate random key: %v", err) + } + + // Generate random IV for this "part" + randomIV := make([]byte, aes.BlockSize) + if _, err := rand.Read(randomIV); err != nil { + t.Fatalf("Failed to generate random IV: %v", err) + } + + // Encrypt the plaintext using the random IV (simulating SSE-C multipart upload) + // This is what CreateSSECEncryptedReader does - uses the IV directly without offset + block, err := aes.NewCipher(customerKey.Key) + if err != nil { + t.Fatalf("Failed to create cipher: %v", err) + } + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCTR(block, randomIV) + stream.XORKeyStream(ciphertext, plaintext) + + partOffset := int64(1024) // Non-zero offset that should NOT be applied during SSE-C decryption + + // TEST: Decrypt using stored IV directly (correct behavior) + decryptedReaderCorrect, err := CreateSSECDecryptedReader( + io.NopCloser(bytes.NewReader(ciphertext)), + customerKey, + randomIV, // Use stored IV directly - CORRECT + ) + if err != nil { + t.Fatalf("Failed to create decrypted reader (correct): %v", err) + } + decryptedCorrect, err := io.ReadAll(decryptedReaderCorrect) + if err != nil { + t.Fatalf("Failed to read decrypted data (correct): %v", err) + } + + // Verify correct decryption + if !bytes.Equal(decryptedCorrect, plaintext) { + t.Errorf("Correct decryption failed:\nExpected: %s\nGot: %s", plaintext, decryptedCorrect) + } else { + t.Logf("✓ Correct decryption (using stored IV directly) successful") + } + + // ANTI-TEST: Decrypt using offset-adjusted IV (incorrect behavior - the bug) + adjustedIV, ivSkip := calculateIVWithOffset(randomIV, partOffset) + decryptedReaderWrong, err := CreateSSECDecryptedReader( + io.NopCloser(bytes.NewReader(ciphertext)), + customerKey, + adjustedIV, // Use adjusted IV - WRONG + ) + if err != nil { + t.Fatalf("Failed to create decrypted reader (wrong): %v", err) + } + + // Skip ivSkip bytes (as the buggy code would do) + if ivSkip > 0 { + io.CopyN(io.Discard, decryptedReaderWrong, int64(ivSkip)) + } + + decryptedWrong, err := io.ReadAll(decryptedReaderWrong) + if err != nil { + t.Fatalf("Failed to read decrypted data (wrong): %v", err) + } + + // Verify that offset adjustment produces DIFFERENT (corrupted) output + if bytes.Equal(decryptedWrong, plaintext) { + t.Errorf("CRITICAL: Offset-adjusted IV produced correct plaintext! This shouldn't happen for SSE-C.") + } else { + t.Logf("✓ Verified: Offset-adjusted IV produces corrupted data (as expected for SSE-C)") + maxLen := 20 + if len(plaintext) < maxLen { + maxLen = len(plaintext) + } + t.Logf(" Plaintext: %q", plaintext[:maxLen]) + maxLen2 := 20 + if len(decryptedWrong) < maxLen2 { + maxLen2 = len(decryptedWrong) + } + t.Logf(" Corrupted: %q", decryptedWrong[:maxLen2]) + } +} + +// TestSSEKMSDecryptChunkView_RequiresOffsetAdjustment verifies that SSE-KMS +// decryption DOES require calculateIVWithOffset, unlike SSE-C. +func TestSSEKMSDecryptChunkView_RequiresOffsetAdjustment(t *testing.T) { + // Setup: Create test data + plaintext := []byte("This is a test message for SSE-KMS decryption with offset adjustment") + + // Generate base IV and key + baseIV := make([]byte, aes.BlockSize) + key := make([]byte, 32) + if _, err := rand.Read(baseIV); err != nil { + t.Fatalf("Failed to generate base IV: %v", err) + } + if _, err := rand.Read(key); err != nil { + t.Fatalf("Failed to generate key: %v", err) + } + + chunkOffset := int64(2048) // Simulate chunk at offset 2048 + + // Encrypt using base IV + offset (simulating SSE-KMS multipart upload) + adjustedIV, ivSkip := calculateIVWithOffset(baseIV, chunkOffset) + block, err := aes.NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher: %v", err) + } + + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCTR(block, adjustedIV) + + // Skip ivSkip bytes in the encryption stream if needed + if ivSkip > 0 { + dummy := make([]byte, ivSkip) + stream.XORKeyStream(dummy, dummy) + } + stream.XORKeyStream(ciphertext, plaintext) + + // TEST: Decrypt using base IV + offset adjustment (correct for SSE-KMS) + adjustedIVDecrypt, ivSkipDecrypt := calculateIVWithOffset(baseIV, chunkOffset) + blockDecrypt, err := aes.NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher for decryption: %v", err) + } + + decrypted := make([]byte, len(ciphertext)) + streamDecrypt := cipher.NewCTR(blockDecrypt, adjustedIVDecrypt) + + // Skip ivSkip bytes in the decryption stream + if ivSkipDecrypt > 0 { + dummy := make([]byte, ivSkipDecrypt) + streamDecrypt.XORKeyStream(dummy, dummy) + } + streamDecrypt.XORKeyStream(decrypted, ciphertext) + + // Verify correct decryption with offset adjustment + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("SSE-KMS decryption with offset adjustment failed:\nExpected: %s\nGot: %s", plaintext, decrypted) + } else { + t.Logf("✓ SSE-KMS decryption with offset adjustment successful") + } + + // ANTI-TEST: Decrypt using base IV directly (incorrect for SSE-KMS) + blockWrong, err := aes.NewCipher(key) + if err != nil { + t.Fatalf("Failed to create cipher for wrong decryption: %v", err) + } + + decryptedWrong := make([]byte, len(ciphertext)) + streamWrong := cipher.NewCTR(blockWrong, baseIV) // Use base IV directly - WRONG for SSE-KMS + streamWrong.XORKeyStream(decryptedWrong, ciphertext) + + // Verify that NOT using offset adjustment produces corrupted output + if bytes.Equal(decryptedWrong, plaintext) { + t.Errorf("CRITICAL: Base IV without offset produced correct plaintext! SSE-KMS requires offset adjustment.") + } else { + t.Logf("✓ Verified: Base IV without offset produces corrupted data (as expected for SSE-KMS)") + } +} + +// TestSSEDecryptionDifferences documents the key differences between SSE types +func TestSSEDecryptionDifferences(t *testing.T) { + t.Log("SSE-C: Random IV per part → Use stored IV DIRECTLY (no offset)") + t.Log("SSE-KMS: Base IV + offset → MUST call calculateIVWithOffset(baseIV, offset)") + t.Log("SSE-S3: Base IV + offset → Stores ADJUSTED IV, use directly") + + // This test documents the critical differences and serves as executable documentation +} |
