From 63fb13d4590b93524cbd4b0da3064684bb976741 Mon Sep 17 00:00:00 2001 From: Shubham Chaturvedi Date: Wed, 17 Jun 2026 15:55:03 -0700 Subject: [PATCH 1/6] fix: add v3HeaderSpoofingTests --- .../encryption/s3/V3HeaderSpoofingTests.java | 304 ++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java new file mode 100644 index 00000000..ed7c0bcb --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java @@ -0,0 +1,304 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import static software.amazon.encryption.s3.TestUtils.appendTestSuffix; +import software.amazon.encryption.s3.client.S3ECTestServerClient; +import software.amazon.encryption.s3.model.CommitmentPolicy; +import software.amazon.encryption.s3.model.CreateClientInput; +import software.amazon.encryption.s3.model.CreateClientOutput; +import software.amazon.encryption.s3.model.EncryptionAlgorithm; +import software.amazon.encryption.s3.model.KeyMaterial; +import software.amazon.encryption.s3.model.S3ECConfig; + +/** + * V3 Header Spoofing Tests + * + * This suite validates that S3 Encryption Client runtimes correctly reject decryption + * when V2 envelope headers (x-amz-key-v2, x-amz-cek-alg, x-amz-iv, x-amz-wrap-alg, + * x-amz-matdesc, x-amz-tag-len) are injected into V3-committed objects. + * + * This simulates a downgrade attack where an adversary injects fake V2 metadata to + * trick the client into the V2 decryption path, bypassing key commitment validation. + * + * Phases: + * 1. EncryptTests - Encrypts V3 committed objects, then injects spoofed V2 headers + * 2. DecryptTests - Waits for encrypt phase to complete, then verifies decryption rejection + * + * Coordination is achieved using a CountDownLatch that EncryptTests signals upon completion + * and DecryptTests awaits before proceeding. + */ +public class V3HeaderSpoofingTests { + // Synchronization latch - released when encrypt phase and header spoofing completes + private static final CountDownLatch encryptPhaseComplete = new CountDownLatch(1); + + // Suffix appended to create spoofed object keys + private static final String SUFFIX_SPOOFED = "-spoofed"; + + /** + * Encryption Tests - Encrypt Phase + * + * These tests encrypt V3 committed objects using KMS key material. + * After all encrypt tests complete, @AfterAll injects spoofed V2 headers + * onto the encrypted objects and signals the latch. + */ + @Nested + @DisplayName("V3HeaderSpoofingTests - Encrypt") + class EncryptTests { + private static final String sharedObjectKeyBase = "test-v3-spoof"; + private static KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + // Thread-safe list for storing encrypted V3 object keys + private static final List crossLanguageObjects = + Collections.synchronizedList(new ArrayList<>()); + + // Thread-safe list for storing spoofed object keys after manipulation + private static final List spoofedObjectKeys = + Collections.synchronizedList(new ArrayList<>()); + + /** + * Returns a defensive copy of the encrypted V3 object keys. + */ + static List getCrossLanguageObjects() { + return new ArrayList<>(crossLanguageObjects); + } + + /** + * Returns a defensive copy of the spoofed object keys. + */ + static List getSpoofedObjectKeys() { + return new ArrayList<>(spoofedObjectKeys); + } + + @ParameterizedTest(name = "{0}: Encrypt V3 committed object for spoofing test") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void encrypt_v3_committed(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt( + client, + S3ECId, + appendTestSuffix(sharedObjectKeyBase + "-" + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + /** + * Injects spoofed V2 headers onto each V3 committed object. + * Uses a plaintext S3 client to read each V3 object, add fake V2 envelope + * headers to the metadata, and upload a spoofed copy. + */ + static void spoofV2Headers() { + SecureRandom random = new SecureRandom(); + + try (S3Client ptS3Client = S3Client.create()) { + for (String objectKey : crossLanguageObjects) { + // Read the original V3 committed object + ResponseBytes encryptedObject = ptS3Client.getObjectAsBytes(builder -> builder + .bucket(TestUtils.BUCKET) + .key(objectKey) + .build()); + + Map originalMetadata = encryptedObject.response().metadata(); + + // Construct spoofed metadata: original V3 metadata + injected V2 headers + Map spoofedMetadata = new HashMap<>(originalMetadata); + + // Inject fake V2 envelope headers to simulate downgrade attack + byte[] fakeEdk = new byte[32]; + random.nextBytes(fakeEdk); + spoofedMetadata.put("x-amz-key-v2", Base64.getEncoder().encodeToString(fakeEdk)); + + spoofedMetadata.put("x-amz-cek-alg", "AES/GCM/NoPadding"); + + byte[] fakeIv = new byte[12]; + random.nextBytes(fakeIv); + spoofedMetadata.put("x-amz-iv", Base64.getEncoder().encodeToString(fakeIv)); + + spoofedMetadata.put("x-amz-wrap-alg", "kms+context"); + spoofedMetadata.put("x-amz-matdesc", "{}"); + spoofedMetadata.put("x-amz-tag-len", "128"); + + // Upload spoofed copy with original ciphertext body + String spoofedKey = objectKey + SUFFIX_SPOOFED; + ptS3Client.putObject(builder -> builder + .bucket(TestUtils.BUCKET) + .key(spoofedKey) + .metadata(spoofedMetadata) + .build(), + software.amazon.awssdk.core.sync.RequestBody.fromBytes(encryptedObject.asByteArray())); + + spoofedObjectKeys.add(spoofedKey); + } + } + } + + @AfterAll + static void signalEncryptionComplete() { + spoofV2Headers(); + + // Signal that all encryption tests and header spoofing have completed + encryptPhaseComplete.countDown(); + } + } + + /** + * Decryption Tests - Decrypt Phase + * + * These tests verify that all language servers reject decryption of spoofed objects + * (V3 committed objects with injected V2 headers) across all commitment policies. + * Also verifies that unmodified V3 objects still decrypt successfully. + */ + @Nested + @DisplayName("V3HeaderSpoofingTests - Decrypt") + class DecryptTests { + private static List spoofedObjectKeys; + private static List originalObjectKeys; + private static KeyMaterial kmsKeyArn; + + @BeforeAll + static void setup() throws InterruptedException { + // Wait for all encryption tests and header spoofing to complete + encryptPhaseComplete.await(); + + // Import object keys from the encrypt phase + spoofedObjectKeys = EncryptTests.getSpoofedObjectKeys(); + originalObjectKeys = EncryptTests.getCrossLanguageObjects(); + kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + // Verify we have objects to test + if (spoofedObjectKeys.isEmpty()) { + throw new IllegalStateException( + "No spoofed objects found. Ensure EncryptTests runs first and at least one server is available."); + } + if (originalObjectKeys.isEmpty()) { + throw new IllegalStateException( + "No original V3 objects found. Ensure EncryptTests runs first and at least one server is available."); + } + } + + /** + * Provides both improved and transition language servers for decrypt tests. + */ + public static Stream improvedAndTransitionClients() { + return Stream.concat( + TestUtils.improvedClientsForTest(), + TestUtils.transitionClientsForTest() + ); + } + + @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with REQUIRE_ENCRYPT_REQUIRE_DECRYPT") + @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") + void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + spoofedObjectKeys, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with REQUIRE_ENCRYPT_ALLOW_DECRYPT") + @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") + void reject_spoofed_require_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + spoofedObjectKeys, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with FORBID_ENCRYPT_ALLOW_DECRYPT") + @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") + void reject_spoofed_forbid_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + spoofedObjectKeys, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @ParameterizedTest(name = "{0}: Original V3 committed objects decrypt successfully") + @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") + void original_v3_decrypts_successfully(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(kmsKeyArn) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt( + client, + S3ECId, + originalObjectKeys, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + } +} From 788bcf297ca3a94948f980900499f6658eb98668 Mon Sep 17 00:00:00 2001 From: Shubham Chaturvedi Date: Wed, 17 Jun 2026 16:35:14 -0700 Subject: [PATCH 2/6] fix: ploicy --- .../software/amazon/encryption/s3/V3HeaderSpoofingTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java index ed7c0bcb..5c3cdfd5 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java @@ -229,6 +229,7 @@ void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarg CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) .build()) .build()); String S3ECId = clientOutput.getClientId(); From 5370b19bf11b0396a8e8bdd65afc659ee01e6f42 Mon Sep 17 00:00:00 2001 From: Shubham Chaturvedi Date: Thu, 18 Jun 2026 09:23:22 -0700 Subject: [PATCH 3/6] fix: tests --- .../software/amazon/encryption/s3/V3HeaderSpoofingTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java index 5c3cdfd5..deba3a25 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java @@ -223,7 +223,7 @@ public static Stream improvedAndTransitionClients() { } @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with REQUIRE_ENCRYPT_REQUIRE_DECRYPT") - @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -243,7 +243,7 @@ void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarg } @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with REQUIRE_ENCRYPT_ALLOW_DECRYPT") - @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") void reject_spoofed_require_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() From 854852bf9756ada5684e40a0a1409190229f2cb1 Mon Sep 17 00:00:00 2001 From: Shubham Chaturvedi Date: Thu, 18 Jun 2026 14:00:03 -0700 Subject: [PATCH 4/6] fix: tests --- .../encryption/s3/V3HeaderSpoofingTests.java | 131 ++++++++---------- 1 file changed, 58 insertions(+), 73 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java index deba3a25..a97470cd 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java @@ -38,71 +38,63 @@ /** * V3 Header Spoofing Tests * - * This suite validates that S3 Encryption Client runtimes correctly reject decryption - * when V2 envelope headers (x-amz-key-v2, x-amz-cek-alg, x-amz-iv, x-amz-wrap-alg, - * x-amz-matdesc, x-amz-tag-len) are injected into V3-committed objects. + * This suite validates that S3EC runtimes correctly reject decryption when V3 key + * commitment headers (x-amz-c, x-amz-d, x-amz-i) are injected into V2-encrypted objects. + * This simulates an upgrade spoofing attack where an adversary adds fake V3 commitment + * headers to a legitimate V2 object (which has NO real key commitment). * - * This simulates a downgrade attack where an adversary injects fake V2 metadata to - * trick the client into the V2 decryption path, bypassing key commitment validation. - * - * Phases: - * 1. EncryptTests - Encrypts V3 committed objects, then injects spoofed V2 headers - * 2. DecryptTests - Waits for encrypt phase to complete, then verifies decryption rejection + * Execution order: + * 1. EncryptTests - Encrypts V2 objects and injects spoofed V3 headers in @AfterAll + * 2. DecryptTests - Waits for encrypt phase, then verifies decryption rejection * * Coordination is achieved using a CountDownLatch that EncryptTests signals upon completion * and DecryptTests awaits before proceeding. */ public class V3HeaderSpoofingTests { - // Synchronization latch - released when encrypt phase and header spoofing completes + // Synchronization latch - released when encrypt phase completes private static final CountDownLatch encryptPhaseComplete = new CountDownLatch(1); // Suffix appended to create spoofed object keys - private static final String SUFFIX_SPOOFED = "-spoofed"; + private static final String SUFFIX_V3_SPOOFED = "-v3spoofed"; /** * Encryption Tests - Encrypt Phase * - * These tests encrypt V3 committed objects using KMS key material. - * After all encrypt tests complete, @AfterAll injects spoofed V2 headers - * onto the encrypted objects and signals the latch. + * These tests encrypt V2 objects (no key commitment) across improved language servers. + * After all encrypt tests complete, @AfterAll injects V3 headers into the V2 metadata + * and uploads spoofed copies to S3. */ @Nested @DisplayName("V3HeaderSpoofingTests - Encrypt") class EncryptTests { - private static final String sharedObjectKeyBase = "test-v3-spoof"; + private static final String sharedObjectKeyBase = "test-v3-header-spoof"; private static KeyMaterial kmsKeyArn = KeyMaterial.builder() .kmsKeyId(TestUtils.KMS_KEY_ARN) .build(); - // Thread-safe list for storing encrypted V3 object keys + // Thread-safe lists for storing object keys private static final List crossLanguageObjects = Collections.synchronizedList(new ArrayList<>()); - - // Thread-safe list for storing spoofed object keys after manipulation private static final List spoofedObjectKeys = Collections.synchronizedList(new ArrayList<>()); - /** - * Returns a defensive copy of the encrypted V3 object keys. - */ static List getCrossLanguageObjects() { return new ArrayList<>(crossLanguageObjects); } - /** - * Returns a defensive copy of the spoofed object keys. - */ static List getSpoofedObjectKeys() { return new ArrayList<>(spoofedObjectKeys); } - @ParameterizedTest(name = "{0}: Encrypt V3 committed object for spoofing test") + @ParameterizedTest(name = "{0}: Encrypt V2 object for V3 header spoofing test") @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void encrypt_v3_committed(TestUtils.LanguageServerTarget language) { + void encrypt_v2_object(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .build()) .build()); String S3ECId = clientOutput.getClientId(); @@ -112,21 +104,19 @@ void encrypt_v3_committed(TestUtils.LanguageServerTarget language) { S3ECId, appendTestSuffix(sharedObjectKeyBase + "-" + language.getLanguageName()), crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } /** - * Injects spoofed V2 headers onto each V3 committed object. - * Uses a plaintext S3 client to read each V3 object, add fake V2 envelope - * headers to the metadata, and upload a spoofed copy. + * Reads each V2 object, injects fake V3 key commitment headers into the + * existing V2 metadata, and uploads spoofed copies to S3. */ - static void spoofV2Headers() { + static void spoofV3Headers() { SecureRandom random = new SecureRandom(); - try (S3Client ptS3Client = S3Client.create()) { for (String objectKey : crossLanguageObjects) { - // Read the original V3 committed object + // Read the V2 encrypted object ResponseBytes encryptedObject = ptS3Client.getObjectAsBytes(builder -> builder .bucket(TestUtils.BUCKET) .key(objectKey) @@ -134,26 +124,22 @@ static void spoofV2Headers() { Map originalMetadata = encryptedObject.response().metadata(); - // Construct spoofed metadata: original V3 metadata + injected V2 headers + // Construct spoofed metadata: preserve all V2 headers and inject V3 headers Map spoofedMetadata = new HashMap<>(originalMetadata); - // Inject fake V2 envelope headers to simulate downgrade attack - byte[] fakeEdk = new byte[32]; - random.nextBytes(fakeEdk); - spoofedMetadata.put("x-amz-key-v2", Base64.getEncoder().encodeToString(fakeEdk)); - - spoofedMetadata.put("x-amz-cek-alg", "AES/GCM/NoPadding"); + // Generate random bytes for fake V3 commitment values + byte[] fakeKeyCommitment = new byte[32]; + random.nextBytes(fakeKeyCommitment); + byte[] fakeMessageId = new byte[28]; + random.nextBytes(fakeMessageId); - byte[] fakeIv = new byte[12]; - random.nextBytes(fakeIv); - spoofedMetadata.put("x-amz-iv", Base64.getEncoder().encodeToString(fakeIv)); + // Inject V3 headers + spoofedMetadata.put("x-amz-c", "115"); // V3 algorithm suite ID (KC-GCM) + spoofedMetadata.put("x-amz-d", Base64.getEncoder().encodeToString(fakeKeyCommitment)); // Fake key commitment + spoofedMetadata.put("x-amz-i", Base64.getEncoder().encodeToString(fakeMessageId)); // Fake message ID - spoofedMetadata.put("x-amz-wrap-alg", "kms+context"); - spoofedMetadata.put("x-amz-matdesc", "{}"); - spoofedMetadata.put("x-amz-tag-len", "128"); - - // Upload spoofed copy with original ciphertext body - String spoofedKey = objectKey + SUFFIX_SPOOFED; + // Upload spoofed copy with original ciphertext body and modified metadata + String spoofedKey = objectKey + SUFFIX_V3_SPOOFED; ptS3Client.putObject(builder -> builder .bucket(TestUtils.BUCKET) .key(spoofedKey) @@ -168,9 +154,9 @@ static void spoofV2Headers() { @AfterAll static void signalEncryptionComplete() { - spoofV2Headers(); + spoofV3Headers(); - // Signal that all encryption tests and header spoofing have completed + // Signal that all encryption tests and metadata manipulation have completed encryptPhaseComplete.countDown(); } } @@ -178,9 +164,11 @@ static void signalEncryptionComplete() { /** * Decryption Tests - Decrypt Phase * - * These tests verify that all language servers reject decryption of spoofed objects - * (V3 committed objects with injected V2 headers) across all commitment policies. - * Also verifies that unmodified V3 objects still decrypt successfully. + * These tests verify that all S3EC runtimes reject decryption of spoofed objects + * (V2 objects with injected V3 headers) across all commitment policies. + * A control test confirms unmodified V2 objects still decrypt successfully. + * + * Execution waits for EncryptTests to complete via CountDownLatch. */ @Nested @DisplayName("V3HeaderSpoofingTests - Decrypt") @@ -191,7 +179,7 @@ class DecryptTests { @BeforeAll static void setup() throws InterruptedException { - // Wait for all encryption tests and header spoofing to complete + // Wait for all encryption tests and metadata manipulation to complete encryptPhaseComplete.await(); // Import object keys from the encrypt phase @@ -201,20 +189,17 @@ static void setup() throws InterruptedException { .kmsKeyId(TestUtils.KMS_KEY_ARN) .build(); - // Verify we have objects to test + // Verify we have objects to decrypt if (spoofedObjectKeys.isEmpty()) { throw new IllegalStateException( - "No spoofed objects found. Ensure EncryptTests runs first and at least one server is available."); + "No spoofed objects found. Ensure EncryptTests runs first and spoofV3Headers() succeeds."); } if (originalObjectKeys.isEmpty()) { throw new IllegalStateException( - "No original V3 objects found. Ensure EncryptTests runs first and at least one server is available."); + "No original V2 objects found. Ensure EncryptTests runs first."); } } - /** - * Provides both improved and transition language servers for decrypt tests. - */ public static Stream improvedAndTransitionClients() { return Stream.concat( TestUtils.improvedClientsForTest(), @@ -222,8 +207,8 @@ public static Stream improvedAndTransitionClients() { ); } - @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with REQUIRE_ENCRYPT_REQUIRE_DECRYPT") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with REQUIRE_ENCRYPT_REQUIRE_DECRYPT") + @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -238,12 +223,12 @@ void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarg client, S3ECId, spoofedObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } - @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with REQUIRE_ENCRYPT_ALLOW_DECRYPT") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with REQUIRE_ENCRYPT_ALLOW_DECRYPT") + @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") void reject_spoofed_require_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -258,11 +243,11 @@ void reject_spoofed_require_encrypt_allow_decrypt(TestUtils.LanguageServerTarget client, S3ECId, spoofedObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } - @ParameterizedTest(name = "{0}: Reject spoofed V2 headers with FORBID_ENCRYPT_ALLOW_DECRYPT") + @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with FORBID_ENCRYPT_ALLOW_DECRYPT") @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") void reject_spoofed_forbid_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); @@ -270,7 +255,6 @@ void reject_spoofed_forbid_encrypt_allow_decrypt(TestUtils.LanguageServerTarget .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn) .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) - .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .build()) .build()); String S3ECId = clientOutput.getClientId(); @@ -279,17 +263,18 @@ void reject_spoofed_forbid_encrypt_allow_decrypt(TestUtils.LanguageServerTarget client, S3ECId, spoofedObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } - @ParameterizedTest(name = "{0}: Original V3 committed objects decrypt successfully") + @ParameterizedTest(name = "{0}: Original V2 objects decrypt successfully") @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") - void original_v3_decrypts_successfully(TestUtils.LanguageServerTarget language) { + void original_v2_decrypts_successfully(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) .build()) .build()); String S3ECId = clientOutput.getClientId(); @@ -298,7 +283,7 @@ void original_v3_decrypts_successfully(TestUtils.LanguageServerTarget language) client, S3ECId, originalObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } } From c9fb919e95b8e8829fa27f5534c4fb098c2a33f8 Mon Sep 17 00:00:00 2001 From: Shubham Chaturvedi Date: Thu, 18 Jun 2026 15:32:27 -0700 Subject: [PATCH 5/6] fix: tests --- .../encryption/s3/V3HeaderSpoofingTests.java | 273 ++++++------------ 1 file changed, 89 insertions(+), 184 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java index a97470cd..eb30d05e 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java @@ -5,74 +5,60 @@ package software.amazon.encryption.s3; -import java.security.SecureRandom; import java.util.ArrayList; -import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import static software.amazon.encryption.s3.TestUtils.Decrypt; +import static software.amazon.encryption.s3.TestUtils.Decrypt_fails; +import static software.amazon.encryption.s3.TestUtils.Encrypt; import static software.amazon.encryption.s3.TestUtils.appendTestSuffix; +import static software.amazon.encryption.s3.TestUtils.testServerClientFor; +import static software.amazon.encryption.s3.TestUtils.validateServersRunning; import software.amazon.encryption.s3.client.S3ECTestServerClient; import software.amazon.encryption.s3.model.CommitmentPolicy; import software.amazon.encryption.s3.model.CreateClientInput; -import software.amazon.encryption.s3.model.CreateClientOutput; import software.amazon.encryption.s3.model.EncryptionAlgorithm; import software.amazon.encryption.s3.model.KeyMaterial; import software.amazon.encryption.s3.model.S3ECConfig; /** - * V3 Header Spoofing Tests + * Validates that S3EC runtimes reject decryption when V3 key commitment headers + * (x-amz-c, x-amz-w) are injected into V2-encrypted objects, simulating an + * upgrade spoofing attack. * - * This suite validates that S3EC runtimes correctly reject decryption when V3 key - * commitment headers (x-amz-c, x-amz-d, x-amz-i) are injected into V2-encrypted objects. - * This simulates an upgrade spoofing attack where an adversary adds fake V3 commitment - * headers to a legitimate V2 object (which has NO real key commitment). - * - * Execution order: - * 1. EncryptTests - Encrypts V2 objects and injects spoofed V3 headers in @AfterAll - * 2. DecryptTests - Waits for encrypt phase, then verifies decryption rejection - * - * Coordination is achieved using a CountDownLatch that EncryptTests signals upon completion - * and DecryptTests awaits before proceeding. + * EncryptTests runs first (@Order(1)), encrypts V2 objects, then injects spoofed + * V3 headers in @AfterAll. DecryptTests (@Order(2)) verifies decryption rejection. */ +@TestClassOrder(ClassOrderer.OrderAnnotation.class) public class V3HeaderSpoofingTests { - // Synchronization latch - released when encrypt phase completes - private static final CountDownLatch encryptPhaseComplete = new CountDownLatch(1); - // Suffix appended to create spoofed object keys private static final String SUFFIX_V3_SPOOFED = "-v3spoofed"; + private static final KeyMaterial KMS_KEY = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); - /** - * Encryption Tests - Encrypt Phase - * - * These tests encrypt V2 objects (no key commitment) across improved language servers. - * After all encrypt tests complete, @AfterAll injects V3 headers into the V2 metadata - * and uploads spoofed copies to S3. - */ @Nested + @Order(1) @DisplayName("V3HeaderSpoofingTests - Encrypt") class EncryptTests { - private static final String sharedObjectKeyBase = "test-v3-header-spoof"; - private static KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - - // Thread-safe lists for storing object keys + private static final String SHARED_OBJECT_KEY_BASE = "test-v3-header-spoof"; private static final List crossLanguageObjects = Collections.synchronizedList(new ArrayList<>()); private static final List spoofedObjectKeys = @@ -86,205 +72,124 @@ static List getSpoofedObjectKeys() { return new ArrayList<>(spoofedObjectKeys); } - @ParameterizedTest(name = "{0}: Encrypt V2 object for V3 header spoofing test") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void encrypt_v2_object(TestUtils.LanguageServerTarget language) { - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn) - .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) - .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) - .build()) - .build()); - String S3ECId = clientOutput.getClientId(); + @BeforeAll + static void setup() { + validateServersRunning(); + } - TestUtils.Encrypt( + @ParameterizedTest(name = "{0}: Encrypt V2 object for V3 header spoofing test") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void encryptV2Object(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = testServerClientFor(language); + String clientId = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder().keyMaterial(KMS_KEY).build()) + .build()).getClientId(); + + Encrypt( client, - S3ECId, - appendTestSuffix(sharedObjectKeyBase + "-" + language.getLanguageName()), + clientId, + appendTestSuffix(SHARED_OBJECT_KEY_BASE + "-" + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } /** - * Reads each V2 object, injects fake V3 key commitment headers into the - * existing V2 metadata, and uploads spoofed copies to S3. + * Reads each V2 object, injects fake V3 key commitment headers, and uploads + * spoofed copies to S3. + * + * Attack vector: + * - x-amz-c ("115"): triggers IsV3Object()=true, bypassing the commitment policy gate + * - x-amz-w ("12"): valid V3 compressed wrap algorithm to prevent ExpandV3WrapAlgorithm crash */ + @AfterAll static void spoofV3Headers() { - SecureRandom random = new SecureRandom(); - try (S3Client ptS3Client = S3Client.create()) { + try (S3Client s3 = S3Client.create()) { for (String objectKey : crossLanguageObjects) { - // Read the V2 encrypted object - ResponseBytes encryptedObject = ptS3Client.getObjectAsBytes(builder -> builder + ResponseBytes encrypted = s3.getObjectAsBytes(b -> b .bucket(TestUtils.BUCKET) - .key(objectKey) - .build()); - - Map originalMetadata = encryptedObject.response().metadata(); - - // Construct spoofed metadata: preserve all V2 headers and inject V3 headers - Map spoofedMetadata = new HashMap<>(originalMetadata); - - // Generate random bytes for fake V3 commitment values - byte[] fakeKeyCommitment = new byte[32]; - random.nextBytes(fakeKeyCommitment); - byte[] fakeMessageId = new byte[28]; - random.nextBytes(fakeMessageId); + .key(objectKey)); - // Inject V3 headers - spoofedMetadata.put("x-amz-c", "115"); // V3 algorithm suite ID (KC-GCM) - spoofedMetadata.put("x-amz-d", Base64.getEncoder().encodeToString(fakeKeyCommitment)); // Fake key commitment - spoofedMetadata.put("x-amz-i", Base64.getEncoder().encodeToString(fakeMessageId)); // Fake message ID + Map spoofedMetadata = new HashMap<>(encrypted.response().metadata()); + spoofedMetadata.put("x-amz-c", "115"); + spoofedMetadata.put("x-amz-w", "12"); - // Upload spoofed copy with original ciphertext body and modified metadata String spoofedKey = objectKey + SUFFIX_V3_SPOOFED; - ptS3Client.putObject(builder -> builder - .bucket(TestUtils.BUCKET) - .key(spoofedKey) - .metadata(spoofedMetadata) - .build(), - software.amazon.awssdk.core.sync.RequestBody.fromBytes(encryptedObject.asByteArray())); + s3.putObject( + b -> b.bucket(TestUtils.BUCKET).key(spoofedKey).metadata(spoofedMetadata), + RequestBody.fromBytes(encrypted.asByteArray())); spoofedObjectKeys.add(spoofedKey); } } } - - @AfterAll - static void signalEncryptionComplete() { - spoofV3Headers(); - - // Signal that all encryption tests and metadata manipulation have completed - encryptPhaseComplete.countDown(); - } } - /** - * Decryption Tests - Decrypt Phase - * - * These tests verify that all S3EC runtimes reject decryption of spoofed objects - * (V2 objects with injected V3 headers) across all commitment policies. - * A control test confirms unmodified V2 objects still decrypt successfully. - * - * Execution waits for EncryptTests to complete via CountDownLatch. - */ @Nested + @Order(2) @DisplayName("V3HeaderSpoofingTests - Decrypt") class DecryptTests { private static List spoofedObjectKeys; private static List originalObjectKeys; - private static KeyMaterial kmsKeyArn; @BeforeAll - static void setup() throws InterruptedException { - // Wait for all encryption tests and metadata manipulation to complete - encryptPhaseComplete.await(); - - // Import object keys from the encrypt phase + static void setup() { spoofedObjectKeys = EncryptTests.getSpoofedObjectKeys(); originalObjectKeys = EncryptTests.getCrossLanguageObjects(); - kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - // Verify we have objects to decrypt if (spoofedObjectKeys.isEmpty()) { throw new IllegalStateException( - "No spoofed objects found. Ensure EncryptTests runs first and spoofV3Headers() succeeds."); + "No spoofed objects found. Ensure EncryptTests ran and spoofV3Headers() succeeded."); } if (originalObjectKeys.isEmpty()) { throw new IllegalStateException( - "No original V2 objects found. Ensure EncryptTests runs first."); + "No original V2 objects found. Ensure EncryptTests ran."); } } - public static Stream improvedAndTransitionClients() { - return Stream.concat( - TestUtils.improvedClientsForTest(), - TestUtils.transitionClientsForTest() - ); - } - @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with REQUIRE_ENCRYPT_REQUIRE_DECRYPT") - @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") - void reject_spoofed_require_encrypt_require_decrypt(TestUtils.LanguageServerTarget language) { - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) - .build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Decrypt_fails( - client, - S3ECId, - spoofedObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF - ); - } - - @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with REQUIRE_ENCRYPT_ALLOW_DECRYPT") - @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") - void reject_spoofed_require_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT) - .build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Decrypt_fails( - client, - S3ECId, - spoofedObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF - ); - } - - @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with FORBID_ENCRYPT_ALLOW_DECRYPT") - @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") - void reject_spoofed_forbid_encrypt_allow_decrypt(TestUtils.LanguageServerTarget language) { - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn) - .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) - .build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Decrypt_fails( - client, - S3ECId, + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void rejectSpoofedRequireEncryptRequireDecrypt(TestUtils.LanguageServerTarget language) { + String clientId = createClient(language, CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT, null); + + // Expected algorithm is V3 committed because spoofed x-amz-c makes + // GetEncryptionAlgorithm classify these as committed objects. + Decrypt_fails( + testServerClientFor(language), + clientId, spoofedObjectKeys, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY ); } @ParameterizedTest(name = "{0}: Original V2 objects decrypt successfully") - @MethodSource("software.amazon.encryption.s3.V3HeaderSpoofingTests$DecryptTests#improvedAndTransitionClients") - void original_v2_decrypts_successfully(TestUtils.LanguageServerTarget language) { - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .keyMaterial(kmsKeyArn) - .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) - .build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Decrypt( - client, - S3ECId, + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void originalV2DecryptsSuccessfully(TestUtils.LanguageServerTarget language) { + // REQUIRE_ENCRYPT_ALLOW_DECRYPT allows decrypting non-committed V2 objects + String clientId = createClient(language, + CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + + Decrypt( + testServerClientFor(language), + clientId, originalObjectKeys, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF ); } + + private String createClient(TestUtils.LanguageServerTarget language, + CommitmentPolicy policy, + EncryptionAlgorithm algorithm) { + S3ECConfig.Builder configBuilder = S3ECConfig.builder() + .keyMaterial(KMS_KEY) + .commitmentPolicy(policy); + if (algorithm != null) { + configBuilder.encryptionAlgorithm(algorithm); + } + return testServerClientFor(language) + .createClient(CreateClientInput.builder().config(configBuilder.build()).build()) + .getClientId(); + } } } From 62df0a4b20dbc3ea816d829f762e9f0a6add85d6 Mon Sep 17 00:00:00 2001 From: Shubham Chaturvedi Date: Thu, 18 Jun 2026 17:18:23 -0700 Subject: [PATCH 6/6] fix: tests --- .../encryption/s3/V3HeaderSpoofingTests.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java index eb30d05e..3a8494f7 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/V3HeaderSpoofingTests.java @@ -18,9 +18,12 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestClassOrder; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.TestAbortedException; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; @@ -47,6 +50,7 @@ * V3 headers in @AfterAll. DecryptTests (@Order(2)) verifies decryption rejection. */ @TestClassOrder(ClassOrderer.OrderAnnotation.class) +@Execution(ExecutionMode.SAME_THREAD) public class V3HeaderSpoofingTests { private static final String SUFFIX_V3_SPOOFED = "-v3spoofed"; @@ -78,11 +82,15 @@ static void setup() { } @ParameterizedTest(name = "{0}: Encrypt V2 object for V3 header spoofing test") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + @MethodSource("software.amazon.encryption.s3.TestUtils#clientsForTest") void encryptV2Object(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = testServerClientFor(language); String clientId = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder().keyMaterial(KMS_KEY).build()) + .config(S3ECConfig.builder() + .keyMaterial(KMS_KEY) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) + .build()) .build()).getClientId(); Encrypt( @@ -148,8 +156,12 @@ static void setup() { } @ParameterizedTest(name = "{0}: Reject spoofed V3 headers with REQUIRE_ENCRYPT_REQUIRE_DECRYPT") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + @MethodSource("software.amazon.encryption.s3.TestUtils#clientsForTest") void rejectSpoofedRequireEncryptRequireDecrypt(TestUtils.LanguageServerTarget language) { + if (!TestUtils.IMPROVED_VERSIONS.contains(language.getLanguageName())) { + throw new TestAbortedException( + "REQUIRE_ENCRYPT_REQUIRE_DECRYPT not supported by: " + language.getLanguageName()); + } String clientId = createClient(language, CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT, null); // Expected algorithm is V3 committed because spoofed x-amz-c makes @@ -163,11 +175,11 @@ void rejectSpoofedRequireEncryptRequireDecrypt(TestUtils.LanguageServerTarget la } @ParameterizedTest(name = "{0}: Original V2 objects decrypt successfully") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + @MethodSource("software.amazon.encryption.s3.TestUtils#clientsForTest") void originalV2DecryptsSuccessfully(TestUtils.LanguageServerTarget language) { - // REQUIRE_ENCRYPT_ALLOW_DECRYPT allows decrypting non-committed V2 objects + // FORBID_ENCRYPT_ALLOW_DECRYPT allows decrypting non-committed V2 objects String clientId = createClient(language, - CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT, + CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); Decrypt(