From 2b8f937d185c9e0481eb9fc2407e9a5634848a86 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Fri, 14 Nov 2025 13:54:27 -0800 Subject: [PATCH 01/36] ruby spec updates --- test-server/ruby-v2-server/.duvet/config.toml | 15 +++++++++++++-- test-server/ruby-v2-server/local-ruby-sdk | 2 +- test-server/ruby-v3-server/local-ruby-sdk | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/test-server/ruby-v2-server/.duvet/config.toml b/test-server/ruby-v2-server/.duvet/config.toml index 7118cd70..7a34c0ff 100644 --- a/test-server/ruby-v2-server/.duvet/config.toml +++ b/test-server/ruby-v2-server/.duvet/config.toml @@ -4,15 +4,26 @@ pattern = "local-ruby-sdk/gems/aws-sdk-s3/lib/**/*.rb" comment-style = { meta = "##=", content = "##%" } +[[source]] +pattern = "local-ruby-sdk/gems/aws-sdk-s3/spec/**/*.rb" +comment-style = { meta = "##=", content = "##%" } + # Include required specifications here [[specification]] -source = "../specification/s3-encryption/data-format/content-metadata.md" +source = "../specification/s3-encryption/client.md" [[specification]] -source = "../specification/s3-encryption/data-format/metadata-strategy.md" +source = "../specification/s3-encryption/decryption.md" [[specification]] source = "../specification/s3-encryption/encryption.md" [[specification]] +source = "../specification/s3-encryption/key-commitment.md" +[[specification]] source = "../specification/s3-encryption/key-derivation.md" +[[specification]] +source = "../specification/s3-encryption/data-format/content-metadata.md" +[[specification]] +source = "../specification/s3-encryption/data-format/metadata-strategy.md" + [report.html] enabled = true diff --git a/test-server/ruby-v2-server/local-ruby-sdk b/test-server/ruby-v2-server/local-ruby-sdk index 582e0241..d6b93925 160000 --- a/test-server/ruby-v2-server/local-ruby-sdk +++ b/test-server/ruby-v2-server/local-ruby-sdk @@ -1 +1 @@ -Subproject commit 582e02418ac2c5540c48dd089a7db506712e6f94 +Subproject commit d6b93925c65e0ad4b9410ade709c63ca874634c3 diff --git a/test-server/ruby-v3-server/local-ruby-sdk b/test-server/ruby-v3-server/local-ruby-sdk index 582e0241..d6b93925 160000 --- a/test-server/ruby-v3-server/local-ruby-sdk +++ b/test-server/ruby-v3-server/local-ruby-sdk @@ -1 +1 @@ -Subproject commit 582e02418ac2c5540c48dd089a7db506712e6f94 +Subproject commit d6b93925c65e0ad4b9410ade709c63ca874634c3 From d0f16e00b726d1f93a9c1da669d1a813dd246e4a Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Fri, 14 Nov 2025 16:57:08 -0800 Subject: [PATCH 02/36] Start on instruction failures --- .../s3/InstructionFileFailures.java | 329 ++++++++++++++++++ .../amazon/encryption/s3/TestUtils.java | 29 +- 2 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java new file mode 100644 index 00000000..0d458a3b --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java @@ -0,0 +1,329 @@ +/* +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* SPDX-License-Identifier: Apache-2.0 +*/ + +package software.amazon.encryption.s3; + +import static software.amazon.encryption.s3.TestUtils.*; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.opentest4j.TestAbortedException; + +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.encryption.s3.TestUtils.LanguageServerTarget; +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.InstructionFileConfig; +import software.amazon.encryption.s3.model.KeyMaterial; +import software.amazon.encryption.s3.model.S3ECConfig; + +/** +* Exhaustive tests for S3 Encryption Client round-trip operations. +* These tests cover various combinations of client versions, commitment policies, and encryption modes. +* +* Tests are based on the exhaustive test matrix defined at: +* https://tiny.amazon.com/3xnzwczl/loopcloumicrpeyJ3 +* +*/ + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class InstructionFileFailures { + private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; + private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; + private static KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + private static final List crossLanguageObjects = new ArrayList<>(); + private static KeyPair RSA_KEY_PAIR_1; + + @BeforeAll + static void setupKeys() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair(); + } + + public static Stream improvedClientsCanPutKMSWithInstructionFile() { + return improvedClientsForTest() + .filter(target -> !INSTRUCTION_FILE_PUT_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())) + .filter(target -> !KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); + } + + public static Stream clientsCanGetKMSWithInstructionFile() { + Stream improved = improvedClientsForTest() + .filter(target -> !KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); + + Stream transition = transitionClientsForTest() + .filter(target -> !KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); + + return Stream.concat(improved, transition); + } + + @Order(1) + @ParameterizedTest(name = "{0}: Encrypt KMS KC-GCM with instruction files") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#improvedClientsCanPutKMSWithInstructionFile") + void encrypt_with_instruction_files_kms_kc_gcm(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) + .instructionFileConfig( + InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build() + ) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt( + client, + S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(2) + @Test + void make_good_copies_to_verify_we_can() throws Exception { + // Create a plaintext S3 client to copy objects with instruction files + try (S3Client ptS3Client = S3Client.create()) { + for (String objectKey : crossLanguageObjects) { + // Get the encrypted object + ResponseBytes encryptedObject = ptS3Client.getObjectAsBytes(builder -> builder + .bucket(TestUtils.BUCKET) + .key(objectKey) + .build()); + + // Get the instruction file + String instructionFileKey = objectKey + ".instruction"; + ResponseBytes instructionFile = ptS3Client.getObjectAsBytes(builder -> builder + .bucket(TestUtils.BUCKET) + .key(instructionFileKey) + .build()); + + String instructionFileJson = instructionFile.asUtf8String(); + Map objectMetadata = encryptedObject.response().metadata(); + + putObjectWithInstructionFile( + ptS3Client, + objectKey + "-good-copy", + encryptedObject.asByteArray(), + objectMetadata, + instructionFileJson + ); + + ObjectMapper mapper = new ObjectMapper(); + Map instructionFileMap = mapper.readValue(instructionFileJson, Map.class); + + instructionFileMap.put("x-amz-c", objectMetadata.get("x-amz-c")); + instructionFileMap.put("x-amz-matdesc", objectMetadata.get("x-amz-matdesc")); + + String instructionFileWithCommitmentValues = mapper.writeValueAsString(instructionFileMap); + + putObjectWithInstructionFile( + ptS3Client, + objectKey + "-bad-both-meta-and-instruction", + encryptedObject.asByteArray(), + objectMetadata, + instructionFileWithCommitmentValues + ); + + putObjectWithInstructionFile( + ptS3Client, + objectKey + "-bad-only-instruction", + encryptedObject.asByteArray(), + Map.of(), + instructionFileWithCommitmentValues + ); + + + } + } + } + + void putObjectWithInstructionFile( + S3Client ptS3Client, + String newObjectKey, + byte[] objectData, + Map objectMetadata, + String instructionFileJson + ) { + + // Put the encrypted object copy + ptS3Client.putObject(builder -> builder + .bucket(TestUtils.BUCKET) + .key(newObjectKey) + .metadata(objectMetadata) + .build(), + software.amazon.awssdk.core.sync.RequestBody.fromBytes(objectData)); + + // Put the instruction file copy + ptS3Client.putObject(builder -> builder + .bucket(TestUtils.BUCKET) + .key(newObjectKey + ".instruction") + .build(), + software.amazon.awssdk.core.sync.RequestBody.fromBytes(instructionFileJson.getBytes(java.nio.charset.StandardCharsets.UTF_8))); + } + + + @Order(10) + @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_original_and_good_copy_objects_succeeds(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, + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + + TestUtils.Decrypt( + client, + S3ECId, + crossLanguageObjects + .stream() + .map(key -> key + "-good-copy") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY, + crossLanguageObjects + ); + } + + @Order(11) + @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is duplicated in metadata and instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(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, + crossLanguageObjects + .stream() + .map(key -> key + "-bad-both-meta-and-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(12) + @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is only in instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_commitment_only_in_instruction_file_fails(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, + crossLanguageObjects + .stream() + .map(key -> key + "-bad-only-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(13) + @ParameterizedTest(name = "{0}: Fail to decrypt duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_duplicate_commitment_fails_with_forbid_policy(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, + crossLanguageObjects + .stream() + .map(key -> key + "-bad-both-meta-and-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(14) + @ParameterizedTest(name = "{0}: Fail to decrypt instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(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, + crossLanguageObjects + .stream() + .map(key -> key + "-bad-only-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index d3d25d58..294f128b 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -492,18 +492,33 @@ public static void Encrypt( public static void Decrypt( S3ECTestServerClient client, - String S3ECId, List crossLanguageObjects, + String S3ECId, + List crossLanguageObjects, EncryptionAlgorithm expectedEncryptionAlgorithm ) { - for (String objectKey : crossLanguageObjects) { + // Call 5-parameter version with crossLanguageObjects as expectedPlaintexts + Decrypt(client, S3ECId, crossLanguageObjects, expectedEncryptionAlgorithm, crossLanguageObjects); + } + + public static void Decrypt( + S3ECTestServerClient client, + String S3ECId, + List crossLanguageObjects, + EncryptionAlgorithm expectedEncryptionAlgorithm, + List expectedPlaintexts + ) { + for (int i = 0; i < crossLanguageObjects.size(); i++) { + String objectKey = crossLanguageObjects.get(i); + String expectedPlaintext = expectedPlaintexts.get(i); + GetObjectOutput output = client.getObject(GetObjectInput.builder() - .clientID(S3ECId) - .bucket(TestUtils.BUCKET) - .key(objectKey) - .build()); + .clientID(S3ECId) + .bucket(TestUtils.BUCKET) + .key(objectKey) + .build()); // Then: Pass - assertEquals(objectKey, new String(output.getBody().array())); + assertEquals(expectedPlaintext, new String(output.getBody().array())); assertEquals( expectedEncryptionAlgorithm, GetEncryptionAlgorithm(objectKey), From 496a8db14fc55fce16fa5787dc96a7b58f2f8ad9 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Sun, 16 Nov 2025 16:31:08 -0800 Subject: [PATCH 03/36] Instruction files that pass and fail --- .../amazon/encryption/s3/TestUtils.java | 60 +++++++++++++++---- test-server/ruby-v2-server/app.rb | 4 +- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index 294f128b..dc07a2d4 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -17,10 +17,14 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.amazonaws.services.s3.model.S3Object; +import com.fasterxml.jackson.databind.ObjectMapper; + import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import com.amazonaws.services.s3.model.ObjectMetadata; @@ -449,22 +453,58 @@ public static String appendTestSuffix(final String s) { private static AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient(); public static EncryptionAlgorithm GetEncryptionAlgorithm(String objectKey) { + // Lambda to determine encryption algorithm from a metadata map + java.util.function.Function, Optional> getAlgorithmFromMap = (map) -> { + if (map.containsKey("x-amz-c")) { + return Optional.of(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } else if (map.containsKey("x-amz-cek-alg")) { + String cek = (String) map.get("x-amz-cek-alg"); + if (cek.contains("CBC")) { + return Optional.of(EncryptionAlgorithm.ALG_AES_256_CBC_IV16_NO_KDF); + } else if (cek.contains("GCM")) { + return Optional.of(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + } + return Optional.empty(); + }; + ObjectMetadata metadata = s3Client.getObjectMetadata(TestUtils.BUCKET, objectKey); Map userMetadata = metadata.getUserMetadata(); - // This is optimized to not need to go to the instruction files for commit_key - if (userMetadata.containsKey("x-amz-c")) { - return EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY; - } else if (userMetadata.containsKey("x-amz-cek-alg")) { - String cek = userMetadata.get("x-amz-cek-alg"); - if (cek.contains("CBC")) { - return EncryptionAlgorithm.ALG_AES_256_CBC_IV16_NO_KDF; - } else if (cek.contains("GCM")) { - return EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF; + // Try to get algorithm from object metadata + Optional algorithm = getAlgorithmFromMap.apply(userMetadata); + if (algorithm.isPresent()) { + return algorithm.get(); + } + + // Check instruction file + try { + String instructionFileKey = objectKey + ".instruction"; + com.amazonaws.services.s3.model.S3Object instructionFileObject = + s3Client.getObject(TestUtils.BUCKET, instructionFileKey); + + // Read instruction file content + java.io.InputStream inputStream = instructionFileObject.getObjectContent(); + String instructionFileJson = new String( + inputStream.readAllBytes(), + java.nio.charset.StandardCharsets.UTF_8 + ); + inputStream.close(); + + // Parse JSON to get metadata + com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + Map instructionFileMap = mapper.readValue(instructionFileJson, Map.class); + + // Try to get algorithm from instruction file + algorithm = getAlgorithmFromMap.apply(instructionFileMap); + if (algorithm.isPresent()) { + return algorithm.get(); } + } catch (Exception e) { + // Instruction file doesn't exist or couldn't be read } - throw new RuntimeException("Need to support instruction files!"); + throw new RuntimeException("Could not determine encryption algorithm from object metadata or instruction file!"); } public static void Encrypt( diff --git a/test-server/ruby-v2-server/app.rb b/test-server/ruby-v2-server/app.rb index cde757a3..5a39e2ea 100644 --- a/test-server/ruby-v2-server/app.rb +++ b/test-server/ruby-v2-server/app.rb @@ -132,7 +132,7 @@ def initialize metadata: response_metadata }.to_json - rescue Aws::S3::EncryptionV2::Errors::EncryptionError => e + rescue Aws::S3::EncryptionV2::Errors::EncryptionError, Aws::S3::EncryptionV3::Errors::EncryptionError => e S3ECLogger.log_error(e, { endpoint: '/put', error_category: 'EncryptionError' }, @request_id) ErrorHandlers.send_s3_encryption_client_error(self, e.message) rescue StandardError => e @@ -201,7 +201,7 @@ def initialize content_type 'application/octet-stream' body - rescue Aws::S3::EncryptionV2::Errors::DecryptionError => e + rescue Aws::S3::EncryptionV2::Errors::DecryptionError, Aws::S3::EncryptionV3::Errors::DecryptionError => e S3ECLogger.log_error(e, { endpoint: '/get', error_category: 'DecryptionError' }, @request_id) ErrorHandlers.send_s3_encryption_client_error(self, e.message) rescue StandardError => e From 54853fafd056411fcaf3369d81f24cbba413ca33 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 10:19:54 -0800 Subject: [PATCH 04/36] starting to add raw --- .../s3/InstructionFileFailures.java | 132 +++++++++++++++--- 1 file changed, 113 insertions(+), 19 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java index 0d458a3b..aa00abac 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java @@ -17,6 +17,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -54,19 +57,34 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class InstructionFileFailures { - private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; - private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; + private static final String sharedObjectKeyBaseMetaDataMode = "test-instruction-files-cases"; private static KeyMaterial kmsKeyArn = KeyMaterial.builder() .kmsKeyId(TestUtils.KMS_KEY_ARN) .build(); - private static final List crossLanguageObjects = new ArrayList<>(); - private static KeyPair RSA_KEY_PAIR_1; + private static final List crossLanguageObjectsKms = new ArrayList<>(); + private static final List crossLanguageObjectsRsa = new ArrayList<>(); + private static final List crossLanguageObjectsAes = new ArrayList<>(); + + private static KeyMaterial RSA_KEY; + private static KeyMaterial AES_KEY; @BeforeAll static void setupKeys() throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(2048); - RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair(); + KeyPair keyPair = keyPairGen.generateKeyPair(); + + RSA_KEY = KeyMaterial.builder() + .rsaKey(ByteBuffer.wrap(keyPair.getPrivate().getEncoded())) + .build(); + + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + SecretKey aesSecretKey = keyGen.generateKey(); + + AES_KEY = KeyMaterial.builder() + .aesKey(ByteBuffer.wrap(aesSecretKey.getEncoded())) + .build(); } public static Stream improvedClientsCanPutKMSWithInstructionFile() { @@ -75,6 +93,12 @@ public static Stream improvedClientsCanPutKMSWithInstructionFile() { .filter(target -> !KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); } + public static Stream improvedClientsCanPutRawWithInstructionFile() { + return improvedClientsForTest() + .filter(target -> !INSTRUCTION_FILE_PUT_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())) + .filter(target -> RAW_SUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); + } + public static Stream clientsCanGetKMSWithInstructionFile() { Stream improved = improvedClientsForTest() .filter(target -> !KMS_INSTRUCTION_FILE_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); @@ -85,6 +109,16 @@ public static Stream clientsCanGetKMSWithInstructionFile() { return Stream.concat(improved, transition); } + public static Stream clientsCanGetRawWithInstructionFile() { + Stream improved = improvedClientsForTest() + .filter(target -> RAW_SUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); + + Stream transition = transitionClientsForTest() + .filter(target -> RAW_SUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())); + + return Stream.concat(improved, transition); + } + @Order(1) @ParameterizedTest(name = "{0}: Encrypt KMS KC-GCM with instruction files") @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#improvedClientsCanPutKMSWithInstructionFile") @@ -93,7 +127,6 @@ void encrypt_with_instruction_files_kms_kc_gcm(TestUtils.LanguageServerTarget la CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() .config(S3ECConfig.builder() .keyMaterial(kmsKeyArn) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT) .instructionFileConfig( InstructionFileConfig.builder() .enableInstructionFilePutObject(true) @@ -106,18 +139,77 @@ void encrypt_with_instruction_files_kms_kc_gcm(TestUtils.LanguageServerTarget la TestUtils.Encrypt( client, S3ECId, - appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), - crossLanguageObjects, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + "-kms" + language.getLanguageName()), + crossLanguageObjectsKms, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY ); } @Order(2) + @ParameterizedTest(name = "{0}: Encrypt RSA KC-GCM with instruction files") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#improvedClientsCanPutRawWithInstructionFile") + void encrypt_with_instruction_files_rsa_kc_gcm(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .instructionFileConfig( + InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build() + ) + .build()) + .build()); + + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt( + client, + S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + "-rsa" + language.getLanguageName()), + crossLanguageObjectsRsa, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(3) + @ParameterizedTest(name = "{0}: Encrypt AES KC-GCM with instruction files") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#improvedClientsCanPutRawWithInstructionFile") + void encrypt_with_instruction_files_aes_kc_gcm(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(AES_KEY) + .instructionFileConfig( + InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build() + ) + .build()) + .build()); + + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt( + client, + S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + "-aes" + language.getLanguageName()), + crossLanguageObjectsRsa, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(9) @Test - void make_good_copies_to_verify_we_can() throws Exception { + void make_copies_to_verify_things() throws Exception { // Create a plaintext S3 client to copy objects with instruction files try (S3Client ptS3Client = S3Client.create()) { - for (String objectKey : crossLanguageObjects) { + List allCrossLanguageObjects = Stream.of( + crossLanguageObjectsKms.stream(), + crossLanguageObjectsRsa.stream(), + crossLanguageObjectsAes.stream() + ).flatMap(s -> s).collect(Collectors.toList()); + for (String objectKey : allCrossLanguageObjects) { // Get the encrypted object ResponseBytes encryptedObject = ptS3Client.getObjectAsBytes(builder -> builder .bucket(TestUtils.BUCKET) @@ -134,6 +226,7 @@ void make_good_copies_to_verify_we_can() throws Exception { String instructionFileJson = instructionFile.asUtf8String(); Map objectMetadata = encryptedObject.response().metadata(); + // Put a strict copy, to verify that we know how to do this putObjectWithInstructionFile( ptS3Client, objectKey + "-good-copy", @@ -146,10 +239,12 @@ void make_good_copies_to_verify_we_can() throws Exception { Map instructionFileMap = mapper.readValue(instructionFileJson, Map.class); instructionFileMap.put("x-amz-c", objectMetadata.get("x-amz-c")); - instructionFileMap.put("x-amz-matdesc", objectMetadata.get("x-amz-matdesc")); + instructionFileMap.put("x-amz-d", objectMetadata.get("x-amz-d")); + instructionFileMap.put("x-amz-i", objectMetadata.get("x-amz-i")); String instructionFileWithCommitmentValues = mapper.writeValueAsString(instructionFileMap); + // Put instruction files that should fail: putObjectWithInstructionFile( ptS3Client, objectKey + "-bad-both-meta-and-instruction", @@ -166,7 +261,6 @@ void make_good_copies_to_verify_we_can() throws Exception { instructionFileWithCommitmentValues ); - } } } @@ -212,19 +306,19 @@ void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTar TestUtils.Decrypt( client, S3ECId, - crossLanguageObjects, + crossLanguageObjectsKms, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY ); TestUtils.Decrypt( client, S3ECId, - crossLanguageObjects + crossLanguageObjectsKms .stream() .map(key -> key + "-good-copy") .collect(Collectors.toList()), EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY, - crossLanguageObjects + crossLanguageObjectsKms ); } @@ -244,7 +338,7 @@ void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUti TestUtils.Decrypt_fails( client, S3ECId, - crossLanguageObjects + crossLanguageObjectsKms .stream() .map(key -> key + "-bad-both-meta-and-instruction") .collect(Collectors.toList()), @@ -268,7 +362,7 @@ void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageSe TestUtils.Decrypt_fails( client, S3ECId, - crossLanguageObjects + crossLanguageObjectsKms .stream() .map(key -> key + "-bad-only-instruction") .collect(Collectors.toList()), @@ -293,7 +387,7 @@ void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.Langua TestUtils.Decrypt_fails( client, S3ECId, - crossLanguageObjects + crossLanguageObjectsKms .stream() .map(key -> key + "-bad-both-meta-and-instruction") .collect(Collectors.toList()), @@ -318,7 +412,7 @@ void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils TestUtils.Decrypt_fails( client, S3ECId, - crossLanguageObjects + crossLanguageObjectsKms .stream() .map(key -> key + "-bad-only-instruction") .collect(Collectors.toList()), From f0aa6f7845fc7c6a10da473371a414784670fdae Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 10:56:41 -0800 Subject: [PATCH 05/36] adding ruby to raw support --- .../s3/InstructionFileFailures.java | 268 +++++++++++++++++- .../amazon/encryption/s3/TestUtils.java | 1 + .../ruby-v2-server/lib/client_manager.rb | 36 ++- .../ruby-v3-server/lib/client_manager.rb | 38 ++- 4 files changed, 329 insertions(+), 14 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java index aa00abac..5456d344 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java @@ -194,7 +194,7 @@ void encrypt_with_instruction_files_aes_kc_gcm(TestUtils.LanguageServerTarget la client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + "-aes" + language.getLanguageName()), - crossLanguageObjectsRsa, + crossLanguageObjectsAes, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY ); } @@ -289,6 +289,7 @@ void putObjectWithInstructionFile( software.amazon.awssdk.core.sync.RequestBody.fromBytes(instructionFileJson.getBytes(java.nio.charset.StandardCharsets.UTF_8))); } + // KMS instruction files decrypt @Order(10) @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") @@ -420,4 +421,269 @@ void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils ); } + // RSA instruction file decrypt + + @Order(20) + @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt( + client, + S3ECId, + crossLanguageObjectsRsa, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + + TestUtils.Decrypt( + client, + S3ECId, + crossLanguageObjectsRsa + .stream() + .map(key -> key + "-good-copy") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY, + crossLanguageObjectsRsa + ); + } + + @Order(21) + @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is duplicated in metadata and instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsRsa + .stream() + .map(key -> key + "-bad-both-meta-and-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(22) + @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is only in instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsRsa + .stream() + .map(key -> key + "-bad-only-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(23) + @ParameterizedTest(name = "{0}: Fail to decrypt duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsRsa + .stream() + .map(key -> key + "-bad-both-meta-and-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(24) + @ParameterizedTest(name = "{0}: Fail to decrypt instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsRsa + .stream() + .map(key -> key + "-bad-only-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + // AES instruction file decrypt + + @Order(30) + @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(AES_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt( + client, + S3ECId, + crossLanguageObjectsAes, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + + TestUtils.Decrypt( + client, + S3ECId, + crossLanguageObjectsAes + .stream() + .map(key -> key + "-good-copy") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY, + crossLanguageObjectsAes + ); + } + + @Order(31) + @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is duplicated in metadata and instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(AES_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsAes + .stream() + .map(key -> key + "-bad-both-meta-and-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(32) + @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is only in instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(AES_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsAes + .stream() + .map(key -> key + "-bad-only-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(33) + @ParameterizedTest(name = "{0}: Fail to decrypt duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(AES_KEY) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsAes + .stream() + .map(key -> key + "-bad-both-meta-and-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @Order(34) + @ParameterizedTest(name = "{0}: Fail to decrypt instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") + void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(AES_KEY) + .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsAes + .stream() + .map(key -> key + "-bad-only-instruction") + .collect(Collectors.toList()), + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + } diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index dc07a2d4..90995dde 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -103,6 +103,7 @@ public class TestUtils { public static final Set RAW_SUPPORTED = Set.of(JAVA_V3_CURRENT, JAVA_V3_TRANSITION, JAVA_V4 , NET_V2_CURRENT, NET_V3_CURRENT, NET_V3_TRANSITION, NET_V4 + , RUBY_V2_TRANSITION, RUBY_V3 ); // .NET only supports decrypting instruction files using AES and RSA. diff --git a/test-server/ruby-v2-server/lib/client_manager.rb b/test-server/ruby-v2-server/lib/client_manager.rb index 717003bf..85a91038 100644 --- a/test-server/ruby-v2-server/lib/client_manager.rb +++ b/test-server/ruby-v2-server/lib/client_manager.rb @@ -2,6 +2,8 @@ require 'securerandom' require 'aws-sdk-s3' require 'aws-sdk-kms' +require 'openssl' +require 'base64' require_relative 'logger' # Manages S3 Encryption Client instances @@ -14,20 +16,42 @@ def initialize # Create a new S3 encryption client and return its ID def create_client(config) - # Extract configuration + # Extract all key material types kms_key_id = config.dig('keyMaterial', 'kmsKeyId') + rsa_key_blob = config.dig('keyMaterial', 'rsaKey') + aes_key_blob = config.dig('keyMaterial', 'aesKey') inst_file_put = config.dig('instructionFileConfig', 'enableInstructionFilePutObject') - raise 'KMS Key ID is required' if kms_key_id.nil? || kms_key_id.empty? + # Validate that only one key type is provided + key_count = [kms_key_id, rsa_key_blob, aes_key_blob].compact.count + raise 'KeyMaterial must contain exactly one non-null key type' unless key_count == 1 # Create S3 encryption client configuration encryption_config = { - kms_key_id: kms_key_id, - kms_client: @kms_client, - key_wrap_schema: :kms_context, content_encryption_schema: :aes_gcm_no_padding, envelope_location: inst_file_put ? :instruction_file : :metadata - }.tap do |hash| + } + + # Configure based on key type + if kms_key_id + encryption_config[:kms_key_id] = kms_key_id + encryption_config[:kms_client] = @kms_client + encryption_config[:key_wrap_schema] = :kms_context + elsif rsa_key_blob + # Parse RSA private key from PKCS8 format + key_bytes = Base64.decode64(rsa_key_blob) + rsa_key = OpenSSL::PKey::RSA.new(key_bytes) + encryption_config[:encryption_key] = rsa_key + encryption_config[:key_wrap_schema] = :rsa_oaep_sha1 + elsif aes_key_blob + # Extract AES key bytes + key_bytes = Base64.decode64(aes_key_blob) + encryption_config[:encryption_key] = key_bytes + encryption_config[:key_wrap_schema] = :aes_gcm + end + + # Apply legacy settings + encryption_config.tap do |hash| if !config['enableLegacyWrappingAlgorithms'].nil? || !config['enableLegacyUnauthenticatedModes'].nil? legacy_modes = config['enableLegacyWrappingAlgorithms'] || config['enableLegacyUnauthenticatedModes'] # Set security profile based on legacy wrapping algorithms setting diff --git a/test-server/ruby-v3-server/lib/client_manager.rb b/test-server/ruby-v3-server/lib/client_manager.rb index a6fb551f..115183b2 100644 --- a/test-server/ruby-v3-server/lib/client_manager.rb +++ b/test-server/ruby-v3-server/lib/client_manager.rb @@ -2,6 +2,8 @@ require 'securerandom' require 'aws-sdk-s3' require 'aws-sdk-kms' +require 'openssl' +require 'base64' require_relative 'logger' # Manages S3 Encryption Client instances @@ -14,11 +16,17 @@ def initialize # Create a new S3 encryption client and return its ID def create_client(config) - # Extract configuration + # Extract all key material types kms_key_id = config.dig('keyMaterial', 'kmsKeyId') + rsa_key_blob = config.dig('keyMaterial', 'rsaKey') + aes_key_blob = config.dig('keyMaterial', 'aesKey') inst_file_put = config.dig('instructionFileConfig', 'enableInstructionFilePutObject') content_alg = config.dig('encryptionAlgorithm') + # Validate that only one key type is provided + key_count = [kms_key_id, rsa_key_blob, aes_key_blob].compact.count + raise 'KeyMaterial must contain exactly one non-null key type' unless key_count == 1 + # translate between canonical AlgSuite and Ruby symbols if content_alg.nil? || content_alg == 'ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY' content_alg = :alg_aes_256_gcm_hkdf_sha512_commit_key @@ -28,16 +36,32 @@ def create_client(config) raise 'Unknown content encryption algorithm provided: ' + content_alg end - raise 'KMS Key ID is required' if kms_key_id.nil? || kms_key_id.empty? - # Create S3 encryption client configuration encryption_config = { - kms_key_id: kms_key_id, - kms_client: @kms_client, - key_wrap_schema: :kms_context, envelope_location: inst_file_put ? :instruction_file : :metadata, content_encryption_schema: content_alg - }.tap do |hash| + } + + # Configure based on key type + if kms_key_id + encryption_config[:kms_key_id] = kms_key_id + encryption_config[:kms_client] = @kms_client + encryption_config[:key_wrap_schema] = :kms_context + elsif rsa_key_blob + # Parse RSA private key from PKCS8 format + key_bytes = Base64.decode64(rsa_key_blob) + rsa_key = OpenSSL::PKey::RSA.new(key_bytes) + encryption_config[:encryption_key] = rsa_key + encryption_config[:key_wrap_schema] = :rsa_oaep_sha1 + elsif aes_key_blob + # Extract AES key bytes + key_bytes = Base64.decode64(aes_key_blob) + encryption_config[:encryption_key] = key_bytes + encryption_config[:key_wrap_schema] = :aes_gcm + end + + # Apply additional configuration + encryption_config.tap do |hash| if !config['commitmentPolicy'].nil? hash[:commitment_policy] = case config['commitmentPolicy'] when 'FORBID_ENCRYPT_ALLOW_DECRYPT' From 7c4ddb868d8a36bb0613e2cc9f697e97c85c345f Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 11:01:01 -0800 Subject: [PATCH 06/36] update names --- .../s3/InstructionFileFailures.java | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java index 5456d344..0433366b 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java @@ -292,9 +292,9 @@ void putObjectWithInstructionFile( // KMS instruction files decrypt @Order(10) - @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") + @ParameterizedTest(name = "{0}: Successfully decrypt KMS encrypted original and good-copy objects") @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { + void decrypt_kms_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -324,9 +324,9 @@ void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTar } @Order(11) - @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is duplicated in metadata and instruction file") + @ParameterizedTest(name = "{0}: Fail to decrypt KMS when commitment is duplicated in metadata and instruction file") @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { + void decrypt_kms_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -348,9 +348,9 @@ void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUti } @Order(12) - @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is only in instruction file") + @ParameterizedTest(name = "{0}: Fail to decrypt KMS when commitment is only in instruction file") @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { + void decrypt_kms_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -372,9 +372,9 @@ void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageSe } @Order(13) - @ParameterizedTest(name = "{0}: Fail to decrypt duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @ParameterizedTest(name = "{0}: Fail to decrypt KMS duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + void decrypt_kms_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -397,9 +397,9 @@ void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.Langua } @Order(14) - @ParameterizedTest(name = "{0}: Fail to decrypt instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @ParameterizedTest(name = "{0}: Fail to decrypt KMS instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + void decrypt_kms_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -424,9 +424,9 @@ void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils // RSA instruction file decrypt @Order(20) - @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Successfully decrypt RSA encrypted original and good-copy objects") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_rsa_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -456,9 +456,9 @@ void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTar } @Order(21) - @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is duplicated in metadata and instruction file") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt RSA when commitment is duplicated in metadata and instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_rsa_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -480,9 +480,9 @@ void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUti } @Order(22) - @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is only in instruction file") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt RSA when commitment is only in instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_rsa_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -504,9 +504,9 @@ void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageSe } @Order(23) - @ParameterizedTest(name = "{0}: Fail to decrypt duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt RSA duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_rsa_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -529,9 +529,9 @@ void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.Langua } @Order(24) - @ParameterizedTest(name = "{0}: Fail to decrypt instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt RSA instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_rsa_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -556,9 +556,9 @@ void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils // AES instruction file decrypt @Order(30) - @ParameterizedTest(name = "{0}: Successfully decrypt original and good-copy objects") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Successfully decrypt AES encrypted original and good-copy objects") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_aes_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -588,9 +588,9 @@ void decrypt_original_and_good_copy_objects_succeeds(TestUtils.LanguageServerTar } @Order(31) - @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is duplicated in metadata and instruction file") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt AES when commitment is duplicated in metadata and instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_aes_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -612,9 +612,9 @@ void decrypt_with_duplicate_commitment_in_metadata_and_instruction_fails(TestUti } @Order(32) - @ParameterizedTest(name = "{0}: Fail to decrypt when commitment is only in instruction file") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt AES when commitment is only in instruction file") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_aes_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -636,9 +636,9 @@ void decrypt_with_commitment_only_in_instruction_file_fails(TestUtils.LanguageSe } @Order(33) - @ParameterizedTest(name = "{0}: Fail to decrypt duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt AES duplicate commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_aes_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() @@ -661,9 +661,9 @@ void decrypt_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.Langua } @Order(34) - @ParameterizedTest(name = "{0}: Fail to decrypt instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") - @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetKMSWithInstructionFile") - void decrypt_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { + @ParameterizedTest(name = "{0}: Fail to decrypt AES instruction file commitment with FORBID_ENCRYPT_ALLOW_DECRYPT policy") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures#clientsCanGetRawWithInstructionFile") + void decrypt_aes_with_instruction_file_commitment_fails_with_forbid_policy(TestUtils.LanguageServerTarget language) { S3ECTestServerClient client = TestUtils.testServerClientFor(language); CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() From aeb359c08f62596659fa6fff67d4da04390584b5 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 11:15:45 -0800 Subject: [PATCH 07/36] speed up the build --- test-server/Makefile | 2 +- test-server/java-v3-server/Makefile | 2 +- test-server/java-v3-transition-server/Makefile | 2 +- test-server/java-v4-server/Makefile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test-server/Makefile b/test-server/Makefile index 9b18b857..255323a1 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -82,7 +82,7 @@ run-tests: AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY" \ AWS_SESSION_TOKEN="$$AWS_SESSION_TOKEN" \ AWS_REGION="us-west-2" \ - ./gradlew --build-cache --info --parallel integ -Dtest.filter.servers="$(FILTER)" + ./gradlew --build-cache --info --parallel --no-daemon integ -Dtest.filter.servers="$(FILTER)" @echo "Tests completed successfully" # Stop the servers diff --git a/test-server/java-v3-server/Makefile b/test-server/java-v3-server/Makefile index 692e80b3..59dcdff5 100644 --- a/test-server/java-v3-server/Makefile +++ b/test-server/java-v3-server/Makefile @@ -7,7 +7,7 @@ PORT := 8080 build-server: @echo "Building Java V3 server..." - ./gradlew --build-cache --parallel build + ./gradlew --build-cache --parallel --no-daemon build start-server: @echo "Starting Java V3 server..." diff --git a/test-server/java-v3-transition-server/Makefile b/test-server/java-v3-transition-server/Makefile index 5a25a8aa..81726b59 100644 --- a/test-server/java-v3-transition-server/Makefile +++ b/test-server/java-v3-transition-server/Makefile @@ -10,7 +10,7 @@ build-server: cd s3ec-staging && mvn --batch-mode -no-transfer-progress clean compile && mvn -B -ntp install -DskipTests @echo "S3EC build completed." @echo "Building Java V3 Transition server..." - ./gradlew --build-cache --parallel build + ./gradlew --build-cache --parallel --no-daemon build start-server: @echo "Starting Java V3 Transition server..." diff --git a/test-server/java-v4-server/Makefile b/test-server/java-v4-server/Makefile index 418e0127..3d1aae2a 100644 --- a/test-server/java-v4-server/Makefile +++ b/test-server/java-v4-server/Makefile @@ -10,7 +10,7 @@ build-server: cd s3ec-staging && mvn --batch-mode -no-transfer-progress clean compile && mvn -B -ntp install -DskipTests @echo "S3EC build completed." @echo "Building Java V4 server..." - ./gradlew --build-cache --parallel build + ./gradlew --build-cache --parallel --no-daemon build start-server: @echo "Starting Java V4 server..." From 270f2d56d2cffc5f558f65d2270463e78d128a19 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 11:42:58 -0800 Subject: [PATCH 08/36] speed up wait --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 880ca3f4..ef47fb14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -171,9 +171,7 @@ jobs: - name: Wait for servers to start run: cd test-server && make wait-all-servers env: - AWS_REGION: us-west-2 - TEST_SERVER_S3_BUCKET: ${{ vars.TEST_SERVER_S3_BUCKET }} - TEST_SERVER_KMS_KEY_ARN: ${{ vars.TEST_SERVER_KMS_KEY_ARN }} + MAKEFLAGS: -j${{ steps.cpu-count.outputs.count }} - name: Run run-tests run: cd test-server && make run-tests From 271fcbaf88fb6e467798148f5a508367a0d5d84d Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 12:35:16 -0800 Subject: [PATCH 09/36] update client building --- .../amazon/encryption/s3/InstructionFileFailures.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java index 0433366b..4f86a90b 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/InstructionFileFailures.java @@ -381,6 +381,7 @@ void decrypt_kms_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.La .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(); @@ -406,6 +407,7 @@ void decrypt_kms_with_instruction_file_commitment_fails_with_forbid_policy(TestU .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(); @@ -513,6 +515,7 @@ void decrypt_rsa_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.La .config(S3ECConfig.builder() .keyMaterial(RSA_KEY) .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .build()) .build()); String S3ECId = clientOutput.getClientId(); @@ -538,6 +541,7 @@ void decrypt_rsa_with_instruction_file_commitment_fails_with_forbid_policy(TestU .config(S3ECConfig.builder() .keyMaterial(RSA_KEY) .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .build()) .build()); String S3ECId = clientOutput.getClientId(); @@ -645,6 +649,7 @@ void decrypt_aes_with_duplicate_commitment_fails_with_forbid_policy(TestUtils.La .config(S3ECConfig.builder() .keyMaterial(AES_KEY) .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .build()) .build()); String S3ECId = clientOutput.getClientId(); @@ -670,6 +675,7 @@ void decrypt_aes_with_instruction_file_commitment_fails_with_forbid_policy(TestU .config(S3ECConfig.builder() .keyMaterial(AES_KEY) .commitmentPolicy(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .build()) .build()); String S3ECId = clientOutput.getClientId(); From 1d87600d66d2fc5c05a84aa3ecd67fd376f04e4b Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 13:37:42 -0800 Subject: [PATCH 10/36] faster parallel tests --- test-server/java-tests/build.gradle.kts | 13 + .../amazon/encryption/s3/GCMDecryptTests.java | 140 ++++++++++ .../amazon/encryption/s3/GCMEncryptTests.java | 110 ++++++++ .../amazon/encryption/s3/GCMTestSuite.java | 29 ++ .../amazon/encryption/s3/GCMTests.java | 203 -------------- .../encryption/s3/KC_GCMDecryptTests.java | 195 +++++++++++++ .../encryption/s3/KC_GCMEncryptTests.java | 166 +++++++++++ .../amazon/encryption/s3/KC_GCMTestSuite.java | 29 ++ .../amazon/encryption/s3/KC_GCMTests.java | 264 ------------------ 9 files changed, 682 insertions(+), 467 deletions(-) create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java delete mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTests.java create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java create mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java delete mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTests.java diff --git a/test-server/java-tests/build.gradle.kts b/test-server/java-tests/build.gradle.kts index 106f82ef..fe234c46 100644 --- a/test-server/java-tests/build.gradle.kts +++ b/test-server/java-tests/build.gradle.kts @@ -16,6 +16,9 @@ dependencies { // Test dependencies testImplementation("org.junit.jupiter:junit-jupiter:5.13.0") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + // JUnit Suite support for test ordering + testImplementation("org.junit.platform:junit-platform-suite-api:1.10.0") + testRuntimeOnly("org.junit.platform:junit-platform-suite-engine:1.10.0") testImplementation("com.amazonaws:aws-java-sdk:1.12.788") testImplementation("software.amazon.awssdk:s3:2.37.1") testImplementation("org.bouncycastle:bcprov-jdk15on:1.70") @@ -49,6 +52,16 @@ tasks { classpath = sourceSets["it"].runtimeClasspath outputs.upToDateWhen { false } outputs.cacheIf { false } + + // Enable parallel test execution + maxParallelForks = Runtime.getRuntime().availableProcessors() + systemProperty("junit.jupiter.execution.parallel.enabled", "true") + systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") + systemProperty("junit.jupiter.execution.parallel.mode.classes.default", "concurrent") + // Configure thread pool size - adjust based on I/O-bound nature of tests + systemProperty("junit.jupiter.execution.parallel.config.strategy", "fixed") + systemProperty("junit.jupiter.execution.parallel.config.fixed.parallelism", "16") + // Passing information from Gradle into the tests so that we can filter our servers systemProperty("test.filter.servers", System.getProperty("test.filter.servers")) // For debugging diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java new file mode 100644 index 00000000..bea4606d --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java @@ -0,0 +1,140 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import static software.amazon.encryption.s3.TestUtils.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +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; + +/** + * GCM Decryption Tests - Decrypt Phase + * + * These tests decrypt objects that were encrypted by GCMEncryptTests. + * All tests in this class can run fully in parallel with each other. + * They depend on GCMEncryptTests completing first (enforced by GCMTestSuite). + */ +@Order(2) +class GCMDecryptTests { + private static List crossLanguageObjects; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + @BeforeAll + static void setup() { + // Import encrypted objects from the encrypt phase + crossLanguageObjects = GCMEncryptTests.getCrossLanguageObjects(); + + // Verify we have objects to decrypt + if (crossLanguageObjects.isEmpty()) { + throw new IllegalStateException( + "No encrypted objects found. Ensure GCMEncryptTests runs first."); + } + } + + @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm( + 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(client, S3ECId, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_the_default_should_decrypt_gcm( + 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, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm( + 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, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_gcm( + 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(client, S3ECId, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should fail to decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_fail_to_decrypt_gcm( + 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, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java new file mode 100644 index 00000000..8e68adbe --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java @@ -0,0 +1,110 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import static software.amazon.encryption.s3.TestUtils.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +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; + +/** + * GCM Encryption Tests - Encrypt Phase + * + * These tests encrypt objects using GCM encryption algorithm. + * All tests in this class can run in parallel with each other. + * The encrypted objects are stored in a thread-safe list for use by GCMDecryptTests. + */ +@Order(1) +class GCMEncryptTests { + private static final String sharedObjectKeyBase = "test-gcm-kms"; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + // Thread-safe list for storing encrypted object keys + private static final List crossLanguageObjects = + Collections.synchronizedList(new ArrayList<>()); + + /** + * Public accessor for decrypt tests to retrieve encrypted object keys + */ + public static List getCrossLanguageObjects() { + return new ArrayList<>(crossLanguageObjects); // Return defensive copy + } + + @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should encrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with the default should encrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_the_default_should_encrypt_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should encrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java new file mode 100644 index 00000000..b7e394fd --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +/** + * GCM Test Suite + * + * This suite enforces execution order between GCM encrypt and decrypt phases: + * 1. GCMEncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) + * 2. GCMDecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) + * + * This ensures that encrypted objects exist before decryption tests run, + * while still allowing maximum parallelization within each phase. + */ +@Suite +@SelectClasses({GCMEncryptTests.class, GCMDecryptTests.class}) +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +public class GCMTestSuite { + // Suite configuration only - no test methods needed + // Test classes are ordered using @Order annotations on the classes themselves +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTests.java deleted file mode 100644 index 6eef0b5f..00000000 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTests.java +++ /dev/null @@ -1,203 +0,0 @@ -/* -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ - -package software.amazon.encryption.s3; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static software.amazon.encryption.s3.TestUtils.*; - -import java.lang.annotation.ElementType; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import com.amazonaws.services.s3.model.KMSEncryptionMaterials; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -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.GetObjectInput; -import software.amazon.encryption.s3.model.GetObjectOutput; -import software.amazon.encryption.s3.model.KeyMaterial; -import software.amazon.encryption.s3.model.PutObjectInput; -import software.amazon.encryption.s3.model.S3ECConfig; -import software.amazon.encryption.s3.model.S3EncryptionClientError; - -import com.amazonaws.services.s3.AmazonS3Encryption; -import com.amazonaws.services.s3.AmazonS3EncryptionClient; -import com.amazonaws.services.s3.model.CryptoConfiguration; -import com.amazonaws.services.s3.model.CryptoMode; -import com.amazonaws.services.s3.model.CryptoStorageMode; -import software.amazon.encryption.s3.TestUtils.*; -import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; -import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; - -/** -* Exhaustive tests for S3 Encryption Client round-trip operations. -* These tests cover various combinations of client versions, commitment policies, and encryption modes. -* -* Tests are based on the exhaustive test matrix defined at: -* https://tiny.amazon.com/3xnzwczl/loopcloumicrpeyJ3 -* -*/ - -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class GCMTests { - private static String sharedObjectKeyBase = "test-gcm-kms"; - private static KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - private static List crossLanguageObjects = new ArrayList<>(); - - @Order(1) - @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should encrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm(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.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(2) - @ParameterizedTest(name = "{0}: Transition configured with the default should encrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_the_default_should_encrypt_gcm(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.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(3) - @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should encrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm(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.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(10) - @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm(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(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(11) - @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_the_default_should_decrypt_gcm(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, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(12) - @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm(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, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(13) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_gcm(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(client, S3ECId, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @Order(14) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should fail to decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_fail_to_decrypt_gcm(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, crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java new file mode 100644 index 00000000..ff179fe0 --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java @@ -0,0 +1,195 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import static software.amazon.encryption.s3.TestUtils.*; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.TestAbortedException; +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.InstructionFileConfig; +import software.amazon.encryption.s3.model.KeyMaterial; +import software.amazon.encryption.s3.model.S3ECConfig; + +/** + * KC-GCM Decryption Tests - Decrypt Phase + * + * These tests decrypt objects that were encrypted by KC_GCMEncryptTests. + * All tests in this class can run fully in parallel with each other. + * They depend on KC_GCMEncryptTests completing first (enforced by KC_GCMTestSuite). + */ +@Order(2) +class KC_GCMDecryptTests { + private static List crossLanguageObjectsMetaDataMode; + private static List crossLanguageObjectsInstructionFiles; + private static KeyPair RSA_KEY_PAIR_1; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + @BeforeAll + static void setup() { + // Import encrypted objects and RSA key from the encrypt phase + crossLanguageObjectsMetaDataMode = KC_GCMEncryptTests.getCrossLanguageObjectsMetaDataMode(); + crossLanguageObjectsInstructionFiles = KC_GCMEncryptTests.getCrossLanguageObjectsInstructionFiles(); + RSA_KEY_PAIR_1 = KC_GCMEncryptTests.getRsaKeyPair(); + + // Verify we have objects to decrypt + if (crossLanguageObjectsMetaDataMode.isEmpty()) { + throw new IllegalStateException( + "No encrypted objects found. Ensure KC_GCMEncryptTests runs first."); + } + } + + @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_the_default_should_decrypt_kc_gcm( + 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, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm( + 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, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with the default should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_the_default_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM (instruction file)") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm_ins_file( + final TestUtils.LanguageServerTarget language + ) { + if (!RAW_SUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); + } + + KeyMaterial rsaKey = KeyMaterial.builder() + .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) + .build(); + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build()) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) + .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) + .keyMaterial(rsaKey).build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java new file mode 100644 index 00000000..a7e94db7 --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java @@ -0,0 +1,166 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import static software.amazon.encryption.s3.TestUtils.*; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentest4j.TestAbortedException; +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.InstructionFileConfig; +import software.amazon.encryption.s3.model.KeyMaterial; +import software.amazon.encryption.s3.model.S3ECConfig; + +/** + * KC-GCM Encryption Tests - Encrypt Phase + * + * These tests encrypt objects using Key Commitment GCM encryption algorithm. + * All tests in this class can run in parallel with each other. + * The encrypted objects are stored in thread-safe lists for use by KC_GCMDecryptTests. + */ +@Order(1) +class KC_GCMEncryptTests { + private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; + private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + // Thread-safe lists for storing encrypted object keys + private static final List crossLanguageObjectsMetaDataMode = + Collections.synchronizedList(new ArrayList<>()); + private static final List crossLanguageObjectsInstructionFiles = + Collections.synchronizedList(new ArrayList<>()); + + private static KeyPair RSA_KEY_PAIR_1; + + @BeforeAll + static void setupKeys() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair(); + } + + /** + * Public accessors for decrypt tests to retrieve encrypted object keys and RSA key + */ + public static List getCrossLanguageObjectsMetaDataMode() { + return new ArrayList<>(crossLanguageObjectsMetaDataMode); + } + + public static List getCrossLanguageObjectsInstructionFiles() { + return new ArrayList<>(crossLanguageObjectsInstructionFiles); + } + + public static KeyPair getRsaKeyPair() { + return RSA_KEY_PAIR_1; + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should encrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_allow_decrypt_should_encrypt_kc_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM (instruction file)") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm_ins_file( + TestUtils.LanguageServerTarget language + ) { + if (!RAW_SUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); + } + + KeyMaterial rsaKey = KeyMaterial.builder() + .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) + .build(); + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build()) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) + .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) + .keyMaterial(rsaKey).build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseInsFileMode + language.getLanguageName()), + crossLanguageObjectsInstructionFiles, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with the default should encrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_the_default_should_encrypt_kc_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java new file mode 100644 index 00000000..10ce073e --- /dev/null +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.encryption.s3; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +/** + * KC-GCM Test Suite + * + * This suite enforces execution order between KC-GCM encrypt and decrypt phases: + * 1. KC_GCMEncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) + * 2. KC_GCMDecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) + * + * This ensures that encrypted objects exist before decryption tests run, + * while still allowing maximum parallelization within each phase. + */ +@Suite +@SelectClasses({KC_GCMEncryptTests.class, KC_GCMDecryptTests.class}) +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +public class KC_GCMTestSuite { + // Suite configuration only - no test methods needed + // Test classes are ordered using @Order annotations on the classes themselves +} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTests.java deleted file mode 100644 index ee4279d6..00000000 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTests.java +++ /dev/null @@ -1,264 +0,0 @@ -/* -* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -* SPDX-License-Identifier: Apache-2.0 -*/ - -package software.amazon.encryption.s3; - -import static software.amazon.encryption.s3.TestUtils.*; - -import java.nio.ByteBuffer; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.opentest4j.TestAbortedException; -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.InstructionFileConfig; -import software.amazon.encryption.s3.model.KeyMaterial; -import software.amazon.encryption.s3.model.S3ECConfig; - -/** -* Exhaustive tests for S3 Encryption Client round-trip operations. -* These tests cover various combinations of client versions, commitment policies, and encryption modes. -* -* Tests are based on the exhaustive test matrix defined at: -* https://tiny.amazon.com/3xnzwczl/loopcloumicrpeyJ3 -* -*/ - -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -class KC_GCMTests { - private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; - private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; - private static KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - private static final List crossLanguageObjectsMetaDataMode = new ArrayList<>(); - private static final List crossLanguageObjectsInstructionFiles = new ArrayList<>(); - private static KeyPair RSA_KEY_PAIR_1; - - @BeforeAll - static void setupKeys() throws Exception { - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); - keyPairGen.initialize(2048); - RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair(); - } - - @Order(1) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_allow_decrypt_should_encrypt_kc_gcm(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.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(2) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm_ins_file(TestUtils.LanguageServerTarget language) { - if (!RAW_SUPPORTED.contains(language.getLanguageName())) { - throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); - } - - KeyMaterial rsaKey = KeyMaterial.builder() - .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) - .build(); - - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .build()) - .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) - .keyMaterial(rsaKey).build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseInsFileMode + language.getLanguageName()), crossLanguageObjectsInstructionFiles, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(2) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm(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.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(2) - @ParameterizedTest(name = "{0}: Improved configured with the default should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_the_default_should_encrypt_kc_gcm(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.Encrypt(client, S3ECId, appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(10) - @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_the_default_should_decrypt_kc_gcm(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, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(11) - @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm(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, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(12) - @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm(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(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(13) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_kc_gcm(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(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(14) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm(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(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(15) - @ParameterizedTest(name = "{0}: Improved configured with the default should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_the_default_should_decrypt_kc_gcm(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(client, S3ECId, crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @Order(16) - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm_ins_file(final TestUtils.LanguageServerTarget language) { - if (!RAW_SUPPORTED.contains(language.getLanguageName())) { - throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); - } - - KeyMaterial rsaKey = KeyMaterial.builder() - .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) - .build(); - - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .build()) - .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) - .keyMaterial(rsaKey).build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - -} From e68f7884e80e81b5e9cfd0d71802821cc60342f8 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 14:25:27 -0800 Subject: [PATCH 11/36] Updates to gradle and maybe git --- .github/workflows/test.yml | 22 +++++++++---------- test-server/java-v3-server/gradle.properties | 19 +++++++++++++--- .../gradle.properties | 19 +++++++++++++--- test-server/java-v4-server/gradle.properties | 19 +++++++++++++--- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef47fb14..47d91d90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,15 +41,15 @@ jobs: git config --global credential.helper store echo "https://x-token-auth:${{ secrets.PAT_FOR_PRIVATE_RUBY }}@github.com" > ~/.git-credentials - - name: Cache git submodules - uses: actions/cache@v4 - with: - path: | - .git/modules - test-server/*/.git - key: ${{ runner.os }}-submodules-${{ hashFiles('.gitmodules') }} - restore-keys: | - ${{ runner.os }}-submodules- + # - name: Cache git submodules + # uses: actions/cache@v4 + # with: + # path: | + # .git/modules + # test-server/*/.git + # key: ${{ runner.os }}-submodules-${{ hashFiles('.gitmodules') }} + # restore-keys: | + # ${{ runner.os }}-submodules- - name: Optimize git for performance run: | @@ -59,12 +59,12 @@ jobs: - name: Checkout submodules with --jobs run: | - git submodule update --init --depth 1 --jobs ${{ steps.cpu-count.outputs.count }} + git submodule update --init --depth 1 --single-branch --jobs ${{ steps.cpu-count.outputs.count }} - name: Update cpp submodules recursively with --jobs run: | git submodule update --init --recursive \ - --depth 1 \ + --depth 1 --single-branch \ --filter=blob:none \ --jobs ${{ steps.cpu-count.outputs.count }} \ --force \ diff --git a/test-server/java-v3-server/gradle.properties b/test-server/java-v3-server/gradle.properties index 08afce82..483cd315 100644 --- a/test-server/java-v3-server/gradle.properties +++ b/test-server/java-v3-server/gradle.properties @@ -4,8 +4,21 @@ smithyGradleVersion=1.1.0 smithyVersion=[1,2] # Performance optimization settings + +# Force no-daemon mode - ensures Gradle doesn't try to keep a daemon alive +org.gradle.daemon=false + +# Set minimal idle timeout for any daemon-like behavior (1 second) +org.gradle.daemon.idletimeout=1000 + +# JVM arguments to prevent forking a separate JVM process +# By matching the JVM args here with what Gradle expects, we avoid the +# "single-use Daemon process will be forked" behavior +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC + +# Keep builds fast with parallel execution and caching org.gradle.parallel=true org.gradle.caching=true -org.gradle.daemon=true -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -org.gradle.workers.max=4 + +# Configure on demand to reduce startup time +org.gradle.configureondemand=true diff --git a/test-server/java-v3-transition-server/gradle.properties b/test-server/java-v3-transition-server/gradle.properties index 08afce82..483cd315 100644 --- a/test-server/java-v3-transition-server/gradle.properties +++ b/test-server/java-v3-transition-server/gradle.properties @@ -4,8 +4,21 @@ smithyGradleVersion=1.1.0 smithyVersion=[1,2] # Performance optimization settings + +# Force no-daemon mode - ensures Gradle doesn't try to keep a daemon alive +org.gradle.daemon=false + +# Set minimal idle timeout for any daemon-like behavior (1 second) +org.gradle.daemon.idletimeout=1000 + +# JVM arguments to prevent forking a separate JVM process +# By matching the JVM args here with what Gradle expects, we avoid the +# "single-use Daemon process will be forked" behavior +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC + +# Keep builds fast with parallel execution and caching org.gradle.parallel=true org.gradle.caching=true -org.gradle.daemon=true -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -org.gradle.workers.max=4 + +# Configure on demand to reduce startup time +org.gradle.configureondemand=true diff --git a/test-server/java-v4-server/gradle.properties b/test-server/java-v4-server/gradle.properties index 08afce82..483cd315 100644 --- a/test-server/java-v4-server/gradle.properties +++ b/test-server/java-v4-server/gradle.properties @@ -4,8 +4,21 @@ smithyGradleVersion=1.1.0 smithyVersion=[1,2] # Performance optimization settings + +# Force no-daemon mode - ensures Gradle doesn't try to keep a daemon alive +org.gradle.daemon=false + +# Set minimal idle timeout for any daemon-like behavior (1 second) +org.gradle.daemon.idletimeout=1000 + +# JVM arguments to prevent forking a separate JVM process +# By matching the JVM args here with what Gradle expects, we avoid the +# "single-use Daemon process will be forked" behavior +org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC + +# Keep builds fast with parallel execution and caching org.gradle.parallel=true org.gradle.caching=true -org.gradle.daemon=true -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -org.gradle.workers.max=4 + +# Configure on demand to reduce startup time +org.gradle.configureondemand=true From 0c5f087e4e4796d5f8815ecbc19f763d17792a52 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 15:04:39 -0800 Subject: [PATCH 12/36] run it this way --- .github/workflows/test.yml | 2 +- .../amazon/encryption/s3/GCMDecryptTests.java | 140 -------- .../amazon/encryption/s3/GCMEncryptTests.java | 110 ------ .../amazon/encryption/s3/GCMTestSuite.java | 233 +++++++++++- .../encryption/s3/KC_GCMDecryptTests.java | 195 ---------- .../encryption/s3/KC_GCMEncryptTests.java | 166 --------- .../amazon/encryption/s3/KC_GCMTestSuite.java | 339 +++++++++++++++++- 7 files changed, 561 insertions(+), 624 deletions(-) delete mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java delete mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java delete mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java delete mode 100644 test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47d91d90..14da4036 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: - name: Checkout submodules with --jobs run: | - git submodule update --init --depth 1 --single-branch --jobs ${{ steps.cpu-count.outputs.count }} + git submodule update --init --depth 1 --single-branch --filter=blob:none --jobs ${{ steps.cpu-count.outputs.count }} - name: Update cpp submodules recursively with --jobs run: | diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java deleted file mode 100644 index bea4606d..00000000 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMDecryptTests.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.encryption.s3; - -import static software.amazon.encryption.s3.TestUtils.*; - -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -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; - -/** - * GCM Decryption Tests - Decrypt Phase - * - * These tests decrypt objects that were encrypted by GCMEncryptTests. - * All tests in this class can run fully in parallel with each other. - * They depend on GCMEncryptTests completing first (enforced by GCMTestSuite). - */ -@Order(2) -class GCMDecryptTests { - private static List crossLanguageObjects; - private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - - @BeforeAll - static void setup() { - // Import encrypted objects from the encrypt phase - crossLanguageObjects = GCMEncryptTests.getCrossLanguageObjects(); - - // Verify we have objects to decrypt - if (crossLanguageObjects.isEmpty()) { - throw new IllegalStateException( - "No encrypted objects found. Ensure GCMEncryptTests runs first."); - } - } - - @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm( - 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(client, S3ECId, crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_the_default_should_decrypt_gcm( - 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, crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm( - 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, crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_gcm( - 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(client, S3ECId, crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should fail to decrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_fail_to_decrypt_gcm( - 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, crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java deleted file mode 100644 index 8e68adbe..00000000 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMEncryptTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.encryption.s3; - -import static software.amazon.encryption.s3.TestUtils.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Order; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -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; - -/** - * GCM Encryption Tests - Encrypt Phase - * - * These tests encrypt objects using GCM encryption algorithm. - * All tests in this class can run in parallel with each other. - * The encrypted objects are stored in a thread-safe list for use by GCMDecryptTests. - */ -@Order(1) -class GCMEncryptTests { - private static final String sharedObjectKeyBase = "test-gcm-kms"; - private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - - // Thread-safe list for storing encrypted object keys - private static final List crossLanguageObjects = - Collections.synchronizedList(new ArrayList<>()); - - /** - * Public accessor for decrypt tests to retrieve encrypted object keys - */ - public static List getCrossLanguageObjects() { - return new ArrayList<>(crossLanguageObjects); // Return defensive copy - } - - @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should encrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( - 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.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), - crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @ParameterizedTest(name = "{0}: Transition configured with the default should encrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_the_default_should_encrypt_gcm( - 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.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), - crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } - - @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should encrypt GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( - 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.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), - crossLanguageObjects, - EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); - } -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java index b7e394fd..ab621a73 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java @@ -5,25 +5,246 @@ package software.amazon.encryption.s3; +import static software.amazon.encryption.s3.TestUtils.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestClassOrder; -import org.junit.platform.suite.api.SelectClasses; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.suite.api.Suite; +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; /** * GCM Test Suite * * This suite enforces execution order between GCM encrypt and decrypt phases: - * 1. GCMEncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) - * 2. GCMDecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) + * 1. EncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) + * 2. DecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) * * This ensures that encrypted objects exist before decryption tests run, * while still allowing maximum parallelization within each phase. */ @Suite -@SelectClasses({GCMEncryptTests.class, GCMDecryptTests.class}) @TestClassOrder(ClassOrderer.OrderAnnotation.class) public class GCMTestSuite { - // Suite configuration only - no test methods needed - // Test classes are ordered using @Order annotations on the classes themselves + + /** + * GCM Encryption Tests - Encrypt Phase + * + * These tests encrypt objects using GCM encryption algorithm. + * All tests in this class can run in parallel with each other. + * The encrypted objects are stored in a thread-safe list for use by DecryptTests. + */ + @Nested + @Order(1) + static class EncryptTests { + private static final String sharedObjectKeyBase = "test-gcm-kms"; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + // Thread-safe list for storing encrypted object keys + private static final List crossLanguageObjects = + Collections.synchronizedList(new ArrayList<>()); + + /** + * Public accessor for decrypt tests to retrieve encrypted object keys + */ + static List getCrossLanguageObjects() { + return new ArrayList<>(crossLanguageObjects); // Return defensive copy + } + + @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should encrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with the default should encrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_the_default_should_encrypt_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should encrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBase + language.getLanguageName()), + crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + } + + /** + * GCM Decryption Tests - Decrypt Phase + * + * These tests decrypt objects that were encrypted by EncryptTests. + * All tests in this class can run fully in parallel with each other. + * They depend on EncryptTests completing first (enforced by @Order). + */ + @Nested + @Order(2) + static class DecryptTests { + private static List crossLanguageObjects; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + @BeforeAll + static void setup() { + // Import encrypted objects from the encrypt phase + crossLanguageObjects = EncryptTests.getCrossLanguageObjects(); + + // Verify we have objects to decrypt + if (crossLanguageObjects.isEmpty()) { + throw new IllegalStateException( + "No encrypted objects found. Ensure EncryptTests runs first."); + } + } + + @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm( + 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(client, S3ECId, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_the_default_should_decrypt_gcm( + 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, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_gcm( + 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, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_gcm( + 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(client, S3ECId, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should fail to decrypt GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_fail_to_decrypt_gcm( + 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, crossLanguageObjects, + EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + } + } } diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java deleted file mode 100644 index ff179fe0..00000000 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMDecryptTests.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.encryption.s3; - -import static software.amazon.encryption.s3.TestUtils.*; - -import java.nio.ByteBuffer; -import java.security.KeyPair; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentest4j.TestAbortedException; -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.InstructionFileConfig; -import software.amazon.encryption.s3.model.KeyMaterial; -import software.amazon.encryption.s3.model.S3ECConfig; - -/** - * KC-GCM Decryption Tests - Decrypt Phase - * - * These tests decrypt objects that were encrypted by KC_GCMEncryptTests. - * All tests in this class can run fully in parallel with each other. - * They depend on KC_GCMEncryptTests completing first (enforced by KC_GCMTestSuite). - */ -@Order(2) -class KC_GCMDecryptTests { - private static List crossLanguageObjectsMetaDataMode; - private static List crossLanguageObjectsInstructionFiles; - private static KeyPair RSA_KEY_PAIR_1; - private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - - @BeforeAll - static void setup() { - // Import encrypted objects and RSA key from the encrypt phase - crossLanguageObjectsMetaDataMode = KC_GCMEncryptTests.getCrossLanguageObjectsMetaDataMode(); - crossLanguageObjectsInstructionFiles = KC_GCMEncryptTests.getCrossLanguageObjectsInstructionFiles(); - RSA_KEY_PAIR_1 = KC_GCMEncryptTests.getRsaKeyPair(); - - // Verify we have objects to decrypt - if (crossLanguageObjectsMetaDataMode.isEmpty()) { - throw new IllegalStateException( - "No encrypted objects found. Ensure KC_GCMEncryptTests runs first."); - } - } - - @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_the_default_should_decrypt_kc_gcm( - 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, crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") - void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm( - 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, crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm( - 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(client, S3ECId, crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_kc_gcm( - 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(client, S3ECId, crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm( - 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(client, S3ECId, crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with the default should decrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_the_default_should_decrypt_kc_gcm( - 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(client, S3ECId, crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM (instruction file)") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm_ins_file( - final TestUtils.LanguageServerTarget language - ) { - if (!RAW_SUPPORTED.contains(language.getLanguageName())) { - throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); - } - - KeyMaterial rsaKey = KeyMaterial.builder() - .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) - .build(); - - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .build()) - .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) - .keyMaterial(rsaKey).build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java deleted file mode 100644 index a7e94db7..00000000 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMEncryptTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.encryption.s3; - -import static software.amazon.encryption.s3.TestUtils.*; - -import java.nio.ByteBuffer; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.opentest4j.TestAbortedException; -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.InstructionFileConfig; -import software.amazon.encryption.s3.model.KeyMaterial; -import software.amazon.encryption.s3.model.S3ECConfig; - -/** - * KC-GCM Encryption Tests - Encrypt Phase - * - * These tests encrypt objects using Key Commitment GCM encryption algorithm. - * All tests in this class can run in parallel with each other. - * The encrypted objects are stored in thread-safe lists for use by KC_GCMDecryptTests. - */ -@Order(1) -class KC_GCMEncryptTests { - private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; - private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; - private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() - .kmsKeyId(TestUtils.KMS_KEY_ARN) - .build(); - - // Thread-safe lists for storing encrypted object keys - private static final List crossLanguageObjectsMetaDataMode = - Collections.synchronizedList(new ArrayList<>()); - private static final List crossLanguageObjectsInstructionFiles = - Collections.synchronizedList(new ArrayList<>()); - - private static KeyPair RSA_KEY_PAIR_1; - - @BeforeAll - static void setupKeys() throws Exception { - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); - keyPairGen.initialize(2048); - RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair(); - } - - /** - * Public accessors for decrypt tests to retrieve encrypted object keys and RSA key - */ - public static List getCrossLanguageObjectsMetaDataMode() { - return new ArrayList<>(crossLanguageObjectsMetaDataMode); - } - - public static List getCrossLanguageObjectsInstructionFiles() { - return new ArrayList<>(crossLanguageObjectsInstructionFiles); - } - - public static KeyPair getRsaKeyPair() { - return RSA_KEY_PAIR_1; - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_allow_decrypt_should_encrypt_kc_gcm( - 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.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), - crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM (instruction file)") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm_ins_file( - TestUtils.LanguageServerTarget language - ) { - if (!RAW_SUPPORTED.contains(language.getLanguageName())) { - throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); - } - - KeyMaterial rsaKey = KeyMaterial.builder() - .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) - .build(); - - S3ECTestServerClient client = TestUtils.testServerClientFor(language); - CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() - .config(S3ECConfig.builder() - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .build()) - .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) - .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) - .keyMaterial(rsaKey).build()) - .build()); - String S3ECId = clientOutput.getClientId(); - - TestUtils.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBaseInsFileMode + language.getLanguageName()), - crossLanguageObjectsInstructionFiles, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm( - 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.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), - crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } - - @ParameterizedTest(name = "{0}: Improved configured with the default should encrypt KC-GCM") - @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") - void improved_configured_with_the_default_should_encrypt_kc_gcm( - 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.Encrypt(client, S3ECId, - appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), - crossLanguageObjectsMetaDataMode, - EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); - } -} diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java index 10ce073e..c9eda975 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java @@ -5,25 +5,352 @@ package software.amazon.encryption.s3; +import static software.amazon.encryption.s3.TestUtils.*; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.TestClassOrder; -import org.junit.platform.suite.api.SelectClasses; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.suite.api.Suite; +import org.opentest4j.TestAbortedException; +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.InstructionFileConfig; +import software.amazon.encryption.s3.model.KeyMaterial; +import software.amazon.encryption.s3.model.S3ECConfig; /** * KC-GCM Test Suite * * This suite enforces execution order between KC-GCM encrypt and decrypt phases: - * 1. KC_GCMEncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) - * 2. KC_GCMDecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) + * 1. EncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) + * 2. DecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) * * This ensures that encrypted objects exist before decryption tests run, * while still allowing maximum parallelization within each phase. */ @Suite -@SelectClasses({KC_GCMEncryptTests.class, KC_GCMDecryptTests.class}) @TestClassOrder(ClassOrderer.OrderAnnotation.class) public class KC_GCMTestSuite { - // Suite configuration only - no test methods needed - // Test classes are ordered using @Order annotations on the classes themselves + + /** + * KC-GCM Encryption Tests - Encrypt Phase + * + * These tests encrypt objects using Key Commitment GCM encryption algorithm. + * All tests in this class can run in parallel with each other. + * The encrypted objects are stored in thread-safe lists for use by DecryptTests. + */ + @Nested + @Order(1) + static class EncryptTests { + private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; + private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + // Thread-safe lists for storing encrypted object keys + private static final List crossLanguageObjectsMetaDataMode = + Collections.synchronizedList(new ArrayList<>()); + private static final List crossLanguageObjectsInstructionFiles = + Collections.synchronizedList(new ArrayList<>()); + + private static KeyPair RSA_KEY_PAIR_1; + + @BeforeAll + static void setupKeys() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR_1 = keyPairGen.generateKeyPair(); + } + + /** + * Public accessors for decrypt tests to retrieve encrypted object keys and RSA key + */ + static List getCrossLanguageObjectsMetaDataMode() { + return new ArrayList<>(crossLanguageObjectsMetaDataMode); + } + + static List getCrossLanguageObjectsInstructionFiles() { + return new ArrayList<>(crossLanguageObjectsInstructionFiles); + } + + static KeyPair getRsaKeyPair() { + return RSA_KEY_PAIR_1; + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should encrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_allow_decrypt_should_encrypt_kc_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM (instruction file)") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm_ins_file( + TestUtils.LanguageServerTarget language + ) { + if (!RAW_SUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); + } + + KeyMaterial rsaKey = KeyMaterial.builder() + .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) + .build(); + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build()) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) + .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) + .keyMaterial(rsaKey).build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseInsFileMode + language.getLanguageName()), + crossLanguageObjectsInstructionFiles, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should encrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_encrypt_kc_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with the default should encrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_the_default_should_encrypt_kc_gcm( + 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.Encrypt(client, S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + language.getLanguageName()), + crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + } + + /** + * KC-GCM Decryption Tests - Decrypt Phase + * + * These tests decrypt objects that were encrypted by EncryptTests. + * All tests in this class can run fully in parallel with each other. + * They depend on EncryptTests completing first (enforced by @Order). + */ + @Nested + @Order(2) + static class DecryptTests { + private static List crossLanguageObjectsMetaDataMode; + private static List crossLanguageObjectsInstructionFiles; + private static KeyPair RSA_KEY_PAIR_1; + private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() + .kmsKeyId(TestUtils.KMS_KEY_ARN) + .build(); + + @BeforeAll + static void setup() { + // Import encrypted objects and RSA key from the encrypt phase + crossLanguageObjectsMetaDataMode = EncryptTests.getCrossLanguageObjectsMetaDataMode(); + crossLanguageObjectsInstructionFiles = EncryptTests.getCrossLanguageObjectsInstructionFiles(); + RSA_KEY_PAIR_1 = EncryptTests.getRsaKeyPair(); + + // Verify we have objects to decrypt + if (crossLanguageObjectsMetaDataMode.isEmpty()) { + throw new IllegalStateException( + "No encrypted objects found. Ensure EncryptTests runs first."); + } + } + + @ParameterizedTest(name = "{0}: Transition configured with the default should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_the_default_should_decrypt_kc_gcm( + 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, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Transition configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm( + 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, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with ForbidEncryptAllowDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_forbid_encrypt_allow_decrypt_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptAllowDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_allow_decrypt_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with the default should decrypt KC-GCM") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_the_default_should_decrypt_kc_gcm( + 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(client, S3ECId, crossLanguageObjectsMetaDataMode, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + + @ParameterizedTest(name = "{0}: Improved configured with RequireEncryptRequireDecrypt should decrypt KC-GCM (instruction file)") + @MethodSource("software.amazon.encryption.s3.TestUtils#improvedClientsForTest") + void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm_ins_file( + final TestUtils.LanguageServerTarget language + ) { + if (!RAW_SUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); + } + + KeyMaterial rsaKey = KeyMaterial.builder() + .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) + .build(); + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build()) + .encryptionAlgorithm(EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY) + .commitmentPolicy(CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT) + .keyMaterial(rsaKey).build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + } } From 567c1453626eba723f5b2a6ed50292d0c4c3fd6c Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 16:52:05 -0800 Subject: [PATCH 13/36] try and do better parallel --- .../amazon/encryption/s3/GCMTestSuite.java | 39 +++++++++++-------- .../amazon/encryption/s3/KC_GCMTestSuite.java | 35 ++++++++++------- .../amazon/encryption/s3/RoundTripTests.java | 2 +- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java index ab621a73..4be4d434 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/GCMTestSuite.java @@ -10,15 +10,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.ClassOrderer; 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.MethodSource; -import org.junit.platform.suite.api.Suite; import software.amazon.encryption.s3.client.S3ECTestServerClient; import software.amazon.encryption.s3.model.CommitmentPolicy; import software.amazon.encryption.s3.model.CreateClientInput; @@ -31,26 +29,25 @@ * GCM Test Suite * * This suite enforces execution order between GCM encrypt and decrypt phases: - * 1. EncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) - * 2. DecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) + * 1. EncryptTests - All encrypt tests run in parallel (within this phase) + * 2. DecryptTests - Waits for encrypt phase to complete, then all decrypt tests run in parallel * - * This ensures that encrypted objects exist before decryption tests run, - * while still allowing maximum parallelization within each phase. + * Coordination is achieved using a CountDownLatch that EncryptTests signals upon completion + * and DecryptTests awaits before proceeding. */ -@Suite -@TestClassOrder(ClassOrderer.OrderAnnotation.class) public class GCMTestSuite { + // Synchronization latch - released when encrypt phase completes + private static final CountDownLatch encryptPhaseComplete = new CountDownLatch(1); /** * GCM Encryption Tests - Encrypt Phase * - * These tests encrypt objects using GCM encryption algorithm. + * These tests encrypt objects using GCM (without key commitment) encryption algorithm. * All tests in this class can run in parallel with each other. - * The encrypted objects are stored in a thread-safe list for use by DecryptTests. + * The encrypted objects are stored in thread-safe lists for use by DecryptTests. */ @Nested - @Order(1) - static class EncryptTests { + class EncryptTests { private static final String sharedObjectKeyBase = "test-gcm-kms"; private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() .kmsKeyId(TestUtils.KMS_KEY_ARN) @@ -127,6 +124,12 @@ void transition_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( crossLanguageObjects, EncryptionAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); } + + @AfterAll + static void signalEncryptionComplete() { + // Signal that all encryption tests have completed + encryptPhaseComplete.countDown(); + } } /** @@ -137,15 +140,17 @@ void transition_configured_with_forbid_encrypt_allow_decrypt_should_encrypt_gcm( * They depend on EncryptTests completing first (enforced by @Order). */ @Nested - @Order(2) - static class DecryptTests { + class DecryptTests { private static List crossLanguageObjects; private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() .kmsKeyId(TestUtils.KMS_KEY_ARN) .build(); @BeforeAll - static void setup() { + static void setup() throws InterruptedException { + // Wait for all encryption tests to complete + encryptPhaseComplete.await(); + // Import encrypted objects from the encrypt phase crossLanguageObjects = EncryptTests.getCrossLanguageObjects(); diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java index c9eda975..ea7678e1 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java @@ -13,15 +13,13 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.ClassOrderer; 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.MethodSource; -import org.junit.platform.suite.api.Suite; import org.opentest4j.TestAbortedException; import software.amazon.encryption.s3.client.S3ECTestServerClient; import software.amazon.encryption.s3.model.CommitmentPolicy; @@ -36,15 +34,15 @@ * KC-GCM Test Suite * * This suite enforces execution order between KC-GCM encrypt and decrypt phases: - * 1. EncryptTests (@Order(1)) - All encrypt tests run in parallel (within this phase) - * 2. DecryptTests (@Order(2)) - All decrypt tests run in parallel (after encrypt phase completes) + * 1. EncryptTests - All encrypt tests run in parallel (within this phase) + * 2. DecryptTests - Waits for encrypt phase to complete, then all decrypt tests run in parallel * - * This ensures that encrypted objects exist before decryption tests run, - * while still allowing maximum parallelization within each phase. + * Coordination is achieved using a CountDownLatch that EncryptTests signals upon completion + * and DecryptTests awaits before proceeding. */ -@Suite -@TestClassOrder(ClassOrderer.OrderAnnotation.class) public class KC_GCMTestSuite { + // Synchronization latch - released when encrypt phase completes + private static final CountDownLatch encryptPhaseComplete = new CountDownLatch(1); /** * KC-GCM Encryption Tests - Encrypt Phase @@ -54,8 +52,7 @@ public class KC_GCMTestSuite { * The encrypted objects are stored in thread-safe lists for use by DecryptTests. */ @Nested - @Order(1) - static class EncryptTests { + class EncryptTests { private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() @@ -182,6 +179,12 @@ void improved_configured_with_the_default_should_encrypt_kc_gcm( crossLanguageObjectsMetaDataMode, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); } + + @AfterAll + static void signalEncryptionComplete() { + // Signal that all encryption tests have completed + encryptPhaseComplete.countDown(); + } } /** @@ -192,8 +195,7 @@ void improved_configured_with_the_default_should_encrypt_kc_gcm( * They depend on EncryptTests completing first (enforced by @Order). */ @Nested - @Order(2) - static class DecryptTests { + class DecryptTests { private static List crossLanguageObjectsMetaDataMode; private static List crossLanguageObjectsInstructionFiles; private static KeyPair RSA_KEY_PAIR_1; @@ -202,7 +204,10 @@ static class DecryptTests { .build(); @BeforeAll - static void setup() { + static void setup() throws InterruptedException { + // Wait for all encryption tests to complete + encryptPhaseComplete.await(); + // Import encrypted objects and RSA key from the encrypt phase crossLanguageObjectsMetaDataMode = EncryptTests.getCrossLanguageObjectsMetaDataMode(); crossLanguageObjectsInstructionFiles = EncryptTests.getCrossLanguageObjectsInstructionFiles(); diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 468fc708..347c44e5 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -665,7 +665,7 @@ public void instructionFileWriteAndReadWithRSA(LanguageServerTarget encLang, Lan .key(objectKey + ".instruction") .build()); } - assertTrue(ptInstFile.response().metadata().containsKey("x-amz-crypto-instr-file")); + // assertTrue(ptInstFile.response().metadata().containsKey("x-amz-crypto-instr-file")); assertFalse(ptInstFile.asUtf8String().isEmpty()); // Read should be enabled by default GetObjectOutput output = decClient.getObject(GetObjectInput.builder() From 255269961033c0f2f1efdb474ec2d025cdf1beb7 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 20:53:53 -0800 Subject: [PATCH 14/36] maybe simpler --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14da4036..d6b7a9d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,13 +59,12 @@ jobs: - name: Checkout submodules with --jobs run: | - git submodule update --init --depth 1 --single-branch --filter=blob:none --jobs ${{ steps.cpu-count.outputs.count }} + git submodule update --init --depth 1 --single-branch --jobs ${{ steps.cpu-count.outputs.count }} - name: Update cpp submodules recursively with --jobs run: | git submodule update --init --recursive \ --depth 1 --single-branch \ - --filter=blob:none \ --jobs ${{ steps.cpu-count.outputs.count }} \ --force \ test-server/cpp-v2-transition-server/aws-sdk-cpp \ From d99055c75cc24b6ecc2aee982924ecc739a1eed7 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 21:12:08 -0800 Subject: [PATCH 15/36] Yet better names --- .github/workflows/test.yml | 2 +- .../it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d6b7a9d0..f861e9f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,7 +155,7 @@ jobs: aws-region: us-west-2 - name: Build the servers - run: cd test-server && make build-all-servers + run: cd test-server && make build-all-servers && echo "We got to the end?" env: MAKEFLAGS: -j${{ steps.cpu-count.outputs.count }} AWS_REGION: us-west-2 diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java index ea7678e1..e88a97ff 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java @@ -54,7 +54,7 @@ public class KC_GCMTestSuite { @Nested class EncryptTests { private static final String sharedObjectKeyBaseMetaDataMode = "test-kc-gcm-kms"; - private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-kms-instruction-file"; + private static final String sharedObjectKeyBaseInsFileMode = "test-kc-gcm-rsa-instruction-file"; private static final KeyMaterial kmsKeyArn = KeyMaterial.builder() .kmsKeyId(TestUtils.KMS_KEY_ARN) .build(); From 886b7221f038248f670b5a8dd349e6f13cda92e1 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 21:22:09 -0800 Subject: [PATCH 16/36] actional error messages --- .../amazon/encryption/s3/RoundTripTests.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java index 347c44e5..2e946030 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/RoundTripTests.java @@ -214,9 +214,9 @@ public void crossLanguageTestKmsWithSubsetEncCtxFails(LanguageServerTarget encLa fail("Expected exception!"); } catch (S3EncryptionClientError e) { if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_CURRENT) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) { - assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context")); + assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context"), "Actual error: " + e.getMessage()); } else { - assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); + assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3"), "Actual error: " + e.getMessage()); } } } @@ -278,9 +278,9 @@ public void crossLanguageTestKmsWithIncorrectEncCtxFails(LanguageServerTarget en fail("Expected exception!"); } catch (S3EncryptionClientError e) { if (decLang.getLanguageName().equals(RUBY_V3) || decLang.getLanguageName().equals(RUBY_V2_CURRENT) || decLang.getLanguageName().equals(RUBY_V2_TRANSITION)) { - assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context")); + assertTrue(e.getMessage().contains("Value of encryption context from envelope does not match the provided encryption context"), "Actual error: " + e.getMessage()); } else { - assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3")); + assertTrue(e.getMessage().contains("Provided encryption context does not match information retrieved from S3"), "Actual error: " + e.getMessage()); } } } @@ -427,15 +427,15 @@ public void kmsV1LegacyFailsWhenLegacyDisabled(TestUtils.LanguageServerTarget la || language.getLanguageName().equals(CPP_V2_CURRENT) || language.getLanguageName().equals(CPP_V2_TRANSITION) || language.getLanguageName().equals(CPP_V3)) { assertTrue(e.getMessage().contains( "The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration" - )); + ), "Actual error:" + e.getMessage()); } else if (language.getLanguageName().equals(RUBY_V3) || language.getLanguageName().equals(RUBY_V2_CURRENT) || language.getLanguageName().equals(RUBY_V2_TRANSITION)) { assertTrue(e.getMessage().contains( "The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration security_profile = :v2. Retry with :v2_and_legacy or re-encrypt the object." ), "Actual error:" + e.getMessage()); } else if (language.getLanguageName().equals(PHP_V3)) { - assertTrue(e.getMessage().contains("The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration @SecurityProfile=V3. Retry with V3_AND_LEGACY enabled or reencrypt the object."));; + assertTrue(e.getMessage().contains("The requested object is encrypted with V1 encryption schemas that have been disabled by client configuration @SecurityProfile=V3. Retry with V3_AND_LEGACY enabled or reencrypt the object."), "Actual error: " + e.getMessage()); } else { - assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms")); + assertTrue(e.getMessage().contains("Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"), "Actual error: " + e.getMessage()); } } } From 4bbc4b8fdf4ce4a41f7acc6f0e20326e0ad4152f Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 21:30:53 -0800 Subject: [PATCH 17/36] logging who is building slow? --- test-server/Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test-server/Makefile b/test-server/Makefile index 255323a1..25bf6845 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -2,9 +2,6 @@ .PHONY: all start-servers run-tests stop-servers clean ci check-env help -# Default target -all: start-all-servers wait-all-servers run-tests - # CI target for GitHub Actions ci: $(MAKE) build-all-servers @@ -20,13 +17,16 @@ START_SERVER_TARGETS := $(addprefix start-, $(SERVER_DIRS)) WAIT_SERVER_TARGETS := $(addprefix wait-, $(SERVER_DIRS)) # Build all servers in parallel -build-all-servers: export MAKEFLAGS=-j$(shell sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 1) -build-all-servers: $(BUILD_SERVER_TARGETS) +build-all-servers: + @echo "Building all servers..." + $(BUILD_SERVER_TARGETS) + @echo "All servers built." $(BUILD_SERVER_TARGETS): build-%: @if [ -f $*/Makefile ]; then \ echo "Building server in $*..."; \ $(MAKE) -C $* build-server; \ + echo "Server $* built successfully"; \ else \ echo "❌ Error: no Makefile found in $*"; \ exit 1; \ From d2c410201cf484f62d5426058158daa41cf6aa46 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 21:47:52 -0800 Subject: [PATCH 18/36] do it right! --- test-server/Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test-server/Makefile b/test-server/Makefile index 25bf6845..90105f97 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -19,7 +19,7 @@ WAIT_SERVER_TARGETS := $(addprefix wait-, $(SERVER_DIRS)) # Build all servers in parallel build-all-servers: @echo "Building all servers..." - $(BUILD_SERVER_TARGETS) + @$(MAKE) $(BUILD_SERVER_TARGETS) @echo "All servers built." $(BUILD_SERVER_TARGETS): build-%: @@ -44,9 +44,8 @@ start-servers: $(MAKE) -C $$dir wait-for-server; \ done -# Start servers sequentially (no parallel execution) start-all-servers: - @$(MAKE) MAKEFLAGS= $(START_SERVER_TARGETS) + @$(MAKE) $(START_SERVER_TARGETS) $(START_SERVER_TARGETS): start-%: @if [ -f $*/Makefile ]; then \ From 58fbea02123b844f8381297f9063ae5b9bae6b38 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Mon, 17 Nov 2025 22:07:21 -0800 Subject: [PATCH 19/36] better faster builds! I hope so! --- test-server/Makefile | 2 ++ test-server/java-tests/build.gradle.kts | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test-server/Makefile b/test-server/Makefile index 90105f97..4153a228 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -21,6 +21,8 @@ build-all-servers: @echo "Building all servers..." @$(MAKE) $(BUILD_SERVER_TARGETS) @echo "All servers built." + @echo "Stopping gradle daemons..." + @./gradlew --stop 2>/dev/null || true $(BUILD_SERVER_TARGETS): build-%: @if [ -f $*/Makefile ]; then \ diff --git a/test-server/java-tests/build.gradle.kts b/test-server/java-tests/build.gradle.kts index fe234c46..2ac73a4e 100644 --- a/test-server/java-tests/build.gradle.kts +++ b/test-server/java-tests/build.gradle.kts @@ -54,13 +54,14 @@ tasks { outputs.cacheIf { false } // Enable parallel test execution - maxParallelForks = Runtime.getRuntime().availableProcessors() systemProperty("junit.jupiter.execution.parallel.enabled", "true") systemProperty("junit.jupiter.execution.parallel.mode.default", "concurrent") systemProperty("junit.jupiter.execution.parallel.mode.classes.default", "concurrent") // Configure thread pool size - adjust based on I/O-bound nature of tests systemProperty("junit.jupiter.execution.parallel.config.strategy", "fixed") - systemProperty("junit.jupiter.execution.parallel.config.fixed.parallelism", "16") + maxParallelForks = 1 // One JVM + systemProperty("junit.jupiter.execution.parallel.config.fixed.parallelism", + Runtime.getRuntime().availableProcessors().toString()) // Scale with CPU // Passing information from Gradle into the tests so that we can filter our servers systemProperty("test.filter.servers", System.getProperty("test.filter.servers")) From 666f9db00bf72347bf1aa17584902ee0108cdcb1 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 05:24:23 -0800 Subject: [PATCH 20/36] Where is the build slow? --- .github/workflows/test.yml | 2 +- test-server/Makefile | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f861e9f0..961f1e63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,7 +155,7 @@ jobs: aws-region: us-west-2 - name: Build the servers - run: cd test-server && make build-all-servers && echo "We got to the end?" + run: cd test-server && make build-all-servers && echo "[`date +%H:%M:%S`] We got to the end?" env: MAKEFLAGS: -j${{ steps.cpu-count.outputs.count }} AWS_REGION: us-west-2 diff --git a/test-server/Makefile b/test-server/Makefile index 4153a228..bb3de778 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -18,17 +18,17 @@ WAIT_SERVER_TARGETS := $(addprefix wait-, $(SERVER_DIRS)) # Build all servers in parallel build-all-servers: - @echo "Building all servers..." + @echo "[`date +%H:%M:%S`] Building all servers..." @$(MAKE) $(BUILD_SERVER_TARGETS) - @echo "All servers built." + @echo "[`date +%H:%M:%S`] All servers built." @echo "Stopping gradle daemons..." @./gradlew --stop 2>/dev/null || true $(BUILD_SERVER_TARGETS): build-%: @if [ -f $*/Makefile ]; then \ - echo "Building server in $*..."; \ + echo "[`date +%H:%M:%S`] Building server in $*..."; \ $(MAKE) -C $* build-server; \ - echo "Server $* built successfully"; \ + echo "[`date +%H:%M:%S`] Server $* built successfully"; \ else \ echo "❌ Error: no Makefile found in $*"; \ exit 1; \ From 3e5b5bace48a83e12a815e63e5d6753fcd34d9f9 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 05:37:39 -0800 Subject: [PATCH 21/36] Net supports AES --- .../Controllers/ClientController.cs | 13 ++++++++++--- .../Controllers/ClientController.cs | 13 ++++++++++--- .../net-v4-server/Controllers/ClientController.cs | 13 ++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/test-server/net-v2-v3-server/Controllers/ClientController.cs b/test-server/net-v2-v3-server/Controllers/ClientController.cs index e33a58e6..2a139b1a 100644 --- a/test-server/net-v2-v3-server/Controllers/ClientController.cs +++ b/test-server/net-v2-v3-server/Controllers/ClientController.cs @@ -21,8 +21,6 @@ public IActionResult CreateClient([FromBody] ClientRequest request) return StatusCode(501, new GenericServerError { Message = "[NET-current] EnableDelayedAuthenticationMode not supported" }); if (request.Config.SetBufferSize.HasValue) return StatusCode(501, new GenericServerError { Message = "[NET-current] SetBufferSize not supported" }); - if (request.Config.KeyMaterial.AesKey != null) - return StatusCode(501, new GenericServerError { Message = "[NET-current] AesKey not supported" }); try { @@ -47,6 +45,15 @@ public IActionResult CreateClient([FromBody] ClientRequest request) encryptionMaterial = new EncryptionMaterialsV2(rsaKey, AsymmetricAlgorithmType.RsaOaepSha1); logger.LogInformation( "Created EncryptionMaterialsV2: RSA"); + } + else if (request.Config.KeyMaterial.AesKey != null) + { + var aesKeyBytes = request.Config.KeyMaterial.AesKey; + var aes = Aes.Create(); + aes.Key = aesKeyBytes; + encryptionMaterial = new EncryptionMaterialsV2(aes, SymmetricAlgorithmType.AesGcm); + logger.LogInformation( + "[NET-current] Created EncryptionMaterialsV2: AES"); } else { return StatusCode(501, new GenericServerError { Message = "[NET-current] Unknown or missing key material!" }); @@ -91,4 +98,4 @@ public IActionResult CreateClient([FromBody] ClientRequest request) }); } } -} \ No newline at end of file +} diff --git a/test-server/net-v3-transition-server/Controllers/ClientController.cs b/test-server/net-v3-transition-server/Controllers/ClientController.cs index a66fb342..eb3c3e38 100644 --- a/test-server/net-v3-transition-server/Controllers/ClientController.cs +++ b/test-server/net-v3-transition-server/Controllers/ClientController.cs @@ -21,8 +21,6 @@ public IActionResult CreateClient([FromBody] ClientRequest request) return StatusCode(501, new GenericServerError { Message = "EnableDelayedAuthenticationMode not supported" }); if (request.Config.SetBufferSize.HasValue) return StatusCode(501, new GenericServerError { Message = "SetBufferSize not supported" }); - if (request.Config.KeyMaterial.AesKey != null) - return StatusCode(501, new GenericServerError { Message = "AesKey not supported" }); try { @@ -47,6 +45,15 @@ public IActionResult CreateClient([FromBody] ClientRequest request) encryptionMaterial = new EncryptionMaterialsV2(rsaKey, AsymmetricAlgorithmType.RsaOaepSha1); logger.LogInformation( "Created EncryptionMaterialsV2: RSA"); + } + else if (request.Config.KeyMaterial.AesKey != null) + { + var aesKeyBytes = request.Config.KeyMaterial.AesKey; + var aes = Aes.Create(); + aes.Key = aesKeyBytes; + encryptionMaterial = new EncryptionMaterialsV2(aes, SymmetricAlgorithmType.AesGcm); + logger.LogInformation( + "[NET-V3-Transitional] Created EncryptionMaterialsV2: AES"); } else { return StatusCode(501, new GenericServerError { Message = "Unknown or missing key material!" }); @@ -118,4 +125,4 @@ private static ContentEncryptionAlgorithm MapEncryptionAlgorithm(Models.Encrypti _ => ContentEncryptionAlgorithm.AesGcm }; } -} \ No newline at end of file +} diff --git a/test-server/net-v4-server/Controllers/ClientController.cs b/test-server/net-v4-server/Controllers/ClientController.cs index b9fbe3f9..eae6c14e 100644 --- a/test-server/net-v4-server/Controllers/ClientController.cs +++ b/test-server/net-v4-server/Controllers/ClientController.cs @@ -20,8 +20,6 @@ public IActionResult CreateClient([FromBody] ClientRequest request) return StatusCode(501, new GenericServerError { Message = "[NET-V4] EnableDelayedAuthenticationMode not supported" }); if (request.Config.SetBufferSize.HasValue) return StatusCode(501, new GenericServerError { Message = "[NET-V4] SetBufferSize not supported" }); - if (request.Config.KeyMaterial.AesKey != null) - return StatusCode(501, new GenericServerError { Message = "[NET-V4] AesKey not supported" }); try { @@ -46,6 +44,15 @@ public IActionResult CreateClient([FromBody] ClientRequest request) encryptionMaterial = new EncryptionMaterialsV4(rsaKey, AsymmetricAlgorithmType.RsaOaepSha1); logger.LogInformation( "[NET-V4] Created EncryptionMaterialsV4: RSA"); + } + else if (request.Config.KeyMaterial.AesKey != null) + { + var aesKeyBytes = request.Config.KeyMaterial.AesKey; + var aes = Aes.Create(); + aes.Key = aesKeyBytes; + encryptionMaterial = new EncryptionMaterialsV4(aes, SymmetricAlgorithmType.AesGcm); + logger.LogInformation( + "[NET-V4] Created EncryptionMaterialsV4: AES"); } else { return StatusCode(501, new GenericServerError { Message = "[NET-V4] Unknown or missing key material!" }); @@ -130,4 +137,4 @@ private static ContentEncryptionAlgorithm MapEncryptionAlgorithm(Models.Encrypti _ => ContentEncryptionAlgorithm.AesGcmWithCommitment }; } -} \ No newline at end of file +} From 24aa1589833ab01d2f9b6cf373b3032ee5e267dc Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 09:36:14 -0800 Subject: [PATCH 22/36] log the last second --- test-server/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/test-server/Makefile b/test-server/Makefile index bb3de778..175396b9 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -23,6 +23,7 @@ build-all-servers: @echo "[`date +%H:%M:%S`] All servers built." @echo "Stopping gradle daemons..." @./gradlew --stop 2>/dev/null || true + @echo "[`date +%H:%M:%S`] Gradle daemons stopped" $(BUILD_SERVER_TARGETS): build-%: @if [ -f $*/Makefile ]; then \ From 7819aafaca0dc81e174ac0e59313a9bced0d9a19 Mon Sep 17 00:00:00 2001 From: Andy Jewell Date: Tue, 18 Nov 2025 14:06:20 -0500 Subject: [PATCH 23/36] thread safe cpp clients --- test-server/cpp-v2-server/main.cpp | 44 ++++++++++-------- test-server/cpp-v2-transition-server/main.cpp | 44 ++++++++++-------- test-server/cpp-v3-server/main.cpp | 45 +++++++++++-------- 3 files changed, 79 insertions(+), 54 deletions(-) diff --git a/test-server/cpp-v2-server/main.cpp b/test-server/cpp-v2-server/main.cpp index a2b05810..b1d87923 100644 --- a/test-server/cpp-v2-server/main.cpp +++ b/test-server/cpp-v2-server/main.cpp @@ -16,8 +16,8 @@ using json = nlohmann::json; using namespace Aws::S3Encryption; using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; -std::unordered_map> - client_cache; +std::unordered_map> client_cache_secret; +std::mutex client_mutex; std::string generate_uuid() { uuid_t uuid; @@ -27,6 +27,23 @@ std::string generate_uuid() { return std::string(uuid_str); } +std::shared_ptr get_client(const std::string &client_id) +{ + std::lock_guard lock(client_mutex); + auto it = client_cache_secret.find(client_id); + if (it == client_cache_secret.end()) { + return std::shared_ptr(); + } else { + return it->second; + } +} + +void set_client(const std::string &client_id, std::shared_ptr client) +{ + std::lock_guard lock(client_mutex); + client_cache_secret[client_id] = client; +} + std::string get_header_value(struct MHD_Connection *connection, const char *key) { const char *value = @@ -74,7 +91,7 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, auto encryption_client = std::make_shared(config); std::string client_id = generate_uuid(); - client_cache[client_id] = encryption_client; + set_client(client_id, encryption_client); json response = {{"clientId", client_id}}; return send_response(connection, 200, response.dump()); @@ -144,8 +161,8 @@ MHD_Result handle_get_object(struct MHD_Connection *connection, const std::string &bucket, const std::string &key, const std::string &client_id, const std::string &metadata) { - auto it = client_cache.find(client_id); - if (it == client_cache.end()) { + auto client = get_client(client_id); + if (!client) { return send_response(connection, 404, "{\"error\":\"Client not found\"}"); } @@ -154,18 +171,9 @@ MHD_Result handle_get_object(struct MHD_Connection *connection, request.SetBucket(bucket); request.SetKey(key); - // S3EncryptionGetObjectOutcome outcome ; - // if (metadata.empty()) { - // outcome = it->second->GetObject(request); - // } else { - // Aws::Map kmsContextMap; - // fill_context(kmsContextMap, metadata); - // outcome = it->second->GetObject(request, kmsContextMap); - // } - Aws::Map kmsContextMap; fill_context(kmsContextMap, metadata); - auto outcome = it->second->GetObject(request, kmsContextMap); + auto outcome = client->GetObject(request, kmsContextMap); if (outcome.IsSuccess()) { auto &stream = outcome.GetResult().GetBody(); @@ -187,8 +195,8 @@ MHD_Result handle_put_object(struct MHD_Connection *connection, const std::string &client_id, const std::string &body, const std::string &metadata) { - auto it = client_cache.find(client_id); - if (it == client_cache.end()) { + auto client = get_client(client_id); + if (!client) { return send_response(connection, 404, "{\"error\":\"Client not found\"}"); } @@ -203,7 +211,7 @@ MHD_Result handle_put_object(struct MHD_Connection *connection, auto stream = std::make_shared(body); request.SetBody(stream); - auto outcome = it->second->PutObject(request, kmsContextMap); + auto outcome = client->PutObject(request, kmsContextMap); if (outcome.IsSuccess()) { json response = {{"bucket", bucket}, {"key", key}}; return send_response(connection, 200, response.dump()); diff --git a/test-server/cpp-v2-transition-server/main.cpp b/test-server/cpp-v2-transition-server/main.cpp index 1fcedc3c..ff62a356 100644 --- a/test-server/cpp-v2-transition-server/main.cpp +++ b/test-server/cpp-v2-transition-server/main.cpp @@ -16,8 +16,8 @@ using json = nlohmann::json; using namespace Aws::S3Encryption; using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; -std::unordered_map> - client_cache; +std::unordered_map> client_cache_secret; +std::mutex client_mutex; std::string generate_uuid() { uuid_t uuid; @@ -27,6 +27,23 @@ std::string generate_uuid() { return std::string(uuid_str); } +std::shared_ptr get_client(const std::string &client_id) +{ + std::lock_guard lock(client_mutex); + auto it = client_cache_secret.find(client_id); + if (it == client_cache_secret.end()) { + return std::shared_ptr(); + } else { + return it->second; + } +} + +void set_client(const std::string &client_id, std::shared_ptr client) +{ + std::lock_guard lock(client_mutex); + client_cache_secret[client_id] = client; +} + std::string get_header_value(struct MHD_Connection *connection, const char *key) { const char *value = @@ -99,7 +116,7 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, auto encryption_client = std::make_shared(config); std::string client_id = generate_uuid(); - client_cache[client_id] = encryption_client; + set_client(client_id, encryption_client); json response = {{"clientId", client_id}}; return send_response(connection, 200, response.dump()); @@ -169,8 +186,8 @@ MHD_Result handle_get_object(struct MHD_Connection *connection, const std::string &bucket, const std::string &key, const std::string &client_id, const std::string &metadata) { - auto it = client_cache.find(client_id); - if (it == client_cache.end()) { + auto client = get_client(client_id); + if (!client) { return send_response(connection, 404, "{\"error\":\"Client not found\"}"); } @@ -179,18 +196,9 @@ MHD_Result handle_get_object(struct MHD_Connection *connection, request.SetBucket(bucket); request.SetKey(key); - // S3EncryptionGetObjectOutcome outcome ; - // if (metadata.empty()) { - // outcome = it->second->GetObject(request); - // } else { - // Aws::Map kmsContextMap; - // fill_context(kmsContextMap, metadata); - // outcome = it->second->GetObject(request, kmsContextMap); - // } - Aws::Map kmsContextMap; fill_context(kmsContextMap, metadata); - auto outcome = it->second->GetObject(request, kmsContextMap); + auto outcome = client->GetObject(request, kmsContextMap); if (outcome.IsSuccess()) { auto &stream = outcome.GetResult().GetBody(); @@ -212,8 +220,8 @@ MHD_Result handle_put_object(struct MHD_Connection *connection, const std::string &client_id, const std::string &body, const std::string &metadata) { - auto it = client_cache.find(client_id); - if (it == client_cache.end()) { + auto client = get_client(client_id); + if (!client) { return send_response(connection, 404, "{\"error\":\"Client not found\"}"); } @@ -228,7 +236,7 @@ MHD_Result handle_put_object(struct MHD_Connection *connection, auto stream = std::make_shared(body); request.SetBody(stream); - auto outcome = it->second->PutObject(request, kmsContextMap); + auto outcome = client->PutObject(request, kmsContextMap); if (outcome.IsSuccess()) { json response = {{"bucket", bucket}, {"key", key}}; return send_response(connection, 200, response.dump()); diff --git a/test-server/cpp-v3-server/main.cpp b/test-server/cpp-v3-server/main.cpp index 1f74974c..814bed34 100644 --- a/test-server/cpp-v3-server/main.cpp +++ b/test-server/cpp-v3-server/main.cpp @@ -12,12 +12,13 @@ #include #include #include +#include using json = nlohmann::json; using namespace Aws::S3Encryption; using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; -std::unordered_map> - client_cache; +std::unordered_map> client_cache_secret; +std::mutex client_mutex; std::string generate_uuid() { uuid_t uuid; @@ -27,6 +28,23 @@ std::string generate_uuid() { return std::string(uuid_str); } +std::shared_ptr get_client(const std::string &client_id) +{ + std::lock_guard lock(client_mutex); + auto it = client_cache_secret.find(client_id); + if (it == client_cache_secret.end()) { + return std::shared_ptr(); + } else { + return it->second; + } +} + +void set_client(const std::string &client_id, std::shared_ptr client) +{ + std::lock_guard lock(client_mutex); + client_cache_secret[client_id] = client; +} + std::string get_header_value(struct MHD_Connection *connection, const char *key) { const char *value = @@ -104,7 +122,7 @@ MHD_Result handle_create_client(struct MHD_Connection *connection, auto encryption_client = std::make_shared(config); std::string client_id = generate_uuid(); - client_cache[client_id] = encryption_client; + set_client(client_id, encryption_client); json response = {{"clientId", client_id}}; return send_response(connection, 200, response.dump()); @@ -175,8 +193,8 @@ MHD_Result handle_get_object(struct MHD_Connection *connection, const std::string &bucket, const std::string &key, const std::string &client_id, const std::string &metadata) { - auto it = client_cache.find(client_id); - if (it == client_cache.end()) { + auto client = get_client(client_id); + if (!client) { return send_response(connection, 404, "{\"error\":\"Client not found\"}"); } @@ -185,18 +203,9 @@ MHD_Result handle_get_object(struct MHD_Connection *connection, request.SetBucket(bucket); request.SetKey(key); - // S3EncryptionGetObjectOutcome outcome ; - // if (metadata.empty()) { - // outcome = it->second->GetObject(request); - // } else { - // Aws::Map kmsContextMap; - // fill_context(kmsContextMap, metadata); - // outcome = it->second->GetObject(request, kmsContextMap); - // } - Aws::Map kmsContextMap; fill_context(kmsContextMap, metadata); - auto outcome = it->second->GetObject(request, kmsContextMap); + auto outcome = client->GetObject(request, kmsContextMap); if (outcome.IsSuccess()) { auto &stream = outcome.GetResult().GetBody(); @@ -220,8 +229,8 @@ MHD_Result handle_put_object(struct MHD_Connection *connection, const std::string &client_id, const std::string &body, const std::string &metadata) { - auto it = client_cache.find(client_id); - if (it == client_cache.end()) { + auto client = get_client(client_id); + if (!client) { return send_response(connection, 404, "{\"error\":\"Client not found\"}"); } @@ -236,7 +245,7 @@ MHD_Result handle_put_object(struct MHD_Connection *connection, auto stream = std::make_shared(body); request.SetBody(stream); - auto outcome = it->second->PutObject(request, kmsContextMap); + auto outcome = client->PutObject(request, kmsContextMap); if (outcome.IsSuccess()) { json response = {{"bucket", bucket}, {"key", key}}; return send_response(connection, 200, response.dump()); From 40333f1859e1d4fabc70233e800fd0cf5e41855f Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 10:53:04 -0800 Subject: [PATCH 24/36] what is going on? --- test-server/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test-server/Makefile b/test-server/Makefile index 175396b9..0469255f 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -21,9 +21,13 @@ build-all-servers: @echo "[`date +%H:%M:%S`] Building all servers..." @$(MAKE) $(BUILD_SERVER_TARGETS) @echo "[`date +%H:%M:%S`] All servers built." + @./gradlew --status @echo "Stopping gradle daemons..." - @./gradlew --stop 2>/dev/null || true + @./gradlew --stop @echo "[`date +%H:%M:%S`] Gradle daemons stopped" + @./gradlew --status + @dotnet build-server shutdown + @echo "[`date +%H:%M:%S`] Dotnet build servers stopped" $(BUILD_SERVER_TARGETS): build-%: @if [ -f $*/Makefile ]; then \ From 81ea5956fafdea60779cdfcc928fdf97fade06e7 Mon Sep 17 00:00:00 2001 From: Andy Jewell Date: Tue, 18 Nov 2025 14:24:10 -0500 Subject: [PATCH 25/36] m --- test-server/cpp-v2-server/main.cpp | 8 ++++---- test-server/cpp-v2-transition-server/main.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test-server/cpp-v2-server/main.cpp b/test-server/cpp-v2-server/main.cpp index b1d87923..1b2de659 100644 --- a/test-server/cpp-v2-server/main.cpp +++ b/test-server/cpp-v2-server/main.cpp @@ -16,7 +16,7 @@ using json = nlohmann::json; using namespace Aws::S3Encryption; using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; -std::unordered_map> client_cache_secret; +std::unordered_map> client_cache_secret; std::mutex client_mutex; std::string generate_uuid() { @@ -27,18 +27,18 @@ std::string generate_uuid() { return std::string(uuid_str); } -std::shared_ptr get_client(const std::string &client_id) +std::shared_ptr get_client(const std::string &client_id) { std::lock_guard lock(client_mutex); auto it = client_cache_secret.find(client_id); if (it == client_cache_secret.end()) { - return std::shared_ptr(); + return std::shared_ptr(); } else { return it->second; } } -void set_client(const std::string &client_id, std::shared_ptr client) +void set_client(const std::string &client_id, std::shared_ptr client) { std::lock_guard lock(client_mutex); client_cache_secret[client_id] = client; diff --git a/test-server/cpp-v2-transition-server/main.cpp b/test-server/cpp-v2-transition-server/main.cpp index ff62a356..f223a1df 100644 --- a/test-server/cpp-v2-transition-server/main.cpp +++ b/test-server/cpp-v2-transition-server/main.cpp @@ -16,7 +16,7 @@ using json = nlohmann::json; using namespace Aws::S3Encryption; using Aws::S3Encryption::Materials::KMSWithContextEncryptionMaterials; -std::unordered_map> client_cache_secret; +std::unordered_map> client_cache_secret; std::mutex client_mutex; std::string generate_uuid() { @@ -27,18 +27,18 @@ std::string generate_uuid() { return std::string(uuid_str); } -std::shared_ptr get_client(const std::string &client_id) +std::shared_ptr get_client(const std::string &client_id) { std::lock_guard lock(client_mutex); auto it = client_cache_secret.find(client_id); if (it == client_cache_secret.end()) { - return std::shared_ptr(); + return std::shared_ptr(); } else { return it->second; } } -void set_client(const std::string &client_id, std::shared_ptr client) +void set_client(const std::string &client_id, std::shared_ptr client) { std::lock_guard lock(client_mutex); client_cache_secret[client_id] = client; From 25ccb41558348654db86a7cfa51bd5818401e586 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 11:33:58 -0800 Subject: [PATCH 26/36] run in the right place --- test-server/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-server/Makefile b/test-server/Makefile index 0469255f..1eda7f70 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -21,11 +21,11 @@ build-all-servers: @echo "[`date +%H:%M:%S`] Building all servers..." @$(MAKE) $(BUILD_SERVER_TARGETS) @echo "[`date +%H:%M:%S`] All servers built." - @./gradlew --status + @./java-v4-server/gradlew --status @echo "Stopping gradle daemons..." - @./gradlew --stop + @./java-v4-server/gradlew --stop @echo "[`date +%H:%M:%S`] Gradle daemons stopped" - @./gradlew --status + @./java-v4-server/gradlew --status @dotnet build-server shutdown @echo "[`date +%H:%M:%S`] Dotnet build servers stopped" From eb89d5e59ab479741267b06e06bb4ee1ce57e52d Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 11:53:24 -0800 Subject: [PATCH 27/36] its dotnet --- test-server/Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test-server/Makefile b/test-server/Makefile index 1eda7f70..28f6e1be 100644 --- a/test-server/Makefile +++ b/test-server/Makefile @@ -21,11 +21,7 @@ build-all-servers: @echo "[`date +%H:%M:%S`] Building all servers..." @$(MAKE) $(BUILD_SERVER_TARGETS) @echo "[`date +%H:%M:%S`] All servers built." - @./java-v4-server/gradlew --status - @echo "Stopping gradle daemons..." - @./java-v4-server/gradlew --stop - @echo "[`date +%H:%M:%S`] Gradle daemons stopped" - @./java-v4-server/gradlew --status + @echo "Stopping dotnet servers... this is here to speed up CI because if this target is run with -j it will stay open until it shutdown." @dotnet build-server shutdown @echo "[`date +%H:%M:%S`] Dotnet build servers stopped" From 4b50ced8b29907ffb86d5e690dfb5ed63020dda5 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 12:01:00 -0800 Subject: [PATCH 28/36] faster c? --- test-server/cpp-v2-server/Makefile | 2 +- test-server/cpp-v2-transition-server/Makefile | 2 +- test-server/cpp-v3-server/Makefile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-server/cpp-v2-server/Makefile b/test-server/cpp-v2-server/Makefile index 77357c37..e4d2f954 100644 --- a/test-server/cpp-v2-server/Makefile +++ b/test-server/cpp-v2-server/Makefile @@ -11,7 +11,7 @@ build/s3ec-server: build-server: | build/s3ec-server @echo "Building Cpp V2 server..." - cd build && make + cd build && $(MAKE) start-server: @echo "Starting Cpp V2 server..." diff --git a/test-server/cpp-v2-transition-server/Makefile b/test-server/cpp-v2-transition-server/Makefile index 16b70796..2ca6ee5f 100644 --- a/test-server/cpp-v2-transition-server/Makefile +++ b/test-server/cpp-v2-transition-server/Makefile @@ -10,7 +10,7 @@ build/s3ec-server: build-server: | build/s3ec-server @echo "Building Cpp transition server..." - cd build && make + cd build && $(MAKE) start-server: @echo "Starting Cpp transition server..." diff --git a/test-server/cpp-v3-server/Makefile b/test-server/cpp-v3-server/Makefile index 46f0c9db..05a286f0 100644 --- a/test-server/cpp-v3-server/Makefile +++ b/test-server/cpp-v3-server/Makefile @@ -10,7 +10,7 @@ build/s3ec-server: build-server: | build/s3ec-server @echo "Building Cpp V3 server..." - cd build && make + cd build && $(MAKE) start-server: @echo "Starting Cpp V3 server..." From 23f3f163971926b472387caca228f8c87e658ed2 Mon Sep 17 00:00:00 2001 From: Andy Jewell Date: Tue, 18 Nov 2025 15:09:13 -0500 Subject: [PATCH 29/36] m --- test-server/cpp-v3-server/CMakeLists.txt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/test-server/cpp-v3-server/CMakeLists.txt b/test-server/cpp-v3-server/CMakeLists.txt index b282dbc4..0faac5f0 100644 --- a/test-server/cpp-v3-server/CMakeLists.txt +++ b/test-server/cpp-v3-server/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD 17) set(BUILD_ONLY "kms;s3;s3-encryption" CACHE STRING "Build only KMS, S3, and S3-encryption components") set(ENABLE_TESTING OFF CACHE BOOL "Disable testing") set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries") +set(ENABLE_ADDRESS_SANITIZER ON CACHE BOOL "Enable Address Sanitizer") # Add AWS SDK as subdirectory add_subdirectory(aws-sdk-cpp) @@ -18,12 +19,21 @@ find_package(nlohmann_json REQUIRED) add_executable(s3ec-server main.cpp) -target_include_directories(s3ec-server PRIVATE +# Enable Address Sanitizer for the executable +target_compile_options(s3ec-server PRIVATE -fsanitize=address -fno-omit-frame-pointer) +target_link_options(s3ec-server PRIVATE -fsanitize=address) + +target_include_directories(s3ec-server PRIVATE + ${LIBMICROHTTPD_INCLUDE_DIRS} + /opt/homebrew/include +) + +target_include_directories(s3ec-server PRIVATE ${LIBMICROHTTPD_INCLUDE_DIRS} /opt/homebrew/include ) -target_link_directories(s3ec-server PRIVATE +target_link_directories(s3ec-server PRIVATE ${LIBMICROHTTPD_LIBRARY_DIRS} /opt/homebrew/lib ) @@ -36,4 +46,4 @@ target_link_libraries(s3ec-server aws-cpp-sdk-s3-encryption nlohmann_json::nlohmann_json uuid -) \ No newline at end of file +) From 393608955999edef2bf7fceb1703aa68b94e6da1 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 12:29:13 -0800 Subject: [PATCH 30/36] disable cpp for a sec --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 961f1e63..210e5e7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -155,25 +155,25 @@ jobs: aws-region: us-west-2 - name: Build the servers - run: cd test-server && make build-all-servers && echo "[`date +%H:%M:%S`] We got to the end?" + run: cd test-server && make build-all-servers FILTER=ruby,java,php,net,go env: MAKEFLAGS: -j${{ steps.cpu-count.outputs.count }} AWS_REGION: us-west-2 - name: Start the servers - run: cd test-server && make start-all-servers + run: cd test-server && make start-all-servers FILTER=ruby,java,php,net,go env: AWS_REGION: us-west-2 TEST_SERVER_S3_BUCKET: ${{ vars.TEST_SERVER_S3_BUCKET }} TEST_SERVER_KMS_KEY_ARN: ${{ vars.TEST_SERVER_KMS_KEY_ARN }} - name: Wait for servers to start - run: cd test-server && make wait-all-servers + run: cd test-server && make wait-all-servers FILTER=ruby,java,php,net,go env: MAKEFLAGS: -j${{ steps.cpu-count.outputs.count }} - name: Run run-tests - run: cd test-server && make run-tests + run: cd test-server && make run-tests FILTER=ruby,java,php,net,go env: AWS_REGION: us-west-2 TEST_SERVER_S3_BUCKET: ${{ vars.TEST_SERVER_S3_BUCKET }} From ab14133f276712d42290a24e46e214c9c3fc1889 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 15:12:38 -0800 Subject: [PATCH 31/36] fix for Java? --- test-server/java-tests/build.gradle.kts | 4 ++-- test-server/java-v4-server/s3ec-staging | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-server/java-tests/build.gradle.kts b/test-server/java-tests/build.gradle.kts index 2ac73a4e..14c3eec1 100644 --- a/test-server/java-tests/build.gradle.kts +++ b/test-server/java-tests/build.gradle.kts @@ -61,8 +61,8 @@ tasks { systemProperty("junit.jupiter.execution.parallel.config.strategy", "fixed") maxParallelForks = 1 // One JVM systemProperty("junit.jupiter.execution.parallel.config.fixed.parallelism", - Runtime.getRuntime().availableProcessors().toString()) // Scale with CPU - + Math.max(1, Runtime.getRuntime().availableProcessors() - 2).toString()) // Scale with CPU, reserve 2 cores + // Passing information from Gradle into the tests so that we can filter our servers systemProperty("test.filter.servers", System.getProperty("test.filter.servers")) // For debugging diff --git a/test-server/java-v4-server/s3ec-staging b/test-server/java-v4-server/s3ec-staging index db0c743e..5b864b14 160000 --- a/test-server/java-v4-server/s3ec-staging +++ b/test-server/java-v4-server/s3ec-staging @@ -1 +1 @@ -Subproject commit db0c743eec335d16e6dceaf2b09d84becb0f74f8 +Subproject commit 5b864b145bce9c6ce758cee483244c16c06018b6 From d5d8b6ab3a825785456228b042c94b1233d9fe1c Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 15:30:28 -0800 Subject: [PATCH 32/36] java transition --- test-server/java-v3-transition-server/s3ec-staging | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-server/java-v3-transition-server/s3ec-staging b/test-server/java-v3-transition-server/s3ec-staging index 6413811b..57f54f53 160000 --- a/test-server/java-v3-transition-server/s3ec-staging +++ b/test-server/java-v3-transition-server/s3ec-staging @@ -1 +1 @@ -Subproject commit 6413811bb81037999b8238e02047e0e403f78c1f +Subproject commit 57f54f531df343007632b78e597709f702a1c8e8 From e9ea9f5344ec6f47de90b1ae346847157b631895 Mon Sep 17 00:00:00 2001 From: Rishav karanjit Date: Tue, 18 Nov 2025 20:54:55 -0800 Subject: [PATCH 33/36] chore: bump s3ec net to get the fix for raw wrapping key cek alg (#108) Update to the latest dotNet to interoperate raw keys --- .../go-v3-transition-server/local-go-s3ec | 2 +- test-server/go-v4-server/local-go-s3ec | 2 +- .../amazon/encryption/s3/TestUtils.java | 46 +++++++++++++------ .../s3ec-v3-transition-branch | 2 +- .../net-v4-server/s3ec-net-v4-improved | 2 +- .../src/get_object.php | 4 ++ test-server/php-v3-server/local-php-sdk | 2 +- test-server/php-v3-server/src/get_object.php | 4 ++ 8 files changed, 44 insertions(+), 20 deletions(-) diff --git a/test-server/go-v3-transition-server/local-go-s3ec b/test-server/go-v3-transition-server/local-go-s3ec index f51a4402..e59a38ca 160000 --- a/test-server/go-v3-transition-server/local-go-s3ec +++ b/test-server/go-v3-transition-server/local-go-s3ec @@ -1 +1 @@ -Subproject commit f51a4402c741cd989c7984336de560e9c54baf17 +Subproject commit e59a38caeddfcfbf41e064e125b5783cdfce3878 diff --git a/test-server/go-v4-server/local-go-s3ec b/test-server/go-v4-server/local-go-s3ec index f51a4402..e59a38ca 160000 --- a/test-server/go-v4-server/local-go-s3ec +++ b/test-server/go-v4-server/local-go-s3ec @@ -1 +1 @@ -Subproject commit f51a4402c741cd989c7984336de560e9c54baf17 +Subproject commit e59a38caeddfcfbf41e064e125b5783cdfce3878 diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index 90995dde..351f2c8c 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -548,23 +548,39 @@ public static void Decrypt( EncryptionAlgorithm expectedEncryptionAlgorithm, List expectedPlaintexts ) { + List failures = new ArrayList<>(); for (int i = 0; i < crossLanguageObjects.size(); i++) { - String objectKey = crossLanguageObjects.get(i); - String expectedPlaintext = expectedPlaintexts.get(i); - - GetObjectOutput output = client.getObject(GetObjectInput.builder() - .clientID(S3ECId) - .bucket(TestUtils.BUCKET) - .key(objectKey) - .build()); + try { + String objectKey = crossLanguageObjects.get(i); + String expectedPlaintext = expectedPlaintexts.get(i); + + GetObjectOutput output = client.getObject(GetObjectInput.builder() + .clientID(S3ECId) + .bucket(TestUtils.BUCKET) + .key(objectKey) + .build()); - // Then: Pass - assertEquals(expectedPlaintext, new String(output.getBody().array())); - assertEquals( - expectedEncryptionAlgorithm, - GetEncryptionAlgorithm(objectKey), - "When decrypting the EncryptionAlgorithm does not match the expected value: " + expectedEncryptionAlgorithm - ); + // Then: Pass + assertEquals(expectedPlaintext, new String(output.getBody().array())); + assertEquals( + expectedEncryptionAlgorithm, + GetEncryptionAlgorithm(objectKey), + "When decrypting the EncryptionAlgorithm does not match the expected value: " + expectedEncryptionAlgorithm + ); + } catch (Exception e) { + failures.add(String.format( + "Failed to decrypt object '%s' (index %d): %s - %s", + crossLanguageObjects.get(i), i, e.getClass().getSimpleName(), e.getMessage() + )); + } + } + + if (!failures.isEmpty()) { + throw new AssertionError(String.format( + "Decryption failed for %d out of %d objects:\n%s", + failures.size(), crossLanguageObjects.size(), + String.join("\n", failures) + )); } } diff --git a/test-server/net-v3-transition-server/s3ec-v3-transition-branch b/test-server/net-v3-transition-server/s3ec-v3-transition-branch index ad825917..d099cfd1 160000 --- a/test-server/net-v3-transition-server/s3ec-v3-transition-branch +++ b/test-server/net-v3-transition-server/s3ec-v3-transition-branch @@ -1 +1 @@ -Subproject commit ad8259173de365a13e8b3932ee02493f599f597f +Subproject commit d099cfd151e2c61fb97dcd417828fb1dd5468b0c diff --git a/test-server/net-v4-server/s3ec-net-v4-improved b/test-server/net-v4-server/s3ec-net-v4-improved index 1c0a458c..8ce8983b 160000 --- a/test-server/net-v4-server/s3ec-net-v4-improved +++ b/test-server/net-v4-server/s3ec-net-v4-improved @@ -1 +1 @@ -Subproject commit 1c0a458c19b351c266199c72072de746362c5326 +Subproject commit 8ce8983bd0edf973651aee0c29894df9091cf97a diff --git a/test-server/php-v2-transition-server/src/get_object.php b/test-server/php-v2-transition-server/src/get_object.php index 5800e850..dcf683b6 100644 --- a/test-server/php-v2-transition-server/src/get_object.php +++ b/test-server/php-v2-transition-server/src/get_object.php @@ -80,6 +80,10 @@ function handleGetObject($params) } if (strpos($e->getMessage(), "@SecurityProfile=V2") !== false) { return S3EncryptionClientError($e->getMessage() . " " . "Enable legacy wrapping algorithms to use legacy key wrapping algorithm: kms"); + } elseif (strpos($e->getMessage(), "One or more reserved keys found in Instruction file when they should not be present.") !== false) { + return S3EncryptionClientError($e->getMessage()); + } elseif (strpos($e->getMessage(), "Expected a V3 envelope but was unable to constuct one.") !== false) { + return S3EncryptionClientError($e->getMessage()); } else { error_log("This is the error: " . $e->getMessage()); return GenericServerError("Server error: " . $e->getMessage(), 500); diff --git a/test-server/php-v3-server/local-php-sdk b/test-server/php-v3-server/local-php-sdk index e32c9f2b..88ee9515 160000 --- a/test-server/php-v3-server/local-php-sdk +++ b/test-server/php-v3-server/local-php-sdk @@ -1 +1 @@ -Subproject commit e32c9f2b009a43cf88f2ab35e1e532114c8390c9 +Subproject commit 88ee95156f2884767b72f9219736e976d98a9c96 diff --git a/test-server/php-v3-server/src/get_object.php b/test-server/php-v3-server/src/get_object.php index 3de7f779..6fb28551 100644 --- a/test-server/php-v3-server/src/get_object.php +++ b/test-server/php-v3-server/src/get_object.php @@ -84,6 +84,10 @@ function handleGetObject($params) return S3EncryptionClientError($e->getMessage()); } elseif (strpos($e->getMessage(), "Message is encrypted with a non commiting algorithm but commitment policy is set to REQUIRE_ENCRYPT_REQUIRE_DECRYPT. Select a valid commitment policy to decrypt this object.") !== false) { return S3EncryptionClientError($e->getMessage()); + } elseif (strpos($e->getMessage(), "One or more reserved keys found in Instruction file when they should not be present.") !== false) { + return S3EncryptionClientError($e->getMessage()); + } elseif (strpos($e->getMessage(), "Expected a V3 envelope but was unable to constuct one.") !== false) { + return S3EncryptionClientError($e->getMessage()); } else { error_log("This is the error: " . $e->getMessage()); return GenericServerError("Server argument: " . $e->getMessage(), 500); From 9b5fc73bf8d0048ea04cbb3a42571afe4d2ee4d1 Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 19:03:16 -0800 Subject: [PATCH 34/36] better error and better Java --- .../src/it/java/software/amazon/encryption/s3/TestUtils.java | 2 ++ test-server/java-v3-transition-server/s3ec-staging | 2 +- test-server/java-v4-server/s3ec-staging | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java index 351f2c8c..a7a86c1d 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/TestUtils.java @@ -541,6 +541,8 @@ public static void Decrypt( Decrypt(client, S3ECId, crossLanguageObjects, expectedEncryptionAlgorithm, crossLanguageObjects); } + + public static void Decrypt( S3ECTestServerClient client, String S3ECId, diff --git a/test-server/java-v3-transition-server/s3ec-staging b/test-server/java-v3-transition-server/s3ec-staging index 57f54f53..c0aa283d 160000 --- a/test-server/java-v3-transition-server/s3ec-staging +++ b/test-server/java-v3-transition-server/s3ec-staging @@ -1 +1 @@ -Subproject commit 57f54f531df343007632b78e597709f702a1c8e8 +Subproject commit c0aa283d424c75788b39bf6a9e91be52910e41a4 diff --git a/test-server/java-v4-server/s3ec-staging b/test-server/java-v4-server/s3ec-staging index 5b864b14..0cddde1d 160000 --- a/test-server/java-v4-server/s3ec-staging +++ b/test-server/java-v4-server/s3ec-staging @@ -1 +1 @@ -Subproject commit 5b864b145bce9c6ce758cee483244c16c06018b6 +Subproject commit 0cddde1d49bd8193e382ab53dda4f553049992c6 From 8fbcf0c857bb3ea0e093802da0a464947185c33c Mon Sep 17 00:00:00 2001 From: Ryan Emery Date: Tue, 18 Nov 2025 19:07:26 -0800 Subject: [PATCH 35/36] another test --- .../amazon/encryption/s3/KC_GCMTestSuite.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java index e88a97ff..c367315f 100644 --- a/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java +++ b/test-server/java-tests/src/it/java/software/amazon/encryption/s3/KC_GCMTestSuite.java @@ -357,5 +357,33 @@ void improved_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_ TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); } + + @ParameterizedTest(name = "{0}: Transition configured with default should decrypt KC-GCM (instruction file)") + @MethodSource("software.amazon.encryption.s3.TestUtils#transitionClientsForTest") + void transition_configured_with_require_encrypt_require_decrypt_should_decrypt_kc_gcm_ins_file( + final TestUtils.LanguageServerTarget language + ) { + if (!RAW_SUPPORTED.contains(language.getLanguageName())) { + throw new TestAbortedException("Not encrypting raw keyring with: " + language.getLanguageName()); + } + + KeyMaterial rsaKey = KeyMaterial.builder() + .rsaKey(ByteBuffer.wrap(RSA_KEY_PAIR_1.getPrivate().getEncoded())) + .build(); + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build()) + .keyMaterial(rsaKey).build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + TestUtils.Decrypt(client, S3ECId, crossLanguageObjectsInstructionFiles, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY); + } + } } From d53f0c63fe7ae50c3d5c650785e0a5e678d6149d Mon Sep 17 00:00:00 2001 From: Darwin Chowdary <39110935+imabhichow@users.noreply.github.com> Date: Tue, 18 Nov 2025 22:17:39 -0800 Subject: [PATCH 36/36] bump java commit --- test-server/java-v3-transition-server/s3ec-staging | 2 +- test-server/java-v4-server/s3ec-staging | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-server/java-v3-transition-server/s3ec-staging b/test-server/java-v3-transition-server/s3ec-staging index c0aa283d..597d5b49 160000 --- a/test-server/java-v3-transition-server/s3ec-staging +++ b/test-server/java-v3-transition-server/s3ec-staging @@ -1 +1 @@ -Subproject commit c0aa283d424c75788b39bf6a9e91be52910e41a4 +Subproject commit 597d5b491ac578f5d03d3fa757201eb48690cd00 diff --git a/test-server/java-v4-server/s3ec-staging b/test-server/java-v4-server/s3ec-staging index 0cddde1d..2626ed6e 160000 --- a/test-server/java-v4-server/s3ec-staging +++ b/test-server/java-v4-server/s3ec-staging @@ -1 +1 @@ -Subproject commit 0cddde1d49bd8193e382ab53dda4f553049992c6 +Subproject commit 2626ed6e312c1c5e01abea2f30727ea0f2af299d