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 6460fbad..0096a8bf 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 @@ -89,6 +89,12 @@ class EncryptTests { Collections.synchronizedList(new ArrayList<>()); private static final List crossLanguageObjectsAes = Collections.synchronizedList(new ArrayList<>()); + + // Thread-safe lists for envelope merge tests + private static final List crossLanguageObjectsMetadataOnly = + Collections.synchronizedList(new ArrayList<>()); + private static final List crossLanguageObjectsInstructionFileDeleted = + Collections.synchronizedList(new ArrayList<>()); private static KeyMaterial RSA_KEY; private static KeyMaterial AES_KEY; @@ -139,6 +145,14 @@ static KeyMaterial getKmsKeyArn() { return kmsKeyArn; } + static List getCrossLanguageObjectsMetadataOnly() { + return new ArrayList<>(crossLanguageObjectsMetadataOnly); + } + + static List getCrossLanguageObjectsInstructionFileDeleted() { + return new ArrayList<>(crossLanguageObjectsInstructionFileDeleted); + } + public static Stream improvedClientsCanPutKMSWithInstructionFile() { return improvedClientsForTest() .filter(target -> !INSTRUCTION_FILE_PUT_UNSUPPORTED.contains(((LanguageServerTarget) target.get()[0]).getLanguageName())) @@ -234,6 +248,55 @@ void encryptWithInstructionFilesAesKcGcm(TestUtils.LanguageServerTarget language ); } + @ParameterizedTest(name = "{0}: Encrypt RSA KC-GCM metadata-only for envelope merge test") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures$EncryptTests#improvedClientsCanPutRawRSAWithInstructionFile") + void encryptMetadataOnlyRsaKcGcm(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + // Encrypt with metadata-only (no instruction file) + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .build()) + .build()); + + String S3ECId = clientOutput.getClientId(); + + TestUtils.Encrypt( + client, + S3ECId, + appendTestSuffix(sharedObjectKeyBaseMetaDataMode + "-envelope-merge-metadata-only-" + language.getLanguageName()), + crossLanguageObjectsMetadataOnly, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @ParameterizedTest(name = "{0}: Encrypt RSA KC-GCM with instruction file for deletion test") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures$EncryptTests#improvedClientsCanPutRawRSAWithInstructionFile") + void encryptWithInstructionFileForDeletionRsaKcGcm(TestUtils.LanguageServerTarget language) { + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + // Encrypt with instruction file (will be deleted later) + 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 + "-envelope-merge-instruction-deleted-" + language.getLanguageName()), + crossLanguageObjectsInstructionFileDeleted, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + static void makeCopiesToVerifyThings() throws Exception { // Create a plaintext S3 client to copy objects with instruction files try (S3Client ptS3Client = S3Client.create()) { @@ -295,6 +358,19 @@ static void makeCopiesToVerifyThings() throws Exception { ); } + + // Delete instruction files for envelope merge tests + for (String objectKey : crossLanguageObjectsInstructionFileDeleted) { + String instructionFileKey = objectKey + ".instruction"; + try { + ptS3Client.deleteObject(builder -> builder + .bucket(TestUtils.BUCKET) + .key(instructionFileKey) + .build()); + } catch (Exception e) { + // Ignore if file doesn't exist + } + } } } @@ -344,6 +420,8 @@ class DecryptTests { private static List crossLanguageObjectsKms; private static List crossLanguageObjectsRsa; private static List crossLanguageObjectsAes; + private static List crossLanguageObjectsMetadataOnly; + private static List crossLanguageObjectsInstructionFileDeleted; private static KeyMaterial kmsKeyArn; private static KeyMaterial RSA_KEY; private static KeyMaterial AES_KEY; @@ -357,6 +435,8 @@ static void setup() throws InterruptedException { crossLanguageObjectsKms = EncryptTests.getCrossLanguageObjectsKms(); crossLanguageObjectsRsa = EncryptTests.getCrossLanguageObjectsRsa(); crossLanguageObjectsAes = EncryptTests.getCrossLanguageObjectsAes(); + crossLanguageObjectsMetadataOnly = EncryptTests.getCrossLanguageObjectsMetadataOnly(); + crossLanguageObjectsInstructionFileDeleted = EncryptTests.getCrossLanguageObjectsInstructionFileDeleted(); kmsKeyArn = EncryptTests.getKmsKeyArn(); RSA_KEY = EncryptTests.getRsaKey(); AES_KEY = EncryptTests.getAesKey(); @@ -784,5 +864,86 @@ void decryptAesWithInstructionFileCommitmentFailsWithForbidPolicy(TestUtils.Lang EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY ); } + + // Envelope merge tests + + @ParameterizedTest(name = "{0}: Successfully decrypt metadata-only object with instruction file config") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures$DecryptTests#clientsCanGetRawRSAWithInstructionFile") + void decryptMetadataOnlyObjectWithInstructionFileConfigSucceeds(TestUtils.LanguageServerTarget language) { + if (crossLanguageObjectsMetadataOnly.isEmpty()) return; + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + // Configure client to look for instruction file but metadata has complete envelope + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .instructionFileConfig( + InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build() + ) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + // Should succeed - instruction file doesn't exist but metadata has complete envelope + TestUtils.Decrypt( + client, + S3ECId, + crossLanguageObjectsMetadataOnly, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @ParameterizedTest(name = "{0}: Fail to decrypt when metadata incomplete and instruction file deleted") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures$DecryptTests#clientsCanGetRawRSAWithInstructionFile") + void decryptWithIncompleteMetadataAndNoInstructionFileFails(TestUtils.LanguageServerTarget language) { + if (crossLanguageObjectsInstructionFileDeleted.isEmpty()) return; + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + // Configure client for metadata-only but metadata is incomplete + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + // Should fail - metadata incomplete (missing x-amz-3, x-amz-w), instruction file deleted + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsInstructionFileDeleted, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } + + @ParameterizedTest(name = "{0}: Fail to decrypt with instruction file config when file deleted and metadata incomplete") + @MethodSource("software.amazon.encryption.s3.InstructionFileFailures$DecryptTests#clientsCanGetRawRSAWithInstructionFile") + void decryptWithInstructionFileConfigWhenFileDeletedFails(TestUtils.LanguageServerTarget language) { + if (crossLanguageObjectsInstructionFileDeleted.isEmpty()) return; + + S3ECTestServerClient client = TestUtils.testServerClientFor(language); + // Configure client to look for instruction file but it's been deleted + CreateClientOutput clientOutput = client.createClient(CreateClientInput.builder() + .config(S3ECConfig.builder() + .keyMaterial(RSA_KEY) + .instructionFileConfig( + InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .build() + ) + .build()) + .build()); + String S3ECId = clientOutput.getClientId(); + + // Should fail - instruction file deleted, metadata incomplete (missing x-amz-3, x-amz-w) + TestUtils.Decrypt_fails( + client, + S3ECId, + crossLanguageObjectsInstructionFileDeleted, + EncryptionAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY + ); + } } } diff --git a/test-server/ruby-v2-server/Gemfile.lock b/test-server/ruby-v2-server/Gemfile.lock index c253552a..b9f08375 100644 --- a/test-server/ruby-v2-server/Gemfile.lock +++ b/test-server/ruby-v2-server/Gemfile.lock @@ -1,15 +1,15 @@ PATH remote: local-ruby-sdk/gems/aws-sdk-kms specs: - aws-sdk-kms (1.113.0) - aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) aws-sigv4 (~> 1.5) PATH remote: local-ruby-sdk/gems/aws-sdk-s3 specs: - aws-sdk-s3 (1.199.1) - aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-s3 (1.206.0) + aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -19,7 +19,7 @@ GEM ast (2.4.3) aws-eventstream (1.4.0) aws-partitions (1.1180.0) - aws-sdk-core (3.236.0) + aws-sdk-core (3.239.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) diff --git a/test-server/ruby-v2-server/local-ruby-sdk b/test-server/ruby-v2-server/local-ruby-sdk index d6b93925..1f32f5b9 160000 --- a/test-server/ruby-v2-server/local-ruby-sdk +++ b/test-server/ruby-v2-server/local-ruby-sdk @@ -1 +1 @@ -Subproject commit d6b93925c65e0ad4b9410ade709c63ca874634c3 +Subproject commit 1f32f5b9ade757b6f2bce0650af43eefe9581d01 diff --git a/test-server/ruby-v3-server/Gemfile.lock b/test-server/ruby-v3-server/Gemfile.lock index c253552a..b9f08375 100644 --- a/test-server/ruby-v3-server/Gemfile.lock +++ b/test-server/ruby-v3-server/Gemfile.lock @@ -1,15 +1,15 @@ PATH remote: local-ruby-sdk/gems/aws-sdk-kms specs: - aws-sdk-kms (1.113.0) - aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-kms (1.118.0) + aws-sdk-core (~> 3, >= 3.239.1) aws-sigv4 (~> 1.5) PATH remote: local-ruby-sdk/gems/aws-sdk-s3 specs: - aws-sdk-s3 (1.199.1) - aws-sdk-core (~> 3, >= 3.231.0) + aws-sdk-s3 (1.206.0) + aws-sdk-core (~> 3, >= 3.234.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -19,7 +19,7 @@ GEM ast (2.4.3) aws-eventstream (1.4.0) aws-partitions (1.1180.0) - aws-sdk-core (3.236.0) + aws-sdk-core (3.239.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) diff --git a/test-server/ruby-v3-server/local-ruby-sdk b/test-server/ruby-v3-server/local-ruby-sdk index d6b93925..1f32f5b9 160000 --- a/test-server/ruby-v3-server/local-ruby-sdk +++ b/test-server/ruby-v3-server/local-ruby-sdk @@ -1 +1 @@ -Subproject commit d6b93925c65e0ad4b9410ade709c63ca874634c3 +Subproject commit 1f32f5b9ade757b6f2bce0650af43eefe9581d01