From 29a455b2339f240ccb98f4a3742451498b5f28f3 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Oct 2026 21:18:03 +0000 Subject: [PATCH 1/2] fix(keyring): validate content algorithm matches selected suite on decrypt The KMS+Context and RSA-OAEP decrypt paths selected the content algorithm suite from the object's content metadata without checking it against the algorithm this data key was bound to. A mismatch (for example a GCM-sealed object whose content-algorithm metadata reads CBC) was accepted, so the object could be processed under a different suite than it was written with. KmsKeyring now rejects a stored aws:x-amz-cek-alg that does not match the selected suite before calling KMS Decrypt. RsaKeyring now accepts only GCM-family selected suites for the RSA-OAEP pseudo-data-key binding. Both preserve the GCM->CTR equivalence used by ranged gets. --- .../encryption/s3/materials/KmsKeyring.java | 29 ++++++ .../encryption/s3/materials/RsaKeyring.java | 23 +++-- .../s3/materials/KmsKeyringTest.java | 56 +++++++++++ .../s3/materials/RsaKeyringTest.java | 99 +++++++++++++++++++ 4 files changed, 201 insertions(+), 6 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java index b52566982..9d133a47a 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/KmsKeyring.java @@ -26,6 +26,7 @@ import software.amazon.awssdk.services.s3.model.S3Request; import software.amazon.encryption.s3.S3EncryptionClient; import software.amazon.encryption.s3.S3EncryptionClientException; +import software.amazon.encryption.s3.algorithms.AlgorithmSuite; import software.amazon.encryption.s3.internal.ApiNameVersion; //= specification/s3-encryption/materials/s3-kms-keyring.md#interface @@ -251,6 +252,17 @@ public byte[] decryptDataKey(DecryptionMaterials materials, byte[] encryptedData throw new S3EncryptionClientException("Provided encryption context does not match information retrieved from S3"); } + // The content encryption algorithm is part of the KMS-authenticated encryption context, while the + // algorithm suite is selected from the (unauthenticated) content metadata. The two must agree; if + // they do not, the content metadata does not correspond to how the object was sealed, so the data + // key is not released for a mismatched suite. + String storedContentAlgorithm = materials.encryptionContext().get(ENCRYPTION_CONTEXT_ALGORITHM_KEY); + if (storedContentAlgorithm != null + && !storedContentAlgorithm.equals(expectedContentAlgorithm(materials.algorithmSuite()))) { + throw new S3EncryptionClientException("The content encryption algorithm in the KMS-authenticated " + + "encryption context does not match the algorithm suite selected for decryption."); + } + //= specification/s3-encryption/materials/s3-kms-keyring.md#kms-context //# To attempt to decrypt a particular [encrypted data key](../structures.md#encrypted-data-key), the KmsKeyring //# MUST call [AWS KMS Decrypt](https://docs.aws.amazon.com/kms/latest/APIReference/API_Decrypt.html) with the configured AWS KMS client. @@ -313,6 +325,23 @@ protected Map decryptDataKeyStrategies() { return decryptDataKeyStrategies; } + // The canonical value the content-encryption algorithm (aws:x-amz-cek-alg) holds in the stored encryption + // context for a given selected algorithm suite. The committing GCM/CTR pair share the committing suite ID, + // and the non-committing GCM/CTR pair share GCM's cipher name, because a ranged get selects the CTR variant + // of an object that was sealed under its GCM counterpart. + private static String expectedContentAlgorithm(AlgorithmSuite algorithmSuite) { + switch (algorithmSuite) { + case ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY: + case ALG_AES_256_CTR_HKDF_SHA512_COMMIT_KEY: + return AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.idAsString(); + case ALG_AES_256_GCM_IV12_TAG16_NO_KDF: + case ALG_AES_256_CTR_IV16_TAG16_NO_KDF: + return AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherName(); + default: + return algorithmSuite.cipherName(); + } + } + public static class Builder extends S3Keyring.Builder { private KmsClient _kmsClient; private String _wrappingKeyId; diff --git a/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java index 74cb66a92..ba4a67bc3 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java @@ -158,13 +158,24 @@ private byte[] parsePseudoDataKey(DecryptionMaterials materials, byte[] pseudoDa throw new S3EncryptionClientException("Invalid key length (" + dataKeyLengthBytes + ") in encrypted data key"); } + // The content algorithm sealed inside the pseudo data key must match the algorithm suite selected + // for decryption. RSA-OAEP-SHA1 objects are only ever written with a GCM-family content suite, so + // the committing GCM/CTR pair maps to the committing suite ID (115) and the non-committing GCM/CTR + // pair maps to GCM's cipher name. Any other selected suite (e.g. CBC) does not correspond to how + // the data key was wrapped and is not accepted. byte[] expectedDataCipherAlg; - // For V3 committed algorithms (both GCM 115 and CTR 116), use GCM's suite ID string (115/0x0073) - if (materials.algorithmSuite().id() == AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.id() || - materials.algorithmSuite().id() == AlgorithmSuite.ALG_AES_256_CTR_HKDF_SHA512_COMMIT_KEY.id()) { - expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.idAsString().getBytes(StandardCharsets.UTF_8); - } else { - expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherName().getBytes(StandardCharsets.UTF_8); + switch (materials.algorithmSuite()) { + case ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY: + case ALG_AES_256_CTR_HKDF_SHA512_COMMIT_KEY: + expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.idAsString().getBytes(StandardCharsets.UTF_8); + break; + case ALG_AES_256_GCM_IV12_TAG16_NO_KDF: + case ALG_AES_256_CTR_IV16_TAG16_NO_KDF: + expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherName().getBytes(StandardCharsets.UTF_8); + break; + default: + throw new S3EncryptionClientException("The algorithm suite selected for decryption does not match " + + "the content algorithm bound to the RSA-wrapped data key. The object may be altered or corrupted"); } int dataCipherNameLength = expectedDataCipherAlg.length; diff --git a/src/test/java/software/amazon/encryption/s3/materials/KmsKeyringTest.java b/src/test/java/software/amazon/encryption/s3/materials/KmsKeyringTest.java index 3a99210dc..7c155c627 100644 --- a/src/test/java/software/amazon/encryption/s3/materials/KmsKeyringTest.java +++ b/src/test/java/software/amazon/encryption/s3/materials/KmsKeyringTest.java @@ -2,13 +2,29 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3.materials; +import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RetryingTest; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.kms.KmsClient; +import software.amazon.awssdk.services.kms.model.DecryptRequest; +import software.amazon.awssdk.services.kms.model.DecryptResponse; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.encryption.s3.S3EncryptionClientException; +import software.amazon.encryption.s3.algorithms.AlgorithmSuite; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class KmsKeyringTest { + private static final String KMS_KEY_ID = "arn:aws:kms:us-west-2:111122223333:key/example"; + @RetryingTest(3) public void buildKmsKeyringWithNullSecureRandomFails() { assertThrows(S3EncryptionClientException.class, () -> KmsKeyring.builder().secureRandom(null)); @@ -19,4 +35,44 @@ public void buildKmsKeyringWithNullDataKeyGeneratorFails() { assertThrows(S3EncryptionClientException.class, () -> KmsKeyring.builder().dataKeyGenerator(null)); } + // The data key was wrapped for a GCM object, so the KMS-authenticated encryption context carries + // aws:x-amz-cek-alg = AES/GCM/NoPadding. The decryption materials select the CBC suite (as they would + // if the content-algorithm metadata read CBC). The authenticated content algorithm and the selected + // suite disagree, so onDecrypt must fail rather than return the data key. + @Test + public void kmsContextDecryptFailsWhenAuthenticatedCekAlgDoesNotMatchSelectedSuite() { + Map storedEncryptionContext = new HashMap<>(); + storedEncryptionContext.put("kms_cmk_id", KMS_KEY_ID); + storedEncryptionContext.put("aws:x-amz-cek-alg", AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherName()); + + // Releases the data key only for the encryption context it was wrapped under. + KmsClient kmsClient = mock(KmsClient.class); + when(kmsClient.decrypt(argThat((DecryptRequest request) -> + storedEncryptionContext.equals(request.encryptionContext())))) + .thenReturn(DecryptResponse.builder() + .keyId(KMS_KEY_ID) + .plaintext(SdkBytes.fromByteArray(new byte[32])) + .build()); + + KmsKeyring keyring = KmsKeyring.builder() + .kmsClient(kmsClient) + .wrappingKeyId(KMS_KEY_ID) + .build(); + + DecryptionMaterials materials = DecryptionMaterials.builder() + .s3Request(GetObjectRequest.builder().bucket("bucket").key("key").build()) + .algorithmSuite(AlgorithmSuite.ALG_AES_256_CBC_IV16_NO_KDF) + .encryptionContext(storedEncryptionContext) + .build(); + + EncryptedDataKey edk = EncryptedDataKey.builder() + .keyProviderId(S3Keyring.KEY_PROVIDER_ID) + .keyProviderInfo("kms+context") + .encryptedDataKey(new byte[]{1, 2, 3}) + .build(); + + assertThrows(S3EncryptionClientException.class, + () -> keyring.onDecrypt(materials, Collections.singletonList(edk))); + } + } diff --git a/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java b/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java index 8069b4f02..fcca9f4ce 100644 --- a/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java +++ b/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java @@ -2,13 +2,41 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3.materials; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RetryingTest; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.encryption.s3.S3EncryptionClientException; +import software.amazon.encryption.s3.algorithms.AlgorithmSuite; +import software.amazon.encryption.s3.internal.ContentMetadata; +import software.amazon.encryption.s3.internal.ContentMetadataDecodingStrategy; +import software.amazon.encryption.s3.internal.InstructionFileConfig; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; public class RsaKeyringTest { + private static KeyPair RSA_KEY_PAIR; + + @BeforeAll + public static void setUp() throws Exception { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR = keyPairGen.generateKeyPair(); + } + @RetryingTest(3) public void buildAesKeyringWithNullSecureRandomFails() { assertThrows(S3EncryptionClientException.class, () -> AesKeyring.builder().secureRandom(null)); @@ -18,4 +46,75 @@ public void buildAesKeyringWithNullSecureRandomFails() { public void buildAesKeyringWithNullDataKeyGeneratorFails() { assertThrows(S3EncryptionClientException.class, () -> AesKeyring.builder().dataKeyGenerator(null)); } + + // RSA-OAEP-SHA1 wraps the data key together with the content cipher ("AES/GCM/NoPadding"). When the + // decryption materials select the CBC suite (as they would if the content-algorithm metadata read CBC), + // the wrapped cipher and the selected suite disagree, so the EDK must be rejected. + @Test + public void rsaOaepDecryptFailsWhenWrappedCekAlgDoesNotMatchSelectedSuite() { + RsaKeyring keyring = RsaKeyring.builder() + .wrappingKeyPair(PartialRsaKeyPair.builder() + .publicKey(RSA_KEY_PAIR.getPublic()) + .privateKey(RSA_KEY_PAIR.getPrivate()) + .build()) + .build(); + + EncryptionMaterials encryptionMaterials = keyring.onEncrypt(EncryptionMaterials.builder() + .s3Request(PutObjectRequest.builder().bucket("bucket").key("key").build()) + .algorithmSuite(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) + .plaintextDataKey(new byte[32]) + .build()); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.builder() + .s3Request(GetObjectRequest.builder().bucket("bucket").key("key").build()) + .algorithmSuite(AlgorithmSuite.ALG_AES_256_CBC_IV16_NO_KDF) + .build(); + + assertThrows(S3EncryptionClientException.class, + () -> keyring.onDecrypt(decryptionMaterials, encryptionMaterials.encryptedDataKeys())); + } + + // Removing x-amz-cek-alg and renaming x-amz-key-v2 to x-amz-key makes the object decode as V1, which + // defaults to the CBC suite while keeping the non-legacy RSA-OAEP-SHA1 wrap-alg. The wrapped cipher is + // still GCM, so the EDK must be rejected for the CBC suite. + @Test + public void rsaOaepDecryptFailsWhenCekAlgHeaderDeletedAndObjectRelabeledV1() { + RsaKeyring keyring = RsaKeyring.builder() + .wrappingKeyPair(PartialRsaKeyPair.builder() + .publicKey(RSA_KEY_PAIR.getPublic()) + .privateKey(RSA_KEY_PAIR.getPrivate()) + .build()) + .build(); + + EncryptionMaterials encryptionMaterials = keyring.onEncrypt(EncryptionMaterials.builder() + .s3Request(PutObjectRequest.builder().bucket("bucket").key("key").build()) + .algorithmSuite(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) + .plaintextDataKey(new byte[32]) + .build()); + + Map v1Metadata = new HashMap<>(); + v1Metadata.put("x-amz-key", Base64.getEncoder() + .encodeToString(encryptionMaterials.encryptedDataKeys().get(0).encryptedDatakey())); + v1Metadata.put("x-amz-wrap-alg", "RSA-OAEP-SHA1"); + v1Metadata.put("x-amz-iv", Base64.getEncoder().encodeToString(new byte[16])); + v1Metadata.put("x-amz-matdesc", "{}"); + v1Metadata.put("x-amz-tag-len", "128"); + + GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket("bucket").key("key").build(); + ContentMetadata contentMetadata = new ContentMetadataDecodingStrategy( + InstructionFileConfig.builder().instructionFileClient(mock(S3Client.class)).build()) + .decode(getObjectRequest, GetObjectResponse.builder().metadata(v1Metadata).build()); + assertEquals(AlgorithmSuite.ALG_AES_256_CBC_IV16_NO_KDF, contentMetadata.algorithmSuite()); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.builder() + .s3Request(getObjectRequest) + .algorithmSuite(contentMetadata.algorithmSuite()) + .encryptionContext(contentMetadata.encryptionContext()) + .materialsDescription(contentMetadata.materialsDescription()) + .build(); + + assertThrows(S3EncryptionClientException.class, + () -> keyring.onDecrypt(decryptionMaterials, + Collections.singletonList(contentMetadata.encryptedDataKey()))); + } } From 8fc493b17bb78b2ba6ce9bbdcd00befee6c70ee9 Mon Sep 17 00:00:00 2001 From: Kess Plasmeier Date: Mon, 26 Oct 2026 22:08:36 +0000 Subject: [PATCH 2/2] fix(kms-keyring): limit content algorithm check to KMS+Context RSA-OAEP data keys record AES/GCM/NoPadding for every non-committing suite, including CBC objects rewrapped by reEncryptInstructionFile, so the wrapped value cannot identify the content algorithm. Revert the RsaKeyring check and its tests; keep the KMS+Context check, where the stored aws:x-amz-cek-alg records the actual content algorithm. --- .../encryption/s3/materials/RsaKeyring.java | 23 ++--- .../s3/materials/RsaKeyringTest.java | 99 ------------------- 2 files changed, 6 insertions(+), 116 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java index ba4a67bc3..74cb66a92 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java @@ -158,24 +158,13 @@ private byte[] parsePseudoDataKey(DecryptionMaterials materials, byte[] pseudoDa throw new S3EncryptionClientException("Invalid key length (" + dataKeyLengthBytes + ") in encrypted data key"); } - // The content algorithm sealed inside the pseudo data key must match the algorithm suite selected - // for decryption. RSA-OAEP-SHA1 objects are only ever written with a GCM-family content suite, so - // the committing GCM/CTR pair maps to the committing suite ID (115) and the non-committing GCM/CTR - // pair maps to GCM's cipher name. Any other selected suite (e.g. CBC) does not correspond to how - // the data key was wrapped and is not accepted. byte[] expectedDataCipherAlg; - switch (materials.algorithmSuite()) { - case ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY: - case ALG_AES_256_CTR_HKDF_SHA512_COMMIT_KEY: - expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.idAsString().getBytes(StandardCharsets.UTF_8); - break; - case ALG_AES_256_GCM_IV12_TAG16_NO_KDF: - case ALG_AES_256_CTR_IV16_TAG16_NO_KDF: - expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherName().getBytes(StandardCharsets.UTF_8); - break; - default: - throw new S3EncryptionClientException("The algorithm suite selected for decryption does not match " - + "the content algorithm bound to the RSA-wrapped data key. The object may be altered or corrupted"); + // For V3 committed algorithms (both GCM 115 and CTR 116), use GCM's suite ID string (115/0x0073) + if (materials.algorithmSuite().id() == AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.id() || + materials.algorithmSuite().id() == AlgorithmSuite.ALG_AES_256_CTR_HKDF_SHA512_COMMIT_KEY.id()) { + expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY.idAsString().getBytes(StandardCharsets.UTF_8); + } else { + expectedDataCipherAlg = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherName().getBytes(StandardCharsets.UTF_8); } int dataCipherNameLength = expectedDataCipherAlg.length; diff --git a/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java b/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java index fcca9f4ce..8069b4f02 100644 --- a/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java +++ b/src/test/java/software/amazon/encryption/s3/materials/RsaKeyringTest.java @@ -2,41 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3.materials; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.RetryingTest; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.encryption.s3.S3EncryptionClientException; -import software.amazon.encryption.s3.algorithms.AlgorithmSuite; -import software.amazon.encryption.s3.internal.ContentMetadata; -import software.amazon.encryption.s3.internal.ContentMetadataDecodingStrategy; -import software.amazon.encryption.s3.internal.InstructionFileConfig; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; public class RsaKeyringTest { - private static KeyPair RSA_KEY_PAIR; - - @BeforeAll - public static void setUp() throws Exception { - KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); - keyPairGen.initialize(2048); - RSA_KEY_PAIR = keyPairGen.generateKeyPair(); - } - @RetryingTest(3) public void buildAesKeyringWithNullSecureRandomFails() { assertThrows(S3EncryptionClientException.class, () -> AesKeyring.builder().secureRandom(null)); @@ -46,75 +18,4 @@ public void buildAesKeyringWithNullSecureRandomFails() { public void buildAesKeyringWithNullDataKeyGeneratorFails() { assertThrows(S3EncryptionClientException.class, () -> AesKeyring.builder().dataKeyGenerator(null)); } - - // RSA-OAEP-SHA1 wraps the data key together with the content cipher ("AES/GCM/NoPadding"). When the - // decryption materials select the CBC suite (as they would if the content-algorithm metadata read CBC), - // the wrapped cipher and the selected suite disagree, so the EDK must be rejected. - @Test - public void rsaOaepDecryptFailsWhenWrappedCekAlgDoesNotMatchSelectedSuite() { - RsaKeyring keyring = RsaKeyring.builder() - .wrappingKeyPair(PartialRsaKeyPair.builder() - .publicKey(RSA_KEY_PAIR.getPublic()) - .privateKey(RSA_KEY_PAIR.getPrivate()) - .build()) - .build(); - - EncryptionMaterials encryptionMaterials = keyring.onEncrypt(EncryptionMaterials.builder() - .s3Request(PutObjectRequest.builder().bucket("bucket").key("key").build()) - .algorithmSuite(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) - .plaintextDataKey(new byte[32]) - .build()); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.builder() - .s3Request(GetObjectRequest.builder().bucket("bucket").key("key").build()) - .algorithmSuite(AlgorithmSuite.ALG_AES_256_CBC_IV16_NO_KDF) - .build(); - - assertThrows(S3EncryptionClientException.class, - () -> keyring.onDecrypt(decryptionMaterials, encryptionMaterials.encryptedDataKeys())); - } - - // Removing x-amz-cek-alg and renaming x-amz-key-v2 to x-amz-key makes the object decode as V1, which - // defaults to the CBC suite while keeping the non-legacy RSA-OAEP-SHA1 wrap-alg. The wrapped cipher is - // still GCM, so the EDK must be rejected for the CBC suite. - @Test - public void rsaOaepDecryptFailsWhenCekAlgHeaderDeletedAndObjectRelabeledV1() { - RsaKeyring keyring = RsaKeyring.builder() - .wrappingKeyPair(PartialRsaKeyPair.builder() - .publicKey(RSA_KEY_PAIR.getPublic()) - .privateKey(RSA_KEY_PAIR.getPrivate()) - .build()) - .build(); - - EncryptionMaterials encryptionMaterials = keyring.onEncrypt(EncryptionMaterials.builder() - .s3Request(PutObjectRequest.builder().bucket("bucket").key("key").build()) - .algorithmSuite(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) - .plaintextDataKey(new byte[32]) - .build()); - - Map v1Metadata = new HashMap<>(); - v1Metadata.put("x-amz-key", Base64.getEncoder() - .encodeToString(encryptionMaterials.encryptedDataKeys().get(0).encryptedDatakey())); - v1Metadata.put("x-amz-wrap-alg", "RSA-OAEP-SHA1"); - v1Metadata.put("x-amz-iv", Base64.getEncoder().encodeToString(new byte[16])); - v1Metadata.put("x-amz-matdesc", "{}"); - v1Metadata.put("x-amz-tag-len", "128"); - - GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket("bucket").key("key").build(); - ContentMetadata contentMetadata = new ContentMetadataDecodingStrategy( - InstructionFileConfig.builder().instructionFileClient(mock(S3Client.class)).build()) - .decode(getObjectRequest, GetObjectResponse.builder().metadata(v1Metadata).build()); - assertEquals(AlgorithmSuite.ALG_AES_256_CBC_IV16_NO_KDF, contentMetadata.algorithmSuite()); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.builder() - .s3Request(getObjectRequest) - .algorithmSuite(contentMetadata.algorithmSuite()) - .encryptionContext(contentMetadata.encryptionContext()) - .materialsDescription(contentMetadata.materialsDescription()) - .build(); - - assertThrows(S3EncryptionClientException.class, - () -> keyring.onDecrypt(decryptionMaterials, - Collections.singletonList(contentMetadata.encryptedDataKey()))); - } }