From b643bc8f04fba170c42ab4ddba5ca9d68869c5f6 Mon Sep 17 00:00:00 2001 From: akareddy04 <142655873+akareddy04@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:53:08 -0700 Subject: [PATCH 01/10] MaterialsDescription Support for Keyrings (#467) * I created the MaterialsDescription class to help identify AES + RSA keys for reEncryptInstructionFile feature Co-authored-by: Anirav Kareddy --- .../s3/internal/ContentMetadata.java | 19 +- .../ContentMetadataDecodingStrategy.java | 8 +- .../ContentMetadataEncodingStrategy.java | 11 +- .../encryption/s3/materials/AesKeyring.java | 17 +- .../s3/materials/EncryptionMaterials.java | 18 +- .../s3/materials/MaterialsDescription.java | 111 ++++++ .../encryption/s3/materials/RawKeyring.java | 63 ++++ .../encryption/s3/materials/RsaKeyring.java | 19 +- .../encryption/s3/materials/S3Keyring.java | 23 -- .../s3/S3EncryptionClientMatDescTest.java | 315 ++++++++++++++++++ .../internal/ContentMetadataStrategyTest.java | 2 +- .../s3/internal/ContentMetadataTest.java | 2 +- .../materials/MaterialsDescriptionTest.java | 96 ++++++ 13 files changed, 645 insertions(+), 59 deletions(-) create mode 100644 src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java create mode 100644 src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java create mode 100644 src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java create mode 100644 src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java diff --git a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java index 270258f15..ac6068a19 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java @@ -15,7 +15,13 @@ public class ContentMetadata { private final EncryptedDataKey _encryptedDataKey; private final String _encryptedDataKeyAlgorithm; - private final Map _encryptedDataKeyContext; + + /** + * This field stores either encryption context or material description. + * We use a single field to store both in order to maintain backwards + * compatibility with V2, which treated both as the same. + */ + private final Map _encryptionContextOrMatDesc; private final byte[] _contentIv; private final String _contentCipher; @@ -27,7 +33,7 @@ private ContentMetadata(Builder builder) { _encryptedDataKey = builder._encryptedDataKey; _encryptedDataKeyAlgorithm = builder._encryptedDataKeyAlgorithm; - _encryptedDataKeyContext = builder._encryptedDataKeyContext; + _encryptionContextOrMatDesc = builder._encryptionContextOrMatDesc; _contentIv = builder._contentIv; _contentCipher = builder._contentCipher; @@ -51,6 +57,7 @@ public String encryptedDataKeyAlgorithm() { return _encryptedDataKeyAlgorithm; } + /** * Note that the underlying implementation uses a Collections.unmodifiableMap which is * immutable. @@ -58,7 +65,7 @@ public String encryptedDataKeyAlgorithm() { @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "False positive; underlying" + " implementation is immutable") public Map encryptedDataKeyContext() { - return _encryptedDataKeyContext; + return _encryptionContextOrMatDesc; } public byte[] contentIv() { @@ -85,7 +92,7 @@ public static class Builder { private EncryptedDataKey _encryptedDataKey; private String _encryptedDataKeyAlgorithm; - private Map _encryptedDataKeyContext; + private Map _encryptionContextOrMatDesc; private byte[] _contentIv; private String _contentCipher; @@ -111,8 +118,8 @@ public Builder encryptedDataKeyAlgorithm(String encryptedDataKeyAlgorithm) { return this; } - public Builder encryptedDataKeyContext(Map encryptedDataKeyContext) { - _encryptedDataKeyContext = Collections.unmodifiableMap(encryptedDataKeyContext); + public Builder encryptionContextOrMatDesc(Map encryptionContextOrMatDesc) { + _encryptionContextOrMatDesc = Collections.unmodifiableMap(encryptionContextOrMatDesc); return this; } diff --git a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java index 0ab055873..eba533135 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java @@ -136,8 +136,8 @@ private ContentMetadata readFromMap(Map metadata, GetObjectRespo .keyProviderInfo(keyProviderInfo.getBytes(StandardCharsets.UTF_8)) .build(); - // Get encrypted data key encryption context - final Map encryptionContext = new HashMap<>(); + // Get encrypted data key encryption context or materials description (depending on the keyring) + final Map encryptionContextOrMatDesc = new HashMap<>(); // The V2 client treats null value here as empty, do the same to avoid incompatibility String jsonEncryptionContext = metadata.getOrDefault(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT, "{}"); // When the encryption context contains non-US-ASCII characters, @@ -149,7 +149,7 @@ private ContentMetadata readFromMap(Map metadata, GetObjectRespo JsonNode objectNode = parser.parse(decodedJsonEncryptionContext); for (Map.Entry entry : objectNode.asObject().entrySet()) { - encryptionContext.put(entry.getKey(), entry.getValue().asString()); + encryptionContextOrMatDesc.put(entry.getKey(), entry.getValue().asString()); } } catch (Exception e) { throw new RuntimeException(e); @@ -161,7 +161,7 @@ private ContentMetadata readFromMap(Map metadata, GetObjectRespo return ContentMetadata.builder() .algorithmSuite(algorithmSuite) .encryptedDataKey(edk) - .encryptedDataKeyContext(encryptionContext) + .encryptionContextOrMatDesc(encryptionContextOrMatDesc) .contentIv(iv) .contentRange(contentRange) .build(); diff --git a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java index cf4e86fa7..a2e7915df 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java @@ -80,11 +80,16 @@ private Map addMetadataToMap(Map map, Encryption try (JsonWriter jsonWriter = JsonWriter.create()) { jsonWriter.writeStartObject(); - for (Map.Entry entry : materials.encryptionContext().entrySet()) { - jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue()); + if (!materials.encryptionContext().isEmpty() && materials.materialsDescription().isEmpty()) { + for (Map.Entry entry : materials.encryptionContext().entrySet()) { + jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue()); + } + } else if (materials.encryptionContext().isEmpty() && !materials.materialsDescription().isEmpty()) { + for (Map.Entry entry : materials.materialsDescription().entrySet()) { + jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue()); + } } jsonWriter.writeEndObject(); - String jsonEncryptionContext = new String(jsonWriter.getBytes(), StandardCharsets.UTF_8); metadata.put(MetadataKeyConstants.ENCRYPTED_DATA_KEY_CONTEXT, jsonEncryptionContext); } catch (JsonWriter.JsonGenerationException e) { diff --git a/src/main/java/software/amazon/encryption/s3/materials/AesKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/AesKeyring.java index e2fbaac08..ec2e474df 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/AesKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/AesKeyring.java @@ -20,7 +20,7 @@ * This keyring can wrap keys with the active keywrap algorithm and * unwrap with the active and legacy algorithms for AES keys. */ -public class AesKeyring extends S3Keyring { +public class AesKeyring extends RawKeyring { private static final String KEY_ALGORITHM = "AES"; @@ -88,6 +88,11 @@ public boolean isLegacy() { return false; } + @Override + public EncryptionMaterials modifyMaterials(EncryptionMaterials materials) { + return modifyMaterialsForRawKeyring(materials); + } + @Override public String keyProviderInfo() { return KEY_PROVIDER_INFO; @@ -98,13 +103,6 @@ public EncryptionMaterials generateDataKey(EncryptionMaterials materials) { return defaultGenerateDataKey(materials); } - @Override - public EncryptionMaterials modifyMaterials(EncryptionMaterials materials) { - warnIfEncryptionContextIsPresent(materials); - - return materials; - } - @Override public byte[] encryptDataKey(SecureRandom secureRandom, EncryptionMaterials materials) @@ -177,7 +175,7 @@ protected Map decryptDataKeyStrategies() { return decryptDataKeyStrategies; } - public static class Builder extends S3Keyring.Builder { + public static class Builder extends RawKeyring.Builder { private SecretKey _wrappingKey; private Builder() { @@ -199,7 +197,6 @@ public Builder wrappingKey(final SecretKey wrappingKey) { _wrappingKey = wrappingKey; return builder(); } - public AesKeyring build() { return new AesKeyring(this); } diff --git a/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterials.java b/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterials.java index 8a358ed72..ff54785b8 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterials.java +++ b/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterials.java @@ -4,6 +4,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import software.amazon.awssdk.services.s3.model.S3Request; +import software.amazon.encryption.s3.S3EncryptionClientException; import software.amazon.encryption.s3.algorithms.AlgorithmSuite; import software.amazon.encryption.s3.internal.CipherMode; import software.amazon.encryption.s3.internal.CipherProvider; @@ -27,6 +28,7 @@ final public class EncryptionMaterials implements CryptographicMaterials { // Additional information passed into encrypted that is required on decryption as well // Should NOT contain sensitive information private final Map _encryptionContext; + private final MaterialsDescription _materialsDescription; private final List _encryptedDataKeys; private final byte[] _plaintextDataKey; @@ -43,6 +45,7 @@ private EncryptionMaterials(Builder builder) { this._cryptoProvider = builder._cryptoProvider; this._plaintextLength = builder._plaintextLength; this._ciphertextLength = _plaintextLength + _algorithmSuite.cipherTagLengthBytes(); + this._materialsDescription = builder._materialsDescription; } static public Builder builder() { @@ -101,6 +104,9 @@ public Provider cryptoProvider() { return _cryptoProvider; } + public MaterialsDescription materialsDescription() { + return _materialsDescription; + } @Override public CipherMode cipherMode() { return CipherMode.ENCRYPT; @@ -119,6 +125,7 @@ public Builder toBuilder() { .encryptedDataKeys(_encryptedDataKeys) .plaintextDataKey(_plaintextDataKey) .cryptoProvider(_cryptoProvider) + .materialsDescription(_materialsDescription) .plaintextLength(_plaintextLength); } @@ -132,6 +139,7 @@ static public class Builder { private byte[] _plaintextDataKey = null; private long _plaintextLength = -1; private Provider _cryptoProvider = null; + private MaterialsDescription _materialsDescription = MaterialsDescription.builder().build(); private Builder() { } @@ -145,7 +153,12 @@ public Builder algorithmSuite(AlgorithmSuite algorithmSuite) { _algorithmSuite = algorithmSuite; return this; } - + public Builder materialsDescription(MaterialsDescription materialsDescription) { + _materialsDescription = materialsDescription == null + ? MaterialsDescription.builder().build() + : materialsDescription; + return this; + } public Builder encryptionContext(Map encryptionContext) { _encryptionContext = encryptionContext == null ? Collections.emptyMap() @@ -175,6 +188,9 @@ public Builder plaintextLength(long plaintextLength) { } public EncryptionMaterials build() { + if (!_materialsDescription.isEmpty() && !_encryptionContext.isEmpty()) { + throw new S3EncryptionClientException("MaterialsDescription and EncryptionContext cannot both be set!"); + } return new EncryptionMaterials(this); } } diff --git a/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java b/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java new file mode 100644 index 000000000..c5de2bf38 --- /dev/null +++ b/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java @@ -0,0 +1,111 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package software.amazon.encryption.s3.materials; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This class is used to provide key-value pairs that describe the key material used with the Keyring, specifically for AES and RSA Keyrings. + * This will be useful during the re-encryption of instruction file. + * The stored Materials Description is immutable once created. + */ +public class MaterialsDescription implements Map { + private final Map materialsDescription; + + private MaterialsDescription(Builder builder) { + this.materialsDescription = Collections.unmodifiableMap(new HashMap<>(builder.materialsDescription)); + } + public static Builder builder() { + return new Builder(); + } + public Map getMaterialsDescription() { + return this.materialsDescription; + } + + @Override + public int size() { + return materialsDescription.size(); + } + + @Override + public boolean isEmpty() { + return materialsDescription.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return materialsDescription.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return materialsDescription.containsValue(value); + } + + @Override + public String get(Object key) { + return materialsDescription.get(key); + } + + @Override + public String put(String key, String value) { + throw new UnsupportedOperationException("This map is immutable"); + } + + @Override + public String remove(Object key) { + return materialsDescription.remove(key); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("This map is immutable"); + } + + @Override + public void clear() { + materialsDescription.clear(); + } + + @Override + public Set keySet() { + return materialsDescription.keySet(); + } + + @Override + public Collection values() { + return materialsDescription.values(); + } + + @Override + public Set> entrySet() { + return materialsDescription.entrySet(); + } + + + public static class Builder { + private final Map materialsDescription = new HashMap<>(); + public Builder put(String key, String value) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + materialsDescription.put(key, value); + return this; + } + public Builder putAll(Map description) { + if (description == null) { + throw new IllegalArgumentException("Description must not be null"); + } + materialsDescription.putAll(description); + return this; + } + public MaterialsDescription build() { + return new MaterialsDescription(this); + } + } +} \ No newline at end of file diff --git a/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java new file mode 100644 index 000000000..1fc7b15f8 --- /dev/null +++ b/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java @@ -0,0 +1,63 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.encryption.s3.materials; + +import org.apache.commons.logging.LogFactory; +import software.amazon.encryption.s3.S3EncryptionClient; + +/** + * This is an abstract base class for keyrings that use raw cryptographic keys (AES + RSA) + */ +public abstract class RawKeyring extends S3Keyring { + protected final MaterialsDescription _materialsDescription; + + protected RawKeyring(Builder builder) { + super(builder); + _materialsDescription = builder._materialsDescription; + } + + public EncryptionMaterials modifyMaterialsForRawKeyring(EncryptionMaterials materials) { + warnIfEncryptionContextIsPresent(materials); + if (_materialsDescription != null && !_materialsDescription.isEmpty()) { + materials = materials.toBuilder() + .materialsDescription(_materialsDescription) + .build(); + } + return materials; + } + + /** + * Checks if an encryption context is present in the EncryptionMaterials and issues a warning + * if an encryption context is found. + *

+ * Encryption context is not recommended for use with + * non-KMS keyrings as it does not provide additional security benefits and is not stored. + * + * @param materials EncryptionMaterials + */ + + public void warnIfEncryptionContextIsPresent(EncryptionMaterials materials) { + materials.s3Request().overrideConfiguration() + .flatMap(overrideConfiguration -> + overrideConfiguration.executionAttributes() + .getOptionalAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT)) + .ifPresent(ctx -> LogFactory.getLog(getClass()).warn("Usage of Encryption Context provides no " + + "security benefit in " + getClass().getSimpleName() + ".Additionally, this Encryption Context WILL NOT be " + + "stored in the material description. Provide a MaterialDescription in the Keyring's builder instead.")); + } + + public static abstract class Builder> + extends S3Keyring.Builder { + + protected MaterialsDescription _materialsDescription; + + protected Builder() { + super(); + } + + public BuilderT materialsDescription(MaterialsDescription materialsDescription) { + _materialsDescription = materialsDescription; + return builder(); + } + } +} 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 da40d4c1b..91563b14c 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/RsaKeyring.java @@ -23,7 +23,7 @@ * This keyring can wrap keys with the active keywrap algorithm and * unwrap with the active and legacy algorithms for RSA keys. */ -public class RsaKeyring extends S3Keyring { +public class RsaKeyring extends RawKeyring { private final PartialRsaKeyPair _partialRsaKeyPair; @@ -93,6 +93,11 @@ public boolean isLegacy() { return false; } + @Override + public EncryptionMaterials modifyMaterials(EncryptionMaterials materials) { + return modifyMaterialsForRawKeyring(materials); + } + @Override public String keyProviderInfo() { return KEY_PROVIDER_INFO; @@ -103,13 +108,6 @@ public EncryptionMaterials generateDataKey(EncryptionMaterials materials) { return defaultGenerateDataKey(materials); } - @Override - public EncryptionMaterials modifyMaterials(EncryptionMaterials materials) { - warnIfEncryptionContextIsPresent(materials); - - return materials; - } - @Override public byte[] encryptDataKey(SecureRandom secureRandom, EncryptionMaterials materials) throws GeneralSecurityException { @@ -197,7 +195,7 @@ protected Map decryptDataKeyStrategies() { return decryptDataKeyStrategies; } - public static class Builder extends S3Keyring.Builder { + public static class Builder extends RawKeyring.Builder { private PartialRsaKeyPair _partialRsaKeyPair; private Builder() { @@ -217,6 +215,7 @@ public Builder wrappingKeyPair(final PartialRsaKeyPair partialRsaKeyPair) { public RsaKeyring build() { return new RsaKeyring(this); } - } + + } } diff --git a/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java b/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java index b0b0e89df..6b268641c 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java @@ -3,7 +3,6 @@ package software.amazon.encryption.s3.materials; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import software.amazon.encryption.s3.S3EncryptionClient; import software.amazon.encryption.s3.S3EncryptionClientException; import java.nio.charset.StandardCharsets; @@ -14,8 +13,6 @@ import java.util.Map; import javax.crypto.SecretKey; -import org.apache.commons.logging.LogFactory; - /** * This serves as the base class for all the keyrings in the S3 encryption client. * Shared functionality is all performed here. @@ -127,30 +124,11 @@ public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List decryptDataKeyStrategies(); - /** - * Checks if an encryption context is present in the EncryptionMaterials and issues a warning - * if an encryption context is found. - *

- * Encryption context is not recommended for use with - * non-KMS keyrings as it may not provide additional security benefits. - * - * @param materials EncryptionMaterials - */ - public void warnIfEncryptionContextIsPresent(EncryptionMaterials materials) { - materials.s3Request().overrideConfiguration() - .flatMap(overrideConfiguration -> - overrideConfiguration.executionAttributes() - .getOptionalAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT)) - .ifPresent(ctx -> LogFactory.getLog(getClass()).warn("Usage of Encryption Context provides no security benefit in " + getClass().getSimpleName())); - - } - abstract public static class Builder> { private boolean _enableLegacyWrappingAlgorithms = false; private SecureRandom _secureRandom; private DataKeyGenerator _dataKeyGenerator = new DefaultDataKeyGenerator(); - protected Builder() {} protected abstract BuilderT builder(); @@ -180,7 +158,6 @@ public BuilderT dataKeyGenerator(final DataKeyGenerator dataKeyGenerator) { _dataKeyGenerator = dataKeyGenerator; return builder(); } - abstract public KeyringT build(); } } diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java new file mode 100644 index 000000000..d246b4cc1 --- /dev/null +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java @@ -0,0 +1,315 @@ +package software.amazon.encryption.s3; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.protocols.jsoncore.JsonNode; +import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.ServerSideEncryption; +import software.amazon.encryption.s3.internal.InstructionFileConfig; +import software.amazon.encryption.s3.materials.AesKeyring; +import software.amazon.encryption.s3.materials.MaterialsDescription; +import software.amazon.encryption.s3.materials.PartialRsaKeyPair; +import software.amazon.encryption.s3.materials.RsaKeyring; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + +public class S3EncryptionClientMatDescTest { + private static SecretKey AES_KEY; + private static KeyPair RSA_KEY_PAIR; + + @BeforeAll + public static void setUp() throws NoSuchAlgorithmException { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + AES_KEY = keyGen.generateKey(); + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR = keyPairGen.generateKeyPair(); + } + + @Test + public void testAesMaterialsDescriptionInObjectMetadata() { + AesKeyring aesKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .secureRandom(new SecureRandom()) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .build()) + .build(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(aesKeyring) + .build(); + final String input = "Testing Materials Description in Object Metadata!"; + final String objectKey = appendTestSuffix("test-aes-materials-description-in-object-metadata"); + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input) + ); + ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, responseBytes.asUtf8String()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode matDescNode = parser.parse(responseBytes.response().metadata().get("x-amz-matdesc")); + assertEquals("1.0", matDescNode.asObject().get("version").asString()); + + deleteObject(BUCKET, objectKey, client); + + } + + @Test + public void testRsaMaterialsDescriptionInObjectMetadata() { + PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); + RsaKeyring rsaKeyring = RsaKeyring.builder() + .wrappingKeyPair(keyPair) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .put("admin", "yes") + .build()) + .build(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(rsaKeyring) + .build(); + final String input = "Testing Materials Description in Instruction File!"; + final String objectKey = appendTestSuffix("test-rsa-materials-description-in-instruction-file"); + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input) + ); + ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, responseBytes.asUtf8String()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode matDescNode = parser.parse(responseBytes.response().metadata().get("x-amz-matdesc")); + assertEquals("1.0", matDescNode.asObject().get("version").asString()); + assertEquals("yes", matDescNode.asObject().get("admin").asString()); + + deleteObject(BUCKET, objectKey, client); + + } + + @Test + public void testAesMaterialsDescriptionInInstructionFile() { + AesKeyring aesKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .secureRandom(new SecureRandom()) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .build()) + .build(); + + S3Client wrappedClient= S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(aesKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .instructionFileClient(wrappedClient) + .build()) + .build(); + + final String input = "Testing Materials Description in Instruction File!"; + final String objectKey = appendTestSuffix("test-aes-materials-description-in-instruction-file"); + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input) + ); + ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, responseBytes.asUtf8String()); + + S3Client defaultClient= S3Client.create(); + + ResponseBytes directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String instructionFileContent = directInstGetResponse.asUtf8String(); + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFileContent); + + JsonNode matDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + assertEquals("1.0", matDescNode.asObject().get("version").asString()); + + deleteObject(BUCKET, objectKey, client); + + } + + @Test + public void testRsaMaterialsDescriptionInInstructionFile() { + PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); + + RsaKeyring rsaKeyring = RsaKeyring.builder() + .wrappingKeyPair(keyPair) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .put("admin", "yes") + .build()) + .build(); + + S3Client wrappedClient= S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(rsaKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .instructionFileClient(wrappedClient) + .build()) + .build(); + + final String input = "Testing Materials Description in Instruction File!"; + final String objectKey = appendTestSuffix("test-rsa-materials-description-in-instruction-file"); + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input) + ); + ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, responseBytes.asUtf8String()); + + S3Client defaultClient= S3Client.create(); + + ResponseBytes directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String instructionFileContent = directInstGetResponse.asUtf8String(); + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFileContent); + + JsonNode matDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + assertEquals("1.0", matDescNode.asObject().get("version").asString()); + assertEquals("yes", matDescNode.asObject().get("admin").asString()); + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testAesKeyringMatDescOverridesPutObjectEncryptionContext() { + AesKeyring aesKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .secureRandom(new SecureRandom()) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .build()) + .build(); + + S3Client wrappedClient= S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(aesKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .enableInstructionFilePutObject(true) + .instructionFileClient(wrappedClient) + .build()) + .build(); + + final String input = "Testing Materials Description in Instruction File and not Encryption Context!"; + final String objectKey = appendTestSuffix("test-aes-materials-description-in-instruction-file-and-not-encryption-context"); + final Map encryptionContext = new HashMap(); + encryptionContext.put("admin", "yes"); + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) + .build(), RequestBody.fromString(input) + ); + ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, responseBytes.asUtf8String()); + + S3Client defaultClient= S3Client.create(); + + ResponseBytes directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String instructionFileContent = directInstGetResponse.asUtf8String(); + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFileContent); + + JsonNode matDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + assertEquals("1.0", matDescNode.asObject().get("version").asString()); + assertNull(matDescNode.asObject().get("admin")); + + } + + @Test + public void testRsaKeyringMatDescOverridesPutObjectEncryptionContext() { + PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); + RsaKeyring rsaKeyring = RsaKeyring.builder() + .wrappingKeyPair(keyPair) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .build()) + .build(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(rsaKeyring) + .build(); + final String input = "Testing Materials Description in Instruction File and not Encryption Context!"; + final String objectKey = appendTestSuffix("test-rsa-materials-description-in-instruction-file-and-not-encryption-context"); + final Map encryptionContext = new HashMap(); + encryptionContext.put("admin", "yes"); + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) + .build(), RequestBody.fromString(input) + ); + ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, responseBytes.asUtf8String()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode matDescNode = parser.parse(responseBytes.response().metadata().get("x-amz-matdesc")); + assertEquals("1.0", matDescNode.asObject().get("version").asString()); + assertNull(matDescNode.asObject().get("admin")); + + } + +} diff --git a/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataStrategyTest.java b/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataStrategyTest.java index 3e7f09e87..43f6a979b 100644 --- a/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataStrategyTest.java +++ b/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataStrategyTest.java @@ -49,7 +49,7 @@ public void decodeWithObjectMetadata() { expectedContentMetadata = ContentMetadata.builder() .algorithmSuite(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF) .encryptedDataKeyAlgorithm(null) - .encryptedDataKeyContext(new HashMap()) + .encryptionContextOrMatDesc(new HashMap()) .contentIv(bytes) .build(); diff --git a/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java b/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java index 73a38f23b..a70c8ed75 100644 --- a/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java +++ b/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java @@ -35,7 +35,7 @@ public void setUp() { .encryptedDataKey(encryptedDataKey) .contentIv(contentIv) .encryptedDataKeyAlgorithm(encryptedDataKeyAlgorithm) - .encryptedDataKeyContext(encryptedDataKeyContext) + .encryptionContextOrMatDesc(encryptedDataKeyContext) .build(); } diff --git a/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java b/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java new file mode 100644 index 000000000..d6f7d59c8 --- /dev/null +++ b/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java @@ -0,0 +1,96 @@ +package software.amazon.encryption.s3.materials; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class MaterialsDescriptionTest { + private static SecretKey AES_KEY; + private static KeyPair RSA_KEY_PAIR; + + @BeforeAll + public static void setUp() throws NoSuchAlgorithmException { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + AES_KEY = keyGen.generateKey(); + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR = keyPairGen.generateKeyPair(); + } + + @Test + public void testSimpleMaterialsDescription() { + MaterialsDescription materialsDescription = MaterialsDescription.builder() + .put("version", "1.0") + .build(); + assertEquals("1.0", materialsDescription.getMaterialsDescription().get("version")); + assertEquals(1, materialsDescription.getMaterialsDescription().size()); + try { + materialsDescription.getMaterialsDescription().put("version", "2.0"); + fail("Expected UnsupportedOperationException!"); + } catch (UnsupportedOperationException e) { + assertNull(e.getMessage()); + } + try { + materialsDescription.getMaterialsDescription().clear(); + fail("Expected UnsupportedOperationException!"); + } catch (UnsupportedOperationException e) { + assertNull(e.getMessage()); + } + } + @Test + public void testMaterialsDescriptionPutAll() { + Map description = new HashMap<>(); + description.put("version", "1.0"); + description.put("next-version", "2.0"); + MaterialsDescription materialsDescription = MaterialsDescription.builder() + .putAll(description) + .build(); + assertEquals(2, materialsDescription.getMaterialsDescription().size()); + assertTrue(materialsDescription.getMaterialsDescription().containsKey("version")); + assertTrue(materialsDescription.getMaterialsDescription().containsKey("next-version")); + assertEquals("1.0", materialsDescription.getMaterialsDescription().get("version")); + assertEquals("2.0", materialsDescription.getMaterialsDescription().get("next-version")); + } + + @Test + public void testMaterialsDescriptionAesKeyring() { + AesKeyring aesKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .put("admin", "yes") + .build()) + .build(); + assertNotNull(aesKeyring); + } + + @Test + public void testMaterialsDescriptionRsaKeyring() { + PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); + RsaKeyring rsaKeyring = RsaKeyring.builder() + .wrappingKeyPair(keyPair) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .put("admin", "yes") + .build()) + .build(); + assertNotNull(rsaKeyring); + + } + +} \ No newline at end of file From 675256f3ce69baeee2f4bd64b9db666780346c72 Mon Sep 17 00:00:00 2001 From: akareddy04 <142655873+akareddy04@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:15:41 -0700 Subject: [PATCH 02/10] feat: ReEncryptInstructionFile Implementation (#470) --------- Co-authored-by: Anirav Kareddy --- .../ReEncryptInstructionFileExample.java | 421 +++++ .../encryption/s3/S3EncryptionClient.java | 103 +- .../s3/S3EncryptionClientUtilities.java | 4 +- .../s3/internal/ContentMetadata.java | 2 +- .../ContentMetadataDecodingStrategy.java | 9 +- .../ContentMetadataEncodingStrategy.java | 13 +- .../internal/GetEncryptedObjectPipeline.java | 2 +- .../s3/internal/InstructionFileConfig.java | 14 +- .../ReEncryptInstructionFileRequest.java | 149 ++ .../ReEncryptInstructionFileResponse.java | 48 + .../s3/materials/MaterialsDescription.java | 184 +- .../encryption/s3/materials/RawKeyring.java | 21 + .../encryption/s3/materials/S3Keyring.java | 2 +- .../S3EncryptionClientCompatibilityTest.java | 11 - .../s3/S3EncryptionClientMatDescTest.java | 6 +- ...ionClientReEncryptInstructionFileTest.java | 1631 +++++++++++++++++ .../s3/internal/ContentMetadataTest.java | 2 +- .../materials/MaterialsDescriptionTest.java | 1 + 18 files changed, 2511 insertions(+), 112 deletions(-) create mode 100644 src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java create mode 100644 src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java create mode 100644 src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java create mode 100644 src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java new file mode 100644 index 000000000..c7affbde5 --- /dev/null +++ b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java @@ -0,0 +1,421 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.encryption.s3.examples; + +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.encryption.s3.S3EncryptionClient; +import software.amazon.encryption.s3.S3EncryptionClientException; +import software.amazon.encryption.s3.internal.InstructionFileConfig; +import software.amazon.encryption.s3.internal.ReEncryptInstructionFileRequest; +import software.amazon.encryption.s3.internal.ReEncryptInstructionFileResponse; +import software.amazon.encryption.s3.materials.AesKeyring; +import software.amazon.encryption.s3.materials.MaterialsDescription; +import software.amazon.encryption.s3.materials.PartialRsaKeyPair; +import software.amazon.encryption.s3.materials.RsaKeyring; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + +public class ReEncryptInstructionFileExample { + + /** + * Generates a 256-bit AES key for encryption/decryption operations. + * + * @return A SecretKey instance for AES operations + * @throws NoSuchAlgorithmException if AES algorithm is not available + */ + private static SecretKey generateAesKey() throws NoSuchAlgorithmException { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + return keyGen.generateKey(); + } + + /** + * Generates a 2048-bit RSA key pair for encryption/decryption operations. + * + * @return A KeyPair instance for RSA operations + * @throws NoSuchAlgorithmException if RSA algorithm is not available + */ + private static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + return keyPairGen.generateKeyPair(); + } + + public static void main(final String[] args) throws NoSuchAlgorithmException { + final String bucket = args[0]; + simpleAesKeyringReEncryptInstructionFile(bucket); + simpleRsaKeyringReEncryptInstructionFile(bucket); + simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(bucket); + } + + /** + * This example demonstrates re-encrypting the encrypted data key in an instruction file with a new AES wrapping key. + * + * @param bucket The name of the Amazon S3 bucket to perform operations on. + * @throws NoSuchAlgorithmException if AES algorithm is not available + */ + public static void simpleAesKeyringReEncryptInstructionFile(final String bucket) throws NoSuchAlgorithmException { + // Set up the S3 object key and content to be encrypted + final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-test"); + final String input = "Testing re-encryption of instruction file with AES Keyring"; + + // Generate the original AES key for initial encryption + SecretKey originalAesKey = generateAesKey(); + + // Create the original AES keyring with materials description + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(originalAesKey) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .put("rotated", "no") + .build()) + .build(); + + // Create a default S3 client for instruction file operations + S3Client wrappedClient = S3Client.create(); + + // Create the S3 Encryption Client with instruction file support enabled + // The client can perform both putObject and getObject operations using the original AES key + S3EncryptionClient originalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + // Upload both the encrypted object and instruction file to the specified bucket in S3 + originalClient.putObject(builder -> builder + .bucket(bucket) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + // Generate a new AES key for re-encryption (rotating wrapping key) + SecretKey newAesKey = generateAesKey(); + + // Create a new keyring with the new AES key and updated materials description + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(newAesKey) + .materialsDescription(MaterialsDescription.builder() + .put("version", "2.0") + .put("rotated", "yes") + .build()) + .build(); + + // Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key + // This updates the instruction file without touching the encrypted object + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(bucket) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + // Perform the re-encryption of the instruction file + ReEncryptInstructionFileResponse response = originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + // Verify that the original client can no longer decrypt the object + // This proves that the instruction file has been successfully re-encrypted + try { + originalClient.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .build()); + throw new RuntimeException("Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Unable to AES/GCM unwrap")); + } + + // Create a new client with the rotated AES key + S3EncryptionClient newClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + // Verify that the new client can successfully decrypt the object + // This proves that the instruction file has been successfully re-encrypted + ResponseBytes decryptedObject = newClient.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .build()); + + // Assert that the decrypted object's content matches the original input + assertEquals(input, decryptedObject.asUtf8String()); + + // Call deleteObject to delete the object from given S3 Bucket + deleteObject(bucket, objectKey, originalClient); + } + + /** + * This example demonstrates re-encrypting the encrypted data key in an instruction file with a new RSA wrapping key. + * + * @param bucket The name of the Amazon S3 bucket to perform operations on. + * @throws NoSuchAlgorithmException if RSA algorithm is not available + */ + public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) throws NoSuchAlgorithmException { + // Set up the S3 object key and content to be encrypted + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring"; + + // Generate the original RSA key pair for initial encryption + KeyPair originalRsaKeyPair = generateRsaKeyPair(); + PublicKey originalPublicKey = originalRsaKeyPair.getPublic(); + PrivateKey originalPrivateKey = originalRsaKeyPair.getPrivate(); + + // Create a partial RSA key pair for the original keyring + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + // Create the original RSA keyring with materials description + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("version", "1.0") + .put("rotated", "no") + .build()) + .build(); + + // Create a default S3 client for instruction file operations + S3Client wrappedClient = S3Client.create(); + + // Create the S3 Encryption Client with instruction file support enabled + // The client can perform both putObject and getObject operations using RSA keyring + S3EncryptionClient originalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + // Upload both the encrypted object and instruction file to the specified bucket in S3 + originalClient.putObject(builder -> builder + .bucket(bucket) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + // Generate a new RSA key pair for the new RSA keyring + KeyPair newKeyPair = generateRsaKeyPair(); + PublicKey newPublicKey = newKeyPair.getPublic(); + PrivateKey newPrivateKey = newKeyPair.getPrivate(); + + // Create a partial RSA key pair for the new RSA keyring + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + // Create the new RSA keyring with updated materials description + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("version", "2.0") + .put("rotated", "yes") + .build()) + .build(); + + // Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key + // This updates the instruction file without touching the encrypted object + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(bucket) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + // Perform the re-encryption of the instruction file + ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + // Verify that the original client can no longer decrypt the object + // This proves that the instruction file has been successfully re-encrypted + try { + originalClient.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .build()); + throw new RuntimeException("Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); + } + + // Create a new client with the rotated RSA key + S3EncryptionClient newClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + // Verify that the new client can successfully decrypt the object + // This proves that the instruction file has been successfully re-encrypted + ResponseBytes decryptedObject = newClient.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .build()); + + // Assert that the decrypted object's content matches the original input + assertEquals(input, decryptedObject.asUtf8String()); + + // Call deleteObject to delete the object from given S3 Bucket + deleteObject(bucket, objectKey, originalClient); + } + + /** + * This example demonstrates generating a custom instruction file to enable access to encrypted object by a third party. + * This enables secure sharing of encrypted objects without sharing private keys. + * + * @param bucket The name of the Amazon S3 bucket to perform operations on. + * @throws NoSuchAlgorithmException if RSA algorithm is not available + */ + public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(final String bucket) throws NoSuchAlgorithmException { + // Set up the S3 object key and content to be encrypted + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test-with-custom-suffix"); + final String input = "Testing re-encryption of instruction file with RSA Keyring"; + + // Generate RSA key pair for the original client + KeyPair clientRsaKeyPair = generateRsaKeyPair(); + PublicKey clientPublicKey = clientRsaKeyPair.getPublic(); + PrivateKey clientPrivateKey = clientRsaKeyPair.getPrivate(); + + // Create a partial RSA key pair for the client's keyring + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + // Create the client's RSA keyring with materials description + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + // Create a default S3 client for instruction file operations + S3Client wrappedClient = S3Client.create(); + + // Create the S3 Encryption Client with instruction file support enabled + // The client can perform both putObject and getObject operations using RSA keyring + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + // Upload both the encrypted object and instruction file to the specified bucket in S3 + client.putObject(builder -> builder + .bucket(bucket) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + // Generate a new RSA key pair for the third party customer + KeyPair thirdPartyKeyPair = generateRsaKeyPair(); + PublicKey thirdPartyPublicKey = thirdPartyKeyPair.getPublic(); + PrivateKey thirdPartyPrivateKey = thirdPartyKeyPair.getPrivate(); + + // Create a partial RSA key pair for the third party's decryption keyring + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + // Create RSA keyring with third party's public key and updated materials description for re-encryption request + RsaKeyring sharedKeyring = RsaKeyring.builder() + .wrappingKeyPair(PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .build()) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + // Create RSA keyring with third party's public and private keys for decryption purposes with updated materials description + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + // Create the re-encryption request that will generate a new instruction file specifically for third party access + // This new instruction file will use a custom suffix and contain the data key encrypted with the third party's public key + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(bucket) + .key(objectKey) + .instructionFileSuffix("third-party-access-instruction-file") // Custom instruction file suffix for third party + .newKeyring(sharedKeyring) + .build(); + + // Perform the re-encryption operation to create the new instruction file + // This creates a new instruction file without modifying the original encrypted object + ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + // Create the third party's S3 Encryption Client + S3EncryptionClient thirdPartyClient = S3EncryptionClient.builder() + .keyring(thirdPartyKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + // Verify that the original client can still decrypt the object in the specified bucket in S3 using the default instruction file + ResponseBytes clientDecryptedObject = client.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .build()); + + // Assert that the decrypted object's content matches the original input + assertEquals(input, clientDecryptedObject.asUtf8String()); + + // Verify that the third party cannot decrypt the object in the specified bucket in S3 using the default instruction file + try { + ResponseBytes thirdPartyDecryptObject = thirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .build()); + throw new RuntimeException("Third party client should not be able to decrypt the object in S3 using the default instruction file!"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); + } + + // Verify that the third party can decrypt the object in the specified bucket in S3 using their custom instruction file + // This demonstrates successful secure sharing of encrypted data + ResponseBytes thirdPartyDecryptedObject = thirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(bucket) + .key(objectKey) + .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) + .build()); + + // Assert that the decrypted object's content matches the original input + assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); + + // Call deleteObject to delete the object from given S3 Bucket + deleteObject(bucket, objectKey, client); + } + +} \ No newline at end of file diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index 0c7f12a7b..bf0a468c5 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -46,20 +46,30 @@ import software.amazon.awssdk.services.s3.model.UploadPartRequest; import software.amazon.awssdk.services.s3.model.UploadPartResponse; 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.ContentMetadataEncodingStrategy; import software.amazon.encryption.s3.internal.ConvertSDKRequests; import software.amazon.encryption.s3.internal.GetEncryptedObjectPipeline; import software.amazon.encryption.s3.internal.InstructionFileConfig; import software.amazon.encryption.s3.internal.MultiFileOutputStream; import software.amazon.encryption.s3.internal.MultipartUploadObjectPipeline; import software.amazon.encryption.s3.internal.PutEncryptedObjectPipeline; +import software.amazon.encryption.s3.internal.ReEncryptInstructionFileRequest; +import software.amazon.encryption.s3.internal.ReEncryptInstructionFileResponse; import software.amazon.encryption.s3.internal.UploadObjectObserver; import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.CryptographicMaterialsManager; +import software.amazon.encryption.s3.materials.DecryptMaterialsRequest; +import software.amazon.encryption.s3.materials.DecryptionMaterials; import software.amazon.encryption.s3.materials.DefaultCryptoMaterialsManager; +import software.amazon.encryption.s3.materials.EncryptedDataKey; +import software.amazon.encryption.s3.materials.EncryptionMaterials; import software.amazon.encryption.s3.materials.Keyring; import software.amazon.encryption.s3.materials.KmsKeyring; import software.amazon.encryption.s3.materials.MultipartConfiguration; import software.amazon.encryption.s3.materials.PartialRsaKeyPair; +import software.amazon.encryption.s3.materials.RawKeyring; import software.amazon.encryption.s3.materials.RsaKeyring; import javax.crypto.SecretKey; @@ -69,6 +79,7 @@ import java.security.Provider; import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -81,7 +92,8 @@ import java.util.function.Consumer; import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_BUFFER_SIZE_BYTES; -import static software.amazon.encryption.s3.S3EncryptionClientUtilities.INSTRUCTION_FILE_SUFFIX; + +import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; import static software.amazon.encryption.s3.S3EncryptionClientUtilities.MAX_ALLOWED_BUFFER_SIZE_BYTES; import static software.amazon.encryption.s3.S3EncryptionClientUtilities.MIN_ALLOWED_BUFFER_SIZE_BYTES; import static software.amazon.encryption.s3.S3EncryptionClientUtilities.instructionFileKeysToDelete; @@ -97,6 +109,9 @@ public class S3EncryptionClient extends DelegatingS3Client { public static final ExecutionAttribute> ENCRYPTION_CONTEXT = new ExecutionAttribute<>("EncryptionContext"); public static final ExecutionAttribute CONFIGURATION = new ExecutionAttribute<>("MultipartConfiguration"); + //Used for specifying custom instruction file suffix on a per-request basis + public static final ExecutionAttribute CUSTOM_INSTRUCTION_FILE_SUFFIX = new ExecutionAttribute<>("CustomInstructionFileSuffix"); + private final S3Client _wrappedClient; private final S3AsyncClient _wrappedAsyncClient; private final CryptographicMaterialsManager _cryptoMaterialsManager; @@ -143,6 +158,18 @@ public static Consumer withAdditionalCo builder.putExecutionAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT, encryptionContext); } + /** + * Attaches a custom instruction file suffix to a request. Must be used as a parameter to + * {@link S3Request#overrideConfiguration()} in the request. + * This allows specifying a custom suffix for the instruction file on a per-request basis. + * @param customInstructionFileSuffix the custom suffix to use for the instruction file. + * @return Consumer for use in overrideConfiguration() + */ + public static Consumer withCustomInstructionFileSuffix(String customInstructionFileSuffix) { + return builder -> + builder.putExecutionAttribute(S3EncryptionClient.CUSTOM_INSTRUCTION_FILE_SUFFIX, customInstructionFileSuffix); + } + /** * Attaches multipart configuration to a request. Must be used as a parameter to * {@link S3Request#overrideConfiguration()} in the request. @@ -154,7 +181,6 @@ public static Consumer withAdditionalCo builder.putExecutionAttribute(S3EncryptionClient.CONFIGURATION, multipartConfiguration); } - /** * Attaches encryption context and multipart configuration to a request. * * Must be used as a parameter to @@ -172,6 +198,77 @@ public static Consumer withAdditionalCo .putExecutionAttribute(S3EncryptionClient.CONFIGURATION, multipartConfiguration); } + /** + * Re-encrypts an instruction file with a new keyring while preserving the original encrypted object in S3. + * This enables: + * 1. Key rotation by updating instruction file metadata without re-encrypting object content + * 2. Sharing encrypted objects with partners by creating new instruction files with a custom suffix using their public keys + *

+ * Key rotation scenarios: + * - Legacy to V3: Can rotate same wrapping key from legacy wrapping algorithms to fully supported wrapping algorithms + * - Within V3: When rotating the wrapping key, the new keyring must be different from the current keyring + * + * @param reEncryptInstructionFileRequest the request containing bucket, object key, new keyring, and optional instruction file suffix + * @return ReEncryptInstructionFileResponse containing the bucket, object key, and instruction file suffix used + * @throws S3EncryptionClientException if the new keyring has the same materials description as the current one + */ + public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstructionFileRequest reEncryptInstructionFileRequest) { + //Build request to retrieve the encrypted object and its associated instruction file + final GetObjectRequest request = GetObjectRequest.builder() + .bucket(reEncryptInstructionFileRequest.bucket()) + .key(reEncryptInstructionFileRequest.key()) + .build(); + + ResponseInputStream response = this.getObject(request); + ContentMetadataDecodingStrategy decodingStrategy = new ContentMetadataDecodingStrategy(_instructionFileConfig); + ContentMetadata contentMetadata = decodingStrategy.decode(request, response.response()); + + //Extract cryptographic parameters from the current instruction file that MUST be preserved during re-encryption + final AlgorithmSuite algorithmSuite = contentMetadata.algorithmSuite(); + final EncryptedDataKey originalEncryptedDataKey = contentMetadata.encryptedDataKey(); + final Map currentKeyringMaterialsDescription = contentMetadata.encryptedDataKeyMatDescOrContext(); + final byte[] iv = contentMetadata.contentIv(); + + //Decrypt the data key using the current keyring + DecryptionMaterials decryptedMaterials = this._cryptoMaterialsManager.decryptMaterials( + DecryptMaterialsRequest.builder() + .algorithmSuite(algorithmSuite) + .encryptedDataKeys(Collections.singletonList(originalEncryptedDataKey)) + .s3Request(request) + .build() + ); + + final byte[] plaintextDataKey = decryptedMaterials.plaintextDataKey(); + + //Prepare encryption materials with the decrypted data key + EncryptionMaterials encryptionMaterials = EncryptionMaterials.builder() + .algorithmSuite(algorithmSuite) + .plaintextDataKey(plaintextDataKey) + .s3Request(request) + .build(); + + //Re-encrypt the data key with the new keyring while preserving other cryptographic parameters + RawKeyring newKeyring = reEncryptInstructionFileRequest.newKeyring(); + EncryptionMaterials encryptedMaterials = newKeyring.onEncrypt(encryptionMaterials); + + final Map newMaterialsDescription = encryptedMaterials.materialsDescription().getMaterialsDescription(); + //Validate that the new keyring has different materials description than the old keyring + if (newMaterialsDescription.equals(currentKeyringMaterialsDescription)) { + throw new S3EncryptionClientException("New keyring must have new materials description!"); + } + + //Create or update instruction file with the re-encrypted metadata while preserving IV + ContentMetadataEncodingStrategy encodeStrategy = new ContentMetadataEncodingStrategy(_instructionFileConfig); + encodeStrategy.encodeMetadata(encryptedMaterials, iv, PutObjectRequest.builder() + .bucket(reEncryptInstructionFileRequest.bucket()) + .key(reEncryptInstructionFileRequest.key()) + .build(), reEncryptInstructionFileRequest.instructionFileSuffix()); + + return new ReEncryptInstructionFileResponse(reEncryptInstructionFileRequest.bucket(), + reEncryptInstructionFileRequest.key(), reEncryptInstructionFileRequest.instructionFileSuffix()); + + } + /** * See {@link S3EncryptionClient#putObject(PutObjectRequest, RequestBody)}. *

@@ -380,7 +477,7 @@ public DeleteObjectResponse deleteObject(DeleteObjectRequest deleteObjectRequest // Delete the object DeleteObjectResponse deleteObjectResponse = _wrappedAsyncClient.deleteObject(actualRequest).join(); // If Instruction file exists, delete the instruction file as well. - String instructionObjectKey = deleteObjectRequest.key() + INSTRUCTION_FILE_SUFFIX; + String instructionObjectKey = deleteObjectRequest.key() + DEFAULT_INSTRUCTION_FILE_SUFFIX; _wrappedAsyncClient.deleteObject(builder -> builder .overrideConfiguration(API_NAME_INTERCEPTOR) .bucket(deleteObjectRequest.bucket()) diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClientUtilities.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClientUtilities.java index 3abc60ab0..3d45849df 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClientUtilities.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClientUtilities.java @@ -15,7 +15,7 @@ */ public class S3EncryptionClientUtilities { - public static final String INSTRUCTION_FILE_SUFFIX = ".instruction"; + public static final String DEFAULT_INSTRUCTION_FILE_SUFFIX = ".instruction"; public static final long MIN_ALLOWED_BUFFER_SIZE_BYTES = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherBlockSizeBytes(); public static final long MAX_ALLOWED_BUFFER_SIZE_BYTES = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF.cipherMaxContentLengthBytes(); @@ -32,7 +32,7 @@ public class S3EncryptionClientUtilities { */ static List instructionFileKeysToDelete(final DeleteObjectsRequest request) { return request.delete().objects().stream() - .map(o -> o.toBuilder().key(o.key() + INSTRUCTION_FILE_SUFFIX).build()) + .map(o -> o.toBuilder().key(o.key() + DEFAULT_INSTRUCTION_FILE_SUFFIX).build()) .collect(Collectors.toList()); } } diff --git a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java index ac6068a19..4d61a2248 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadata.java @@ -64,7 +64,7 @@ public String encryptedDataKeyAlgorithm() { */ @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "False positive; underlying" + " implementation is immutable") - public Map encryptedDataKeyContext() { + public Map encryptedDataKeyMatDescOrContext() { return _encryptionContextOrMatDesc; } diff --git a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java index eba533135..3d1e4edd9 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataDecodingStrategy.java @@ -9,6 +9,7 @@ import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.NoSuchKeyException; +import software.amazon.encryption.s3.S3EncryptionClient; import software.amazon.encryption.s3.S3EncryptionClientException; import software.amazon.encryption.s3.algorithms.AlgorithmSuite; import software.amazon.encryption.s3.materials.EncryptedDataKey; @@ -24,7 +25,7 @@ import java.util.Map; import java.util.concurrent.CompletionException; -import static software.amazon.encryption.s3.S3EncryptionClientUtilities.INSTRUCTION_FILE_SUFFIX; +import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; public class ContentMetadataDecodingStrategy { @@ -224,9 +225,13 @@ private ContentMetadata decodeFromObjectMetadata(GetObjectRequest request, GetOb } private ContentMetadata decodeFromInstructionFile(GetObjectRequest request, GetObjectResponse response) { + String instructionFileSuffix = request.overrideConfiguration() + .flatMap(config -> config.executionAttributes().getOptionalAttribute(S3EncryptionClient.CUSTOM_INSTRUCTION_FILE_SUFFIX)) + .orElse(DEFAULT_INSTRUCTION_FILE_SUFFIX); + GetObjectRequest instructionGetObjectRequest = GetObjectRequest.builder() .bucket(request.bucket()) - .key(request.key() + INSTRUCTION_FILE_SUFFIX) + .key(request.key() + instructionFileSuffix) .build(); ResponseInputStream instruction; diff --git a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java index a2e7915df..dd7eb3e00 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ContentMetadataEncodingStrategy.java @@ -14,6 +14,8 @@ import java.util.HashMap; import java.util.Map; +import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; + public class ContentMetadataEncodingStrategy { private static final Base64.Encoder ENCODER = Base64.getEncoder(); @@ -24,16 +26,20 @@ public ContentMetadataEncodingStrategy(InstructionFileConfig instructionFileConf } public PutObjectRequest encodeMetadata(EncryptionMaterials materials, byte[] iv, PutObjectRequest putObjectRequest) { + return encodeMetadata(materials, iv, putObjectRequest, DEFAULT_INSTRUCTION_FILE_SUFFIX); + } + + public PutObjectRequest encodeMetadata(EncryptionMaterials materials, byte[] iv, PutObjectRequest putObjectRequest, String instructionFileSuffix) { if (_instructionFileConfig.isInstructionFilePutEnabled()) { final String metadataString = metadataToString(materials, iv); - _instructionFileConfig.putInstructionFile(putObjectRequest, metadataString); + _instructionFileConfig.putInstructionFile(putObjectRequest, metadataString, instructionFileSuffix); // the original request object is returned as-is return putObjectRequest; } else { Map newMetadata = addMetadataToMap(putObjectRequest.metadata(), materials, iv); return putObjectRequest.toBuilder() - .metadata(newMetadata) - .build(); + .metadata(newMetadata) + .build(); } } @@ -51,6 +57,7 @@ public CreateMultipartUploadRequest encodeMetadata(EncryptionMaterials materials .build(); } } + private String metadataToString(EncryptionMaterials materials, byte[] iv) { // this is just the metadata map serialized as JSON // so first get the Map diff --git a/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java b/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java index b0805a6ac..bc3563688 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java +++ b/src/main/java/software/amazon/encryption/s3/internal/GetEncryptedObjectPipeline.java @@ -85,7 +85,7 @@ private DecryptionMaterials prepareMaterialsFromRequest(final GetObjectRequest g .s3Request(getObjectRequest) .algorithmSuite(algorithmSuite) .encryptedDataKeys(encryptedDataKeys) - .encryptionContext(contentMetadata.encryptedDataKeyContext()) + .encryptionContext(contentMetadata.encryptedDataKeyMatDescOrContext()) .ciphertextLength(getObjectResponse.contentLength()) .contentRange(getObjectRequest.range()) .build(); diff --git a/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java b/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java index 59cccc788..d9eed1cf0 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java +++ b/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java @@ -15,7 +15,8 @@ import java.util.HashMap; import java.util.Map; -import static software.amazon.encryption.s3.S3EncryptionClientUtilities.INSTRUCTION_FILE_SUFFIX; + +import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; import static software.amazon.encryption.s3.internal.MetadataKeyConstants.INSTRUCTION_FILE; /** @@ -49,6 +50,10 @@ boolean isInstructionFilePutEnabled() { } PutObjectResponse putInstructionFile(PutObjectRequest request, String instructionFileContent) { + return putInstructionFile(request, instructionFileContent, DEFAULT_INSTRUCTION_FILE_SUFFIX); + } + + PutObjectResponse putInstructionFile(PutObjectRequest request, String instructionFileContent, String instructionFileSuffix) { // This shouldn't happen in practice because the metadata strategy will evaluate // if instruction file Puts are enabled before calling this method; check again anyway for robustness if (!_enableInstructionFilePut) { @@ -60,12 +65,11 @@ PutObjectResponse putInstructionFile(PutObjectRequest request, String instructio // It contains a key with no value identifying it as an instruction file instFileMetadata.put(INSTRUCTION_FILE, ""); - // In a future release, non-default suffixes will be supported. // Use toBuilder to keep all other fields the same as the actual request final PutObjectRequest instPutRequest = request.toBuilder() - .key(request.key() + INSTRUCTION_FILE_SUFFIX) - .metadata(instFileMetadata) - .build(); + .key(request.key() + instructionFileSuffix) + .metadata(instFileMetadata) + .build(); switch (_clientType) { case SYNCHRONOUS: return _s3Client.putObject(instPutRequest, RequestBody.fromString(instructionFileContent)); diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java new file mode 100644 index 000000000..69f7def52 --- /dev/null +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java @@ -0,0 +1,149 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.encryption.s3.internal; + +import software.amazon.encryption.s3.S3EncryptionClientException; +import software.amazon.encryption.s3.materials.AesKeyring; +import software.amazon.encryption.s3.materials.RawKeyring; + +import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; + +/** + * Request object for re-encrypting instruction files in S3. + * This request supports re-encryption operations using either AES or RSA keyrings. + * For AES keyrings, only the default instruction file suffix is supported. + * For RSA keyrings, both the default and custom instruction file suffixes are supported. + */ +public class ReEncryptInstructionFileRequest { + private final String bucket; + private final String key; + private final RawKeyring newKeyring; + private final String instructionFileSuffix; + + private ReEncryptInstructionFileRequest(Builder builder) { + bucket = builder.bucket; + key = builder.key; + newKeyring = builder.newKeyring; + instructionFileSuffix = builder.instructionFileSuffix; + } + + /** + * @return the S3 bucket name that contains the encrypted object and instruction file to re-encrypt + */ + public String bucket() { + return bucket; + } + + /** + * @return the S3 object key of the encrypted object whose instruction file will be re-encrypted + */ + public String key() { + return key; + } + + /** + * @return the new keyring (AES or RSA) that will be used to re-encrypt the instruction file + */ + public RawKeyring newKeyring() { + return newKeyring; + } + + /** + * @return the suffix to use for the instruction file. The default instruction file suffix is ".instruction" + */ + public String instructionFileSuffix() { + return instructionFileSuffix; + } + + /** + * Creates a builder that can be used to configure and create a {@link ReEncryptInstructionFileRequest} + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for ReEncryptInstructionFileRequest. + */ + public static class Builder { + private String bucket; + private String key; + private RawKeyring newKeyring; + private String instructionFileSuffix = DEFAULT_INSTRUCTION_FILE_SUFFIX; + + /** + * Sets the S3 bucket name for the re-encryption of instruction file. + * + * @param bucket the S3 bucket name + * @return a reference to this object so that method calls can be chained together. + */ + public Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + /** + * Sets the S3 object key for the re-encryption of instruction file. + * + * @param key the S3 object key + * @return a reference to this object so that method calls can be chained together. + */ + public Builder key(String key) { + this.key = key; + return this; + } + + /** + * Sets the new keyring for re-encryption of instruction file. + * + * @param newKeyring the new keyring for re-encryption + * @return a reference to this object so that method calls can be chained together. + */ + public Builder newKeyring(RawKeyring newKeyring) { + this.newKeyring = newKeyring; + return this; + } + + /** + * Sets a custom instruction file suffix for the re-encrypted instruction file. + * For AES keyrings, only the default instruction file suffix is allowed. + * For RSA keyrings, both the default and custom instruction file suffixes are allowed. + * Note: The "." prefix is automatically added to the suffix + * + * @param instructionFileSuffix the instruction file suffix + * @return a reference to this object so that method calls can be chained together. + */ + public Builder instructionFileSuffix(String instructionFileSuffix) { + this.instructionFileSuffix = "." + instructionFileSuffix; + return this; + } + + /** + * Validates and builds the ReEncryptInstructionFileRequest according + * to the configuration options passed to the Builder object. + * + * @return an instance of the ReEncryptInstructionFileRequest + */ + public ReEncryptInstructionFileRequest build() { + if (bucket == null || bucket.isEmpty()) { + throw new S3EncryptionClientException("Bucket must be provided!"); + } + if (key == null || key.isEmpty()) { + throw new S3EncryptionClientException("Key must be provided!"); + } + if (newKeyring == null) { + throw new S3EncryptionClientException("New keyring must be provided!"); + } + if (newKeyring instanceof AesKeyring) { + if (!instructionFileSuffix.equals(DEFAULT_INSTRUCTION_FILE_SUFFIX)) { + throw new S3EncryptionClientException("Custom Instruction file suffix is not applicable for AES keyring!"); + } + } + return new ReEncryptInstructionFileRequest(this); + } + + } + +} diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java new file mode 100644 index 000000000..37ec8a919 --- /dev/null +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java @@ -0,0 +1,48 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.encryption.s3.internal; + +/** + * Response object returned after re-encrypting an instruction file in S3. + * Contains the S3 bucket name, object key, and instruction file suffix used for the re-encrypted instruction file + */ +public class ReEncryptInstructionFileResponse { + private final String bucket; + private final String key; + private final String instructionFileSuffix; + + /** + * Creates a new ReEncryptInstructionFileResponse object with the specified parameters. + * + * @param bucket the S3 bucket containing the re-encrypted instruction file + * @param key the S3 object key of the encrypted object in S3 + * @param instructionFileSuffix the suffix used for the instruction file + */ + public ReEncryptInstructionFileResponse(String bucket, String key, String instructionFileSuffix) { + this.bucket = bucket; + this.key = key; + this.instructionFileSuffix = instructionFileSuffix.substring(1); + } + + /** + * @return the S3 bucket containing the re-encrypted instruction file + */ + public String bucket() { + return bucket; + } + + /** + * @return the S3 object key of the encrypted object in S3 + */ + public String key() { + return key; + } + + /** + * @return the instruction file suffix used for the instruction file + */ + public String instructionFileSuffix() { + return instructionFileSuffix; + } +} + diff --git a/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java b/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java index c5de2bf38..ee09b56cf 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java +++ b/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java @@ -1,6 +1,5 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 - package software.amazon.encryption.s3.materials; import java.util.Collection; @@ -20,92 +19,119 @@ public class MaterialsDescription implements Map { private MaterialsDescription(Builder builder) { this.materialsDescription = Collections.unmodifiableMap(new HashMap<>(builder.materialsDescription)); } + + /** + * @return a new builder instance + */ public static Builder builder() { return new Builder(); } + + /** + * @return the materials description map + */ public Map getMaterialsDescription() { - return this.materialsDescription; + return this.materialsDescription; + } + + @Override + public int size() { + return materialsDescription.size(); + } + + @Override + public boolean isEmpty() { + return materialsDescription.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return materialsDescription.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return materialsDescription.containsValue(value); + } + + @Override + public String get(Object key) { + return materialsDescription.get(key); } - @Override - public int size() { - return materialsDescription.size(); - } - - @Override - public boolean isEmpty() { - return materialsDescription.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return materialsDescription.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return materialsDescription.containsValue(value); - } - - @Override - public String get(Object key) { - return materialsDescription.get(key); - } - - @Override - public String put(String key, String value) { - throw new UnsupportedOperationException("This map is immutable"); - } - - @Override - public String remove(Object key) { - return materialsDescription.remove(key); - } - - @Override - public void putAll(Map m) { + @Override + public String put(String key, String value) { throw new UnsupportedOperationException("This map is immutable"); - } - - @Override - public void clear() { - materialsDescription.clear(); - } - - @Override - public Set keySet() { - return materialsDescription.keySet(); - } - - @Override - public Collection values() { - return materialsDescription.values(); - } - - @Override - public Set> entrySet() { - return materialsDescription.entrySet(); - } - - - public static class Builder { - private final Map materialsDescription = new HashMap<>(); - public Builder put(String key, String value) { - if (key == null || value == null) { - throw new IllegalArgumentException("Key and value must not be null"); + } + + @Override + public String remove(Object key) { + return materialsDescription.remove(key); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("This map is immutable"); + } + + @Override + public void clear() { + materialsDescription.clear(); + } + + @Override + public Set keySet() { + return materialsDescription.keySet(); + } + + @Override + public Collection values() { + return materialsDescription.values(); + } + + @Override + public Set> entrySet() { + return materialsDescription.entrySet(); + } + + /** + * Builder for MaterialsDescription. + */ + public static class Builder { + private final Map materialsDescription = new HashMap<>(); + + /** + * @param key the key to add + * @param value the value to add + * @return a reference to this object so that method calls can be chained together. + * @throws IllegalArgumentException if key or value is null + */ + public Builder put(String key, String value) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + materialsDescription.put(key, value); + return this; + } + + /** + * @param description the map of key-value pairs to add + * @return a reference to this object so that method calls can be chained together. + * @throws IllegalArgumentException if description is null + */ + public Builder putAll(Map description) { + if (description == null) { + throw new IllegalArgumentException("Description must not be null"); } - materialsDescription.put(key, value); + materialsDescription.putAll(description); return this; - } - public Builder putAll(Map description) { - if (description == null) { - throw new IllegalArgumentException("Description must not be null"); } - materialsDescription.putAll(description); - return this; - } - public MaterialsDescription build() { - return new MaterialsDescription(this); - } - } + + /** + * @return the built MaterialsDescription + */ + public MaterialsDescription build() { + return new MaterialsDescription(this); + } + } } \ No newline at end of file diff --git a/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java index 1fc7b15f8..1cd82673a 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java @@ -16,6 +16,13 @@ protected RawKeyring(Builder builder) { _materialsDescription = builder._materialsDescription; } + /** + * Modifies encryption materials with the keyring's materials description if present. + * Issues a warning if encryption context is found, as it provides no security benefit for raw keyrings. + * + * @param materials the encryption materials to modify + * @return modified encryption materials with the keyring's materials description or original encryption materials if no materials description is set + */ public EncryptionMaterials modifyMaterialsForRawKeyring(EncryptionMaterials materials) { warnIfEncryptionContextIsPresent(materials); if (_materialsDescription != null && !_materialsDescription.isEmpty()) { @@ -46,6 +53,13 @@ public void warnIfEncryptionContextIsPresent(EncryptionMaterials materials) { "stored in the material description. Provide a MaterialDescription in the Keyring's builder instead.")); } + /** + * Abstract builder for RawKeyring implementations. + * Provides common functionality for setting materials description on raw keyrings. + * + * @param the type of keyring being built + * @param the type of builder + */ public static abstract class Builder> extends S3Keyring.Builder { @@ -55,6 +69,13 @@ protected Builder() { super(); } + /** + * Sets the materials description for this keyring. + * Materials description provides additional metadata for raw keyrings. + * + * @param materialsDescription the materials description to associate with this keyring. + * @return a reference to this object so that method calls can be chained together. + */ public BuilderT materialsDescription(MaterialsDescription materialsDescription) { _materialsDescription = materialsDescription; return builder(); diff --git a/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java b/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java index 6b268641c..fce9788fa 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/S3Keyring.java @@ -126,7 +126,7 @@ public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List> { private boolean _enableLegacyWrappingAlgorithms = false; - private SecureRandom _secureRandom; + private SecureRandom _secureRandom = new SecureRandom(); private DataKeyGenerator _dataKeyGenerator = new DefaultDataKeyGenerator(); protected Builder() {} diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java index fb8dad2e9..c7bd28fa7 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientCompatibilityTest.java @@ -15,15 +15,9 @@ import com.amazonaws.services.s3.model.EncryptedPutObjectRequest; import com.amazonaws.services.s3.model.EncryptionMaterials; import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; -import com.amazonaws.services.s3.model.GetObjectMetadataRequest; -import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; -import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; import com.amazonaws.services.s3.model.KMSEncryptionMaterials; import com.amazonaws.services.s3.model.KMSEncryptionMaterialsProvider; -import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; -import com.amazonaws.services.s3.model.StorageClass; -import com.amazonaws.services.s3.model.UploadObjectRequest; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.ResponseBytes; @@ -34,25 +28,20 @@ import software.amazon.awssdk.services.s3.model.MetadataDirective; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import software.amazon.encryption.s3.internal.InstructionFileConfig; -import software.amazon.encryption.s3.utils.BoundedInputStream; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ExecutionException; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static software.amazon.encryption.s3.S3EncryptionClient.builder; import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.KMS_KEY_ID; diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java index d246b4cc1..fd181aaef 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java @@ -53,7 +53,7 @@ public static void setUp() throws NoSuchAlgorithmException { public void testAesMaterialsDescriptionInObjectMetadata() { AesKeyring aesKeyring = AesKeyring.builder() .wrappingKey(AES_KEY) - .secureRandom(new SecureRandom()) + .materialsDescription(MaterialsDescription.builder() .put("version", "1.0") .build()) @@ -123,7 +123,7 @@ public void testRsaMaterialsDescriptionInObjectMetadata() { public void testAesMaterialsDescriptionInInstructionFile() { AesKeyring aesKeyring = AesKeyring.builder() .wrappingKey(AES_KEY) - .secureRandom(new SecureRandom()) + .materialsDescription(MaterialsDescription.builder() .put("version", "1.0") .build()) @@ -227,7 +227,7 @@ public void testRsaMaterialsDescriptionInInstructionFile() { public void testAesKeyringMatDescOverridesPutObjectEncryptionContext() { AesKeyring aesKeyring = AesKeyring.builder() .wrappingKey(AES_KEY) - .secureRandom(new SecureRandom()) + .materialsDescription(MaterialsDescription.builder() .put("version", "1.0") .build()) diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java new file mode 100644 index 000000000..e4fda2352 --- /dev/null +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java @@ -0,0 +1,1631 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.encryption.s3; + +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.s3.AmazonS3Encryption; +import com.amazonaws.services.s3.AmazonS3EncryptionClient; +import com.amazonaws.services.s3.AmazonS3EncryptionClientV2; +import com.amazonaws.services.s3.AmazonS3EncryptionV2; +import com.amazonaws.services.s3.model.CryptoConfiguration; +import com.amazonaws.services.s3.model.CryptoConfigurationV2; +import com.amazonaws.services.s3.model.CryptoMode; +import com.amazonaws.services.s3.model.CryptoStorageMode; +import com.amazonaws.services.s3.model.EncryptedGetObjectRequest; +import com.amazonaws.services.s3.model.EncryptionMaterials; +import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; +import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.ResponseBytes; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.protocols.jsoncore.JsonNode; +import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; +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.encryption.s3.internal.InstructionFileConfig; +import software.amazon.encryption.s3.internal.ReEncryptInstructionFileRequest; +import software.amazon.encryption.s3.internal.ReEncryptInstructionFileResponse; +import software.amazon.encryption.s3.materials.AesKeyring; +import software.amazon.encryption.s3.materials.MaterialsDescription; +import software.amazon.encryption.s3.materials.PartialRsaKeyPair; +import software.amazon.encryption.s3.materials.RsaKeyring; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + +public class S3EncryptionClientReEncryptInstructionFileTest { + private static SecretKey AES_KEY; + private static SecretKey AES_KEY_TWO; + private static KeyPair RSA_KEY_PAIR; + private static KeyPair RSA_KEY_PAIR_TWO; + + @BeforeAll + public static void setUp() throws NoSuchAlgorithmException { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); + AES_KEY = keyGen.generateKey(); + AES_KEY_TWO = keyGen.generateKey(); + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); + keyPairGen.initialize(2048); + RSA_KEY_PAIR = keyPairGen.generateKeyPair(); + RSA_KEY_PAIR_TWO = keyPairGen.generateKeyPair(); + } + + @Test + public void testAesReEncryptInstructionFileFailsWithSameMaterialsDescription() { + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-with-same-materials-description-test"); + final String input = "Testing re-encryption of instruction file with AES Keyring"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + try { + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + throw new RuntimeException("Expected failure"); + } catch (S3EncryptionClientException e) {; + assertTrue(e.getMessage().contains("New keyring must have new materials description!")); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaReEncryptInstructionFileWithCustomSuffixFailsWithSameMaterialsDescription() { + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-with-custom-suffix-and-same-materials-description-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .instructionFileSuffix("third-party-access-instruction-file") + .newKeyring(thirdPartyKeyring) + .build(); + + try { + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + throw new RuntimeException("Expected failure"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("New keyring must have new materials description!")); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testAesReEncryptInstructionFileRejectsCustomInstructionFileSuffix() { + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-with-custom-suffix-test"); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + try { + ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .instructionFileSuffix("custom-suffix") + .build(); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Custom Instruction file suffix is not applicable for AES keyring!")); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testAesKeyringReEncryptInstructionFile() { + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-test"); + final String input = "Testing re-encryption of instruction file with AES Keyring"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String instructionFileContent = instructionFile.asUtf8String(); + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFileContent); + + String originalIv = instructionFileNode.asObject().get("x-amz-iv").asString(); + String originalEncryptedDataKeyAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String originalEncryptedDataKey = instructionFileNode.asObject().get("x-amz-key-v2").asString(); + JsonNode originalMatDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + assertEquals("no", originalMatDescNode.asObject().get("rotated").asString()); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + assertEquals(BUCKET, response.bucket()); + assertEquals(objectKey, response.key()); + assertEquals("instruction", response.instructionFileSuffix()); + + S3Client rotatedWrappedClient = S3Client.create(); + + S3EncryptionClient rotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(rotatedWrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + try { + client.getObjectAsBytes(GetObjectRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .build()); + throw new RuntimeException("Expected exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Unable to AES/GCM unwrap")); + } + + ResponseBytes getResponse = rotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, getResponse.asUtf8String()); + + ResponseBytes reEncryptedInstructionFile = rotatedWrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String newInstructionFileContent = reEncryptedInstructionFile.asUtf8String(); + JsonNode newInstructionFileNode = parser.parse(newInstructionFileContent); + + String postReEncryptionIv = newInstructionFileNode.asObject().get("x-amz-iv").asString(); + String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String postReEncryptionEncryptedDataKey = newInstructionFileNode.asObject().get("x-amz-key-v2").asString(); + JsonNode postReEncryptionMatDescNode = parser.parse(newInstructionFileNode.asObject().get("x-amz-matdesc").asString()); + + assertEquals("yes", postReEncryptionMatDescNode.asObject().get("rotated").asString()); + assertEquals(originalIv, postReEncryptionIv); + assertEquals(originalEncryptedDataKeyAlgorithm, postReEncryptionEncryptedDataKeyAlgorithm); + assertNotEquals(originalEncryptedDataKey, postReEncryptionEncryptedDataKey); + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaKeyringReEncryptInstructionFile() { + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring oldKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String instructionFileContent = instructionFile.asUtf8String(); + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFileContent); + + String originalIv = instructionFileNode.asObject().get("x-amz-iv").asString(); + String originalEncryptedDataKeyAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String originalEncryptedDataKey = instructionFileNode.asObject().get("x-amz-key-v2").asString(); + JsonNode originalMatDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + assertEquals("no", originalMatDescNode.asObject().get("rotated").asString()); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + assertEquals(BUCKET, response.bucket()); + assertEquals(objectKey, response.key()); + assertEquals("instruction", response.instructionFileSuffix()); + + S3Client rotatedWrappedClient = S3Client.create(); + + S3EncryptionClient rotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(rotatedWrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + try { + client.getObjectAsBytes(GetObjectRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .build()); + throw new RuntimeException("Expected exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); + } + + ResponseBytes getResponse = rotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, getResponse.asUtf8String()); + + ResponseBytes reEncryptedInstructionFile = rotatedWrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + String newInstructionFileContent = reEncryptedInstructionFile.asUtf8String(); + JsonNode newInstructionFileNode = parser.parse(newInstructionFileContent); + + String postReEncryptionIv = newInstructionFileNode.asObject().get("x-amz-iv").asString(); + String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String postReEncryptionEncryptedDataKey = newInstructionFileNode.asObject().get("x-amz-key-v2").asString(); + JsonNode postReEncryptionMatDescNode = parser.parse(newInstructionFileNode.asObject().get("x-amz-matdesc").asString()); + + assertEquals("yes", postReEncryptionMatDescNode.asObject().get("rotated").asString()); + assertEquals(originalIv, postReEncryptionIv); + assertEquals(originalEncryptedDataKeyAlgorithm, postReEncryptionEncryptedDataKeyAlgorithm); + assertNotEquals(originalEncryptedDataKey, postReEncryptionEncryptedDataKey); + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffix() { + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-with-custom-suffix-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .instructionFileSuffix("third-party-access-instruction-file") + .newKeyring(thirdPartyKeyring) + .build(); + + S3EncryptionClient thirdPartyClient = S3EncryptionClient.builder() + .keyring(thirdPartyKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + assertEquals(BUCKET, reEncryptInstructionFileResponse.bucket()); + assertEquals(objectKey, reEncryptInstructionFileResponse.key()); + assertEquals("third-party-access-instruction-file", reEncryptInstructionFileResponse.instructionFileSuffix()); + + ResponseBytes clientInstructionFile= wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + JsonNodeParser parser = JsonNodeParser.create(); + + String clientInstructionFileContent = clientInstructionFile.asUtf8String(); + + JsonNode clientInstructionFileNode = parser.parse(clientInstructionFileContent); + String clientIv = clientInstructionFileNode.asObject().get("x-amz-iv").asString(); + String clientEncryptedDataKeyAlgorithm = clientInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String clientEncryptedDataKey = clientInstructionFileNode.asObject().get("x-amz-key-v2").asString(); + JsonNode clientMatDescNode = parser.parse(clientInstructionFileNode.asObject().get("x-amz-matdesc").asString()); + + assertEquals("yes", clientMatDescNode.asObject().get("isOwner").asString()); + assertEquals("admin", clientMatDescNode.asObject().get("access-level").asString()); + + ResponseBytes thirdPartyInstFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".third-party-access-instruction-file") + .build()); + + String thirdPartyInstructionFileContent = thirdPartyInstFile.asUtf8String(); + JsonNode thirdPartyInstructionFileNode = parser.parse(thirdPartyInstructionFileContent); + String thirdPartyIv = thirdPartyInstructionFileNode.asObject().get("x-amz-iv").asString(); + String thirdPartyEncryptedDataKeyAlgorithm = thirdPartyInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String thirdPartyEncryptedDataKey = thirdPartyInstructionFileNode.asObject().get("x-amz-key-v2").asString(); + JsonNode thirdPartyMatDescNode = parser.parse(thirdPartyInstructionFileNode.asObject().get("x-amz-matdesc").asString()); + assertEquals("no", thirdPartyMatDescNode.asObject().get("isOwner").asString()); + assertEquals("user", thirdPartyMatDescNode.asObject().get("access-level").asString()); + + assertEquals(clientIv, thirdPartyIv); + assertEquals(clientEncryptedDataKeyAlgorithm, thirdPartyEncryptedDataKeyAlgorithm); + assertNotEquals(clientEncryptedDataKey, thirdPartyEncryptedDataKey); + + try { + ResponseBytes thirdPartyDecryptObject = thirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + throw new RuntimeException("Expected exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); + } + + ResponseBytes thirdPartyDecryptedObject = thirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) + .build()); + + assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); + + ResponseBytes clientDecryptedObject = client.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, clientDecryptedObject.asUtf8String()); + + deleteObject(BUCKET, objectKey, client); + + } + + @Test + public void testReEncryptInstructionFileV2AesToV3() { + final String input = "Testing re-encryption of instruction file with AES keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-aes-to-v3-re-encrypt-instruction-file-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + ); + + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider newMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY_TWO) + .addDescription("rotated", "yes") + ); + + CryptoConfigurationV2 newCryptoConfig = + new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2RotatedClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(newCryptoConfig) + .withEncryptionMaterialsProvider(newMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, v3DecryptObject.asUtf8String()); + + String v2DecryptObject = v2RotatedClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v2DecryptObject); + + deleteObject(BUCKET, objectKey, v3RotatedClient); + + } + + @Test + public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() throws IOException { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient.builder() + .keyring(thirdPartyKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider thirdPartyMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("isOwner", "no") + .addDescription("access-level", "user") + ); + + CryptoConfigurationV2 thirdPartyCryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2ThirdPartyRotatedClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(thirdPartyCryptoConfig) + .withEncryptionMaterialsProvider(thirdPartyMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3OriginalClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, v3DecryptObject.asUtf8String()); + + String v2DecryptObject = v2OriginalClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v2DecryptObject); + + ResponseBytes thirdPartyDecryptedObject = v3ThirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) + .build()); + + assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); + + EncryptedGetObjectRequest request = new EncryptedGetObjectRequest(BUCKET, objectKey) + .withInstructionFileSuffix("third-party-access-instruction-file"); + + String v2ThirdPartyDecryptObject = IOUtils.toString(v2ThirdPartyRotatedClient.getObject(request).getObjectContent(), StandardCharsets.UTF_8); + assertEquals(input, v2ThirdPartyDecryptObject); + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testReEncryptInstructionFileV2RsaToV3() throws IOException { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + ); + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider newMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("rotated", "yes") + ); + + CryptoConfigurationV2 newCryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2RotatedClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(newCryptoConfig) + .withEncryptionMaterialsProvider(newMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, v3DecryptObject.asUtf8String()); + + String v2DecryptObject = v2RotatedClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v2DecryptObject); + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testReEncryptInstructionFileUpgradesV1AesToV3() { + final String input = "Testing re-encryption of instruction file, upgrading legacy V1 AES to V3"; + final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + + CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .enableLegacyUnauthenticatedModes(true) + .enableLegacyWrappingAlgorithms(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider newMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") + ); + + CryptoConfiguration newCryptoConfig = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(newCryptoConfig) + .withEncryptionMaterials(newMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, v3DecryptObject.asUtf8String()); + + ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); + String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + assertEquals("AES/GCM", wrappingAlgorithm); + + String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v1DecryptObject); + + deleteObject(BUCKET, objectKey, v3RotatedClient); + + } + + @Test + public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaToV3() throws IOException { + final String input = "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient.builder() + .keyring(thirdPartyKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider thirdPartyMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("isOwner", "no") + .addDescription("access-level", "user") + ); + + CryptoConfiguration thirdPartyCryptoConfig = + new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1ThirdPartyRotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(thirdPartyCryptoConfig) + .withEncryptionMaterials(thirdPartyMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3OriginalClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, v3DecryptObject.asUtf8String()); + + String v2DecryptObject = v1OriginalClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v2DecryptObject); + + ResponseBytes thirdPartyDecryptedObject = v3ThirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) + .build()); + + assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); + + EncryptedGetObjectRequest request = new EncryptedGetObjectRequest(BUCKET, objectKey) + .withInstructionFileSuffix("third-party-access-instruction-file"); + + String v1ThirdPartyDecryptObject = IOUtils.toString(v1ThirdPartyRotatedClient.getObject(request).getObjectContent(), StandardCharsets.UTF_8); + assertEquals(input, v1ThirdPartyDecryptObject); + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testReEncryptInstructionFileUpgradesV1RsaToV3() throws IOException { + final String input = "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider newMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") + ); + + CryptoConfiguration newCryptoConfig = + new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(newCryptoConfig) + .withEncryptionMaterials(newMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, v3DecryptObject.asUtf8String()); + + String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v1DecryptObject); + + ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); + String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + assertEquals("RSA-OAEP-SHA1", wrappingAlgorithm); + + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testReEncryptInstructionFileUpgradesV1AesEncryptionOnlyToV3() { + final String input = "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only AES to V3"; + final String objectKey = appendTestSuffix("v1-aes-encryption-only-to-v3-re-encrypt-instruction-file-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .enableLegacyUnauthenticatedModes(true) + .enableLegacyWrappingAlgorithms(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider newMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") + ); + + CryptoConfiguration newCryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(newCryptoConfig) + .withEncryptionMaterials(newMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, v3DecryptObject.asUtf8String()); + + ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); + String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + assertEquals("AES/GCM", wrappingAlgorithm); + + try { + String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); + throw new RuntimeException("V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to AES/GCM algorithm upgrade"); + } catch (AmazonClientException e) { + assertTrue(e.getMessage().contains("An exception was thrown when attempting to decrypt the Content Encryption Key")); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + + } + @Test + public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaEncryptionOnlyToV3() throws IOException { + final String input = "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only RSA to V3"; + final String objectKey = appendTestSuffix("v1-rsa-encryption-only-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient.builder() + .keyring(thirdPartyKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider thirdPartyMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("isOwner", "no") + .addDescription("access-level", "user") + ); + + CryptoConfiguration thirdPartyCryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1ThirdPartyRotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(thirdPartyCryptoConfig) + .withEncryptionMaterials(thirdPartyMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3OriginalClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + assertEquals(input, v3DecryptObject.asUtf8String()); + + String v1DecryptObject = v1OriginalClient.getObjectAsString(BUCKET, objectKey); + assertEquals(input, v1DecryptObject); + + ResponseBytes thirdPartyDecryptedObject = v3ThirdPartyClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) + .build()); + + assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); + + EncryptedGetObjectRequest request = new EncryptedGetObjectRequest(BUCKET, objectKey) + .withInstructionFileSuffix("third-party-access-instruction-file"); + + try { + String v1ThirdPartyDecryptObject = IOUtils.toString(v1ThirdPartyRotatedClient.getObject(request).getObjectContent(), StandardCharsets.UTF_8); + throw new RuntimeException("V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to RSA algorithm upgrade"); + } catch (SecurityException e) { + assertTrue(e.getMessage().contains("The content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted.")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testReEncryptInstructionFileUpgradesV1RsaEncryptionOnlyToV3() throws IOException { + final String input = "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only RSA to V3"; + final String objectKey = appendTestSuffix("v1-rsa-encryption-only-to-v3-re-encrypt-instruction-file-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + EncryptionMaterialsProvider newMaterialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") + ); + + CryptoConfiguration newCryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(newCryptoConfig) + .withEncryptionMaterials(newMaterialsProvider) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + + ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build()); + + assertEquals(input, v3DecryptObject.asUtf8String()); + + try { + String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); + throw new RuntimeException("V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to RSA algorithm upgrade"); + } catch (SecurityException e) { + assertTrue(e.getMessage().contains("The content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted.")); + } + + ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder + .bucket(BUCKET) + .key(objectKey + ".instruction") + .build()); + + JsonNodeParser parser = JsonNodeParser.create(); + JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); + String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + assertEquals("RSA-OAEP-SHA1", wrappingAlgorithm); + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + +} diff --git a/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java b/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java index a70c8ed75..d40233ca0 100644 --- a/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java +++ b/src/test/java/software/amazon/encryption/s3/internal/ContentMetadataTest.java @@ -57,7 +57,7 @@ public void testEncryptedDataKeyAlgorithm() { @Test public void testEncryptedDataKeyContext() { - assertEquals(encryptedDataKeyContext, actualContentMetadata.encryptedDataKeyContext()); + assertEquals(encryptedDataKeyContext, actualContentMetadata.encryptedDataKeyMatDescOrContext()); } @Test diff --git a/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java b/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java index d6f7d59c8..29cd1ac21 100644 --- a/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java +++ b/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java @@ -52,6 +52,7 @@ public void testSimpleMaterialsDescription() { assertNull(e.getMessage()); } } + @Test public void testMaterialsDescriptionPutAll() { Map description = new HashMap<>(); From 5e414703918bc718cedd419ffc3de143a5f2c152 Mon Sep 17 00:00:00 2001 From: akareddy04 <142655873+akareddy04@users.noreply.github.com> Date: Fri, 18 Jul 2025 16:49:34 -0700 Subject: [PATCH 03/10] feat: Enforce Rotation Functionality for ReEncrypt-feature (#474) Implemented a stronger level of validation for reEncryptInstructionFile where, when enabled, the client will attempt to decrypt the re-encrypted instruction file with the old key material and throw an exception when decryption succeeds. This is a stronger level of validation that the wrapping key has been rotated than the standard assertion that the materials descriptions are different. --------- Co-authored-by: Anirav Kareddy --- .../ReEncryptInstructionFileExample.java | 2 + .../encryption/s3/S3EncryptionClient.java | 23 +- .../ReEncryptInstructionFileRequest.java | 22 + .../ReEncryptInstructionFileResponse.java | 14 +- ...ionClientReEncryptInstructionFileTest.java | 1738 ++++++++++++++++- 5 files changed, 1792 insertions(+), 7 deletions(-) diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java index c7affbde5..12f68ed54 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java @@ -123,6 +123,7 @@ public static void simpleAesKeyringReEncryptInstructionFile(final String bucket) .bucket(bucket) .key(objectKey) .newKeyring(newKeyring) + .enforceRotation(true) .build(); // Perform the re-encryption of the instruction file @@ -239,6 +240,7 @@ public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) .bucket(bucket) .key(objectKey) .newKeyring(newKeyring) + .enforceRotation(true) .build(); // Perform the re-encryption of the instruction file diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index bf0a468c5..ed57ed691 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -207,6 +207,7 @@ public static Consumer withAdditionalCo * Key rotation scenarios: * - Legacy to V3: Can rotate same wrapping key from legacy wrapping algorithms to fully supported wrapping algorithms * - Within V3: When rotating the wrapping key, the new keyring must be different from the current keyring + * - Enforce Rotation: When enabled, ensures old keyring cannot decrypt data encrypted by new keyring * * @param reEncryptInstructionFileRequest the request containing bucket, object key, new keyring, and optional instruction file suffix * @return ReEncryptInstructionFileResponse containing the bucket, object key, and instruction file suffix used @@ -257,6 +258,11 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru throw new S3EncryptionClientException("New keyring must have new materials description!"); } + // If enforceRotation is set to true, ensure that the old keyring cannot decrypt the newly encrypted data key + if (reEncryptInstructionFileRequest.enforceRotation()) { + enforceRotation(encryptedMaterials, request); + } + //Create or update instruction file with the re-encrypted metadata while preserving IV ContentMetadataEncodingStrategy encodeStrategy = new ContentMetadataEncodingStrategy(_instructionFileConfig); encodeStrategy.encodeMetadata(encryptedMaterials, iv, PutObjectRequest.builder() @@ -265,8 +271,23 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru .build(), reEncryptInstructionFileRequest.instructionFileSuffix()); return new ReEncryptInstructionFileResponse(reEncryptInstructionFileRequest.bucket(), - reEncryptInstructionFileRequest.key(), reEncryptInstructionFileRequest.instructionFileSuffix()); + reEncryptInstructionFileRequest.key(), reEncryptInstructionFileRequest.instructionFileSuffix(), reEncryptInstructionFileRequest.enforceRotation()); + + } + private void enforceRotation(EncryptionMaterials newEncryptionMaterials, GetObjectRequest request) { + try { + DecryptionMaterials decryptedMaterials = this._cryptoMaterialsManager.decryptMaterials( + DecryptMaterialsRequest.builder() + .algorithmSuite(newEncryptionMaterials.algorithmSuite()) + .encryptedDataKeys(newEncryptionMaterials.encryptedDataKeys()) + .s3Request(request) + .build() + ); + } catch (S3EncryptionClientException e) { + return; + } + throw new S3EncryptionClientException("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key"); } /** diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java index 69f7def52..a4b8d1f17 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java @@ -19,12 +19,14 @@ public class ReEncryptInstructionFileRequest { private final String key; private final RawKeyring newKeyring; private final String instructionFileSuffix; + private final boolean enforceRotation; private ReEncryptInstructionFileRequest(Builder builder) { bucket = builder.bucket; key = builder.key; newKeyring = builder.newKeyring; instructionFileSuffix = builder.instructionFileSuffix; + enforceRotation = builder.enforceRotation; } /** @@ -55,6 +57,11 @@ public String instructionFileSuffix() { return instructionFileSuffix; } + /** + * @return whether to enforce rotation for the re-encrypted instruction file + */ + public boolean enforceRotation() { return enforceRotation; } + /** * Creates a builder that can be used to configure and create a {@link ReEncryptInstructionFileRequest} * @@ -72,6 +79,7 @@ public static class Builder { private String key; private RawKeyring newKeyring; private String instructionFileSuffix = DEFAULT_INSTRUCTION_FILE_SUFFIX; + private boolean enforceRotation = false; /** * Sets the S3 bucket name for the re-encryption of instruction file. @@ -120,6 +128,20 @@ public Builder instructionFileSuffix(String instructionFileSuffix) { return this; } + /** + * Sets whether to enforce rotation for the re-encrypted instruction file. + * When enabled, the client will attempt to decrypt the re-encrypted instruction file with the old key material and + * throw an exception when decryption succeeds. This is a stronger level of validation that the wrapping key has been + * rotated than the standard assertion that the materials descriptions are different. + * + * @param enforceRotation whether to enforce rotation + * @return a reference to this object so that method calls can be chained together. + */ + public Builder enforceRotation(boolean enforceRotation) { + this.enforceRotation = enforceRotation; + return this; + } + /** * Validates and builds the ReEncryptInstructionFileRequest according * to the configuration options passed to the Builder object. diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java index 37ec8a919..c2b579670 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java @@ -4,12 +4,13 @@ /** * Response object returned after re-encrypting an instruction file in S3. - * Contains the S3 bucket name, object key, and instruction file suffix used for the re-encrypted instruction file + * Contains the S3 bucket name, object key, instruction file suffix, and rotation enforcement status for the re-encrypted instruction file */ public class ReEncryptInstructionFileResponse { private final String bucket; private final String key; private final String instructionFileSuffix; + private final boolean enforceRotation; /** * Creates a new ReEncryptInstructionFileResponse object with the specified parameters. @@ -17,11 +18,13 @@ public class ReEncryptInstructionFileResponse { * @param bucket the S3 bucket containing the re-encrypted instruction file * @param key the S3 object key of the encrypted object in S3 * @param instructionFileSuffix the suffix used for the instruction file + * @param enforceRotation whether rotation was enforced for the re-encrypted instruction file */ - public ReEncryptInstructionFileResponse(String bucket, String key, String instructionFileSuffix) { + public ReEncryptInstructionFileResponse(String bucket, String key, String instructionFileSuffix, boolean enforceRotation) { this.bucket = bucket; this.key = key; this.instructionFileSuffix = instructionFileSuffix.substring(1); + this.enforceRotation = enforceRotation; } /** @@ -38,6 +41,13 @@ public String key() { return key; } + /** + * @return whether rotation was enforced for the re-encrypted instruction file + */ + public boolean enforceRotation() { + return enforceRotation; + } + /** * @return the instruction file suffix used for the instruction file */ diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java index e4fda2352..02ab636ce 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java @@ -44,8 +44,10 @@ import java.security.PublicKey; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; @@ -112,7 +114,7 @@ public void testAesReEncryptInstructionFileFailsWithSameMaterialsDescription() { try { client.reEncryptInstructionFile(reEncryptInstructionFileRequest); throw new RuntimeException("Expected failure"); - } catch (S3EncryptionClientException e) {; + } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("New keyring must have new materials description!")); } @@ -287,6 +289,7 @@ public void testAesKeyringReEncryptInstructionFile() { assertEquals(BUCKET, response.bucket()); assertEquals(objectKey, response.key()); assertEquals("instruction", response.instructionFileSuffix()); + assertFalse(response.enforceRotation()); S3Client rotatedWrappedClient = S3Client.create(); @@ -411,6 +414,7 @@ public void testRsaKeyringReEncryptInstructionFile() { assertEquals(BUCKET, response.bucket()); assertEquals(objectKey, response.key()); assertEquals("instruction", response.instructionFileSuffix()); + assertFalse(response.enforceRotation()); S3Client rotatedWrappedClient = S3Client.create(); @@ -531,6 +535,7 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffix() { assertEquals(BUCKET, reEncryptInstructionFileResponse.bucket()); assertEquals(objectKey, reEncryptInstructionFileResponse.key()); assertEquals("third-party-access-instruction-file", reEncryptInstructionFileResponse.instructionFileSuffix()); + assertFalse(reEncryptInstructionFileResponse.enforceRotation()); ResponseBytes clientInstructionFile= wrappedClient.getObjectAsBytes(builder -> builder .bucket(BUCKET) @@ -686,7 +691,7 @@ public void testReEncryptInstructionFileV2AesToV3() { } @Test - public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() throws IOException { + public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() throws IOException{ final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); @@ -808,7 +813,7 @@ public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() throws IOExc } @Test - public void testReEncryptInstructionFileV2RsaToV3() throws IOException { + public void testReEncryptInstructionFileV2RsaToV3() { final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-test"); @@ -1141,7 +1146,7 @@ public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaToV3() thro } @Test - public void testReEncryptInstructionFileUpgradesV1RsaToV3() throws IOException { + public void testReEncryptInstructionFileUpgradesV1RsaToV3() { final String input = "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-test"); @@ -1628,4 +1633,1729 @@ public void testReEncryptInstructionFileUpgradesV1RsaEncryptionOnlyToV3() throws deleteObject(BUCKET, objectKey, v3OriginalClient); } + @Test + public void testAesKeyringReEncryptInstructionFileEnforceRotation() { + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-enforce-rotation-test"); + final String input = "Testing re-encryption of instruction file with AES Keyring and enforce rotation enabled"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testAesKeyringReEncryptInstructionFileEnforceRotationWithSameKey() { + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-enforce-rotation-with-same-key-test"); + final String input = "Testing re-encryption of instruction file with AES keyring and enforce rotation enabled"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileEnforceRotation() { + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring oldKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileEnforceRotationWithSameKey() { + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring oldKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-with-same-key-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotation() { + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-with-custom-suffix-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .enforceRotation(true) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); + + try { + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotationWithSameKey(){ + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-with-custom-suffix-and-same-key-test"); + final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject(builder -> builder + .bucket(BUCKET) + .key(objectKey) + .build(), RequestBody.fromString(input)); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .enforceRotation(true) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); + + try { + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, client); + } + + @Test + public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotation() { + final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-test"); + final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + + CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .enableLegacyUnauthenticatedModes(true) + .enableLegacyWrappingAlgorithms(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + } + + @Test + public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationWithSameKey() { + final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-test"); + final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + + CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .enableLegacyUnauthenticatedModes(true) + .enableLegacyWrappingAlgorithms(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + } + + @Test + public void testAesKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameKey() { + final String objectKey = appendTestSuffix("v2-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-test"); + final String input = "Testing re-encryption of instruction file from V2 to V3 with AES Keyring and enforce rotation enabled"; + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + ); + + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + } + + @Test + public void testAesKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { + final String objectKey = appendTestSuffix("v2-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-test"); + final String input = "Testing re-encryption of instruction file from V2 to V3 with AES Keyring and enforce rotation enabled"; + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + ); + + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV2ToV3EnforceRotation() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV2ToV3EnforceRotationWithSameKey() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + ); + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameKey() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + ); + CryptoConfigurationV2 cryptoConfig = + new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterialsProvider(materialsProvider) + .build(); + + v2OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotation() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameKey() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotation() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotationWithSameKey() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotationEncryptionOnly() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-encryption-only-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(thirdPartyPublicKey) + .privateKey(thirdPartyPrivateKey) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(thirdPartyPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotationWithSameKeyEncryptionOnly() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-encryption-only-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(clientPublicKey) + .privateKey(clientPrivateKey) + .build(); + + RsaKeyring clientKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(clientKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .wrappingKeyPair(clientPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("isOwner", "no") + .put("access-level", "user") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + + } + + @Test + public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationEncryptionOnly() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-encryption-only-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameKeyEncryptionOnly() { + final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-encryption-only-test"); + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + CryptoConfiguration cryptoConfig = + new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1OriginalClient.putObject(BUCKET, objectKey, input); + + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring originalKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(originalKeyring) + .enableLegacyWrappingAlgorithms(true) + .enableLegacyUnauthenticatedModes(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + RsaKeyring newKeyring = RsaKeyring.builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3OriginalClient); + } + + @Test + public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationEncryptionOnly() { + final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-encryption-only-test"); + final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + + CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .enableLegacyUnauthenticatedModes(true) + .enableLegacyWrappingAlgorithms(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY_TWO) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + assertTrue(response.enforceRotation()); + } catch (S3EncryptionClientException e) { + fail("Enforce rotation should not throw exception"); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + } + + @Test + public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationWithSameKeyEncryptionOnly() { + final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-encryption-only-test"); + final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + + EncryptionMaterialsProvider materialsProvider = + new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") + ); + + CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + .withStorageMode(CryptoStorageMode.InstructionFile); + + AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + .withCryptoConfiguration(cryptoConfig) + .withEncryptionMaterials(materialsProvider) + .build(); + + v1Client.putObject(BUCKET, objectKey, input); + + AesKeyring oldKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .enableLegacyWrappingAlgorithms(true) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build()) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + .keyring(oldKeyring) + .enableLegacyUnauthenticatedModes(true) + .enableLegacyWrappingAlgorithms(true) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + AesKeyring newKeyring = AesKeyring.builder() + .wrappingKey(AES_KEY) + .materialsDescription(MaterialsDescription.builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build()) + .build(); + + S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + .keyring(newKeyring) + .instructionFileConfig(InstructionFileConfig.builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build()) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); + + try { + ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + fail("Enforce rotation should throw exception"); + } catch (S3EncryptionClientException e) { + assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + } + + deleteObject(BUCKET, objectKey, v3RotatedClient); + } + } From dc29afba4cbc2b3c746ebc49107cb0478d37dc42 Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Mon, 21 Jul 2025 11:31:04 -0700 Subject: [PATCH 04/10] chore: applied formatting to code --- .prettierignore | 3 + .../ReEncryptInstructionFileExample.java | 413 +- .../ReEncryptInstructionFileRequest.java | 16 +- .../ReEncryptInstructionFileResponse.java | 9 +- .../s3/materials/MaterialsDescription.java | 221 +- .../encryption/s3/materials/RawKeyring.java | 51 +- .../s3/S3EncryptionClientMatDescTest.java | 369 +- ...ionClientReEncryptInstructionFileTest.java | 4453 +++++++++++------ .../materials/MaterialsDescriptionTest.java | 86 +- 9 files changed, 3503 insertions(+), 2118 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..1b8ac8894 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# Ignore artifacts: +build +coverage diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java index 12f68ed54..a29aa33b3 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java @@ -2,6 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3.examples; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import software.amazon.awssdk.core.ResponseBytes; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; @@ -16,20 +29,6 @@ import software.amazon.encryption.s3.materials.PartialRsaKeyPair; import software.amazon.encryption.s3.materials.RsaKeyring; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; - public class ReEncryptInstructionFileExample { /** @@ -69,21 +68,30 @@ public static void main(final String[] args) throws NoSuchAlgorithmException { * @param bucket The name of the Amazon S3 bucket to perform operations on. * @throws NoSuchAlgorithmException if AES algorithm is not available */ - public static void simpleAesKeyringReEncryptInstructionFile(final String bucket) throws NoSuchAlgorithmException { + public static void simpleAesKeyringReEncryptInstructionFile( + final String bucket + ) throws NoSuchAlgorithmException { // Set up the S3 object key and content to be encrypted - final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-test"); - final String input = "Testing re-encryption of instruction file with AES Keyring"; + final String objectKey = appendTestSuffix( + "aes-re-encrypt-instruction-file-test" + ); + final String input = + "Testing re-encryption of instruction file with AES Keyring"; // Generate the original AES key for initial encryption SecretKey originalAesKey = generateAesKey(); // Create the original AES keyring with materials description - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(originalAesKey) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "1.0") + .put("rotated", "no") + .build() + ) .build(); // Create a default S3 client for instruction file operations @@ -91,71 +99,87 @@ public static void simpleAesKeyringReEncryptInstructionFile(final String bucket) // Create the S3 Encryption Client with instruction file support enabled // The client can perform both putObject and getObject operations using the original AES key - S3EncryptionClient originalClient = S3EncryptionClient.builder() + S3EncryptionClient originalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); // Upload both the encrypted object and instruction file to the specified bucket in S3 - originalClient.putObject(builder -> builder - .bucket(bucket) - .key(objectKey) - .build(), RequestBody.fromString(input)); + originalClient.putObject( + builder -> builder.bucket(bucket).key(objectKey).build(), + RequestBody.fromString(input) + ); // Generate a new AES key for re-encryption (rotating wrapping key) SecretKey newAesKey = generateAesKey(); // Create a new keyring with the new AES key and updated materials description - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(newAesKey) - .materialsDescription(MaterialsDescription.builder() - .put("version", "2.0") - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "2.0") + .put("rotated", "yes") + .build() + ) .build(); // Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key // This updates the instruction file without touching the encrypted object - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(bucket) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(bucket) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); // Perform the re-encryption of the instruction file - ReEncryptInstructionFileResponse response = originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); // Verify that the original client can no longer decrypt the object // This proves that the instruction file has been successfully re-encrypted try { - originalClient.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .build()); - throw new RuntimeException("Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!"); + originalClient.getObjectAsBytes(builder -> + builder.bucket(bucket).key(objectKey).build() + ); + throw new RuntimeException( + "Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!" + ); } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("Unable to AES/GCM unwrap")); } // Create a new client with the rotated AES key - S3EncryptionClient newClient = S3EncryptionClient.builder() + S3EncryptionClient newClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); // Verify that the new client can successfully decrypt the object // This proves that the instruction file has been successfully re-encrypted - ResponseBytes decryptedObject = newClient.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .build()); + ResponseBytes decryptedObject = + newClient.getObjectAsBytes(builder -> + builder.bucket(bucket).key(objectKey).build() + ); // Assert that the decrypted object's content matches the original input assertEquals(input, decryptedObject.asUtf8String()); @@ -170,10 +194,15 @@ public static void simpleAesKeyringReEncryptInstructionFile(final String bucket) * @param bucket The name of the Amazon S3 bucket to perform operations on. * @throws NoSuchAlgorithmException if RSA algorithm is not available */ - public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) throws NoSuchAlgorithmException { + public static void simpleRsaKeyringReEncryptInstructionFile( + final String bucket + ) throws NoSuchAlgorithmException { // Set up the S3 object key and content to be encrypted - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring"; + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring"; // Generate the original RSA key pair for initial encryption KeyPair originalRsaKeyPair = generateRsaKeyPair(); @@ -181,18 +210,23 @@ public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) PrivateKey originalPrivateKey = originalRsaKeyPair.getPrivate(); // Create a partial RSA key pair for the original keyring - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); // Create the original RSA keyring with materials description - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "1.0") + .put("rotated", "no") + .build() + ) .build(); // Create a default S3 client for instruction file operations @@ -200,19 +234,23 @@ public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) // Create the S3 Encryption Client with instruction file support enabled // The client can perform both putObject and getObject operations using RSA keyring - S3EncryptionClient originalClient = S3EncryptionClient.builder() + S3EncryptionClient originalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); // Upload both the encrypted object and instruction file to the specified bucket in S3 - originalClient.putObject(builder -> builder - .bucket(bucket) - .key(objectKey) - .build(), RequestBody.fromString(input)); + originalClient.putObject( + builder -> builder.bucket(bucket).key(objectKey).build(), + RequestBody.fromString(input) + ); // Generate a new RSA key pair for the new RSA keyring KeyPair newKeyPair = generateRsaKeyPair(); @@ -220,59 +258,72 @@ public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) PrivateKey newPrivateKey = newKeyPair.getPrivate(); // Create a partial RSA key pair for the new RSA keyring - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); // Create the new RSA keyring with updated materials description - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("version", "2.0") - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "2.0") + .put("rotated", "yes") + .build() + ) .build(); // Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key // This updates the instruction file without touching the encrypted object - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(bucket) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(bucket) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); // Perform the re-encryption of the instruction file - ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = + originalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); // Verify that the original client can no longer decrypt the object // This proves that the instruction file has been successfully re-encrypted try { - originalClient.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .build()); - throw new RuntimeException("Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!"); + originalClient.getObjectAsBytes(builder -> + builder.bucket(bucket).key(objectKey).build() + ); + throw new RuntimeException( + "Original client should not be able to decrypt the object in S3 post re-encryption of instruction file!" + ); } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); } // Create a new client with the rotated RSA key - S3EncryptionClient newClient = S3EncryptionClient.builder() + S3EncryptionClient newClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); // Verify that the new client can successfully decrypt the object // This proves that the instruction file has been successfully re-encrypted - ResponseBytes decryptedObject = newClient.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .build()); + ResponseBytes decryptedObject = + newClient.getObjectAsBytes(builder -> + builder.bucket(bucket).key(objectKey).build() + ); // Assert that the decrypted object's content matches the original input assertEquals(input, decryptedObject.asUtf8String()); @@ -288,10 +339,15 @@ public static void simpleRsaKeyringReEncryptInstructionFile(final String bucket) * @param bucket The name of the Amazon S3 bucket to perform operations on. * @throws NoSuchAlgorithmException if RSA algorithm is not available */ - public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(final String bucket) throws NoSuchAlgorithmException { + public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix( + final String bucket + ) throws NoSuchAlgorithmException { // Set up the S3 object key and content to be encrypted - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test-with-custom-suffix"); - final String input = "Testing re-encryption of instruction file with RSA Keyring"; + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-test-with-custom-suffix" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring"; // Generate RSA key pair for the original client KeyPair clientRsaKeyPair = generateRsaKeyPair(); @@ -299,18 +355,23 @@ public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(fina PrivateKey clientPrivateKey = clientRsaKeyPair.getPrivate(); // Create a partial RSA key pair for the client's keyring - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); // Create the client's RSA keyring with materials description - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); // Create a default S3 client for instruction file operations @@ -318,19 +379,23 @@ public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(fina // Create the S3 Encryption Client with instruction file support enabled // The client can perform both putObject and getObject operations using RSA keyring - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); // Upload both the encrypted object and instruction file to the specified bucket in S3 - client.putObject(builder -> builder - .bucket(bucket) - .key(objectKey) - .build(), RequestBody.fromString(input)); + client.putObject( + builder -> builder.bucket(bucket).key(objectKey).build(), + RequestBody.fromString(input) + ); // Generate a new RSA key pair for the third party customer KeyPair thirdPartyKeyPair = generateRsaKeyPair(); @@ -338,80 +403,105 @@ public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(fina PrivateKey thirdPartyPrivateKey = thirdPartyKeyPair.getPrivate(); // Create a partial RSA key pair for the third party's decryption keyring - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); // Create RSA keyring with third party's public key and updated materials description for re-encryption request - RsaKeyring sharedKeyring = RsaKeyring.builder() - .wrappingKeyPair(PartialRsaKeyPair.builder() - .publicKey(thirdPartyPublicKey) - .build()) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) + RsaKeyring sharedKeyring = RsaKeyring + .builder() + .wrappingKeyPair( + PartialRsaKeyPair.builder().publicKey(thirdPartyPublicKey).build() + ) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) .build(); // Create RSA keyring with third party's public and private keys for decryption purposes with updated materials description - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) .build(); // Create the re-encryption request that will generate a new instruction file specifically for third party access // This new instruction file will use a custom suffix and contain the data key encrypted with the third party's public key - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(bucket) - .key(objectKey) - .instructionFileSuffix("third-party-access-instruction-file") // Custom instruction file suffix for third party - .newKeyring(sharedKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(bucket) + .key(objectKey) + .instructionFileSuffix("third-party-access-instruction-file") // Custom instruction file suffix for third party + .newKeyring(sharedKeyring) + .build(); // Perform the re-encryption operation to create the new instruction file // This creates a new instruction file without modifying the original encrypted object - ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); // Create the third party's S3 Encryption Client - S3EncryptionClient thirdPartyClient = S3EncryptionClient.builder() + S3EncryptionClient thirdPartyClient = S3EncryptionClient + .builder() .keyring(thirdPartyKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); // Verify that the original client can still decrypt the object in the specified bucket in S3 using the default instruction file - ResponseBytes clientDecryptedObject = client.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .build()); + ResponseBytes clientDecryptedObject = + client.getObjectAsBytes(builder -> + builder.bucket(bucket).key(objectKey).build() + ); // Assert that the decrypted object's content matches the original input assertEquals(input, clientDecryptedObject.asUtf8String()); // Verify that the third party cannot decrypt the object in the specified bucket in S3 using the default instruction file try { - ResponseBytes thirdPartyDecryptObject = thirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .build()); - throw new RuntimeException("Third party client should not be able to decrypt the object in S3 using the default instruction file!"); + ResponseBytes thirdPartyDecryptObject = + thirdPartyClient.getObjectAsBytes(builder -> + builder.bucket(bucket).key(objectKey).build() + ); + throw new RuntimeException( + "Third party client should not be able to decrypt the object in S3 using the default instruction file!" + ); } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); } // Verify that the third party can decrypt the object in the specified bucket in S3 using their custom instruction file // This demonstrates successful secure sharing of encrypted data - ResponseBytes thirdPartyDecryptedObject = thirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(bucket) - .key(objectKey) - .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) - .build()); + ResponseBytes thirdPartyDecryptedObject = + thirdPartyClient.getObjectAsBytes(builder -> + builder + .bucket(bucket) + .key(objectKey) + .overrideConfiguration( + withCustomInstructionFileSuffix( + ".third-party-access-instruction-file" + ) + ) + .build() + ); // Assert that the decrypted object's content matches the original input assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); @@ -419,5 +509,4 @@ public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix(fina // Call deleteObject to delete the object from given S3 Bucket deleteObject(bucket, objectKey, client); } - -} \ No newline at end of file +} diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java index a4b8d1f17..72c824d5d 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 package software.amazon.encryption.s3.internal; +import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; + import software.amazon.encryption.s3.S3EncryptionClientException; import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.RawKeyring; -import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; - /** * Request object for re-encrypting instruction files in S3. * This request supports re-encryption operations using either AES or RSA keyrings. @@ -15,6 +15,7 @@ * For RSA keyrings, both the default and custom instruction file suffixes are supported. */ public class ReEncryptInstructionFileRequest { + private final String bucket; private final String key; private final RawKeyring newKeyring; @@ -60,7 +61,9 @@ public String instructionFileSuffix() { /** * @return whether to enforce rotation for the re-encrypted instruction file */ - public boolean enforceRotation() { return enforceRotation; } + public boolean enforceRotation() { + return enforceRotation; + } /** * Creates a builder that can be used to configure and create a {@link ReEncryptInstructionFileRequest} @@ -75,6 +78,7 @@ public static Builder builder() { * Builder for ReEncryptInstructionFileRequest. */ public static class Builder { + private String bucket; private String key; private RawKeyring newKeyring; @@ -160,12 +164,12 @@ public ReEncryptInstructionFileRequest build() { } if (newKeyring instanceof AesKeyring) { if (!instructionFileSuffix.equals(DEFAULT_INSTRUCTION_FILE_SUFFIX)) { - throw new S3EncryptionClientException("Custom Instruction file suffix is not applicable for AES keyring!"); + throw new S3EncryptionClientException( + "Custom Instruction file suffix is not applicable for AES keyring!" + ); } } return new ReEncryptInstructionFileRequest(this); } - } - } diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java index c2b579670..630abe500 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileResponse.java @@ -7,6 +7,7 @@ * Contains the S3 bucket name, object key, instruction file suffix, and rotation enforcement status for the re-encrypted instruction file */ public class ReEncryptInstructionFileResponse { + private final String bucket; private final String key; private final String instructionFileSuffix; @@ -20,7 +21,12 @@ public class ReEncryptInstructionFileResponse { * @param instructionFileSuffix the suffix used for the instruction file * @param enforceRotation whether rotation was enforced for the re-encrypted instruction file */ - public ReEncryptInstructionFileResponse(String bucket, String key, String instructionFileSuffix, boolean enforceRotation) { + public ReEncryptInstructionFileResponse( + String bucket, + String key, + String instructionFileSuffix, + boolean enforceRotation + ) { this.bucket = bucket; this.key = key; this.instructionFileSuffix = instructionFileSuffix.substring(1); @@ -55,4 +61,3 @@ public String instructionFileSuffix() { return instructionFileSuffix; } } - diff --git a/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java b/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java index ee09b56cf..148b20c49 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java +++ b/src/main/java/software/amazon/encryption/s3/materials/MaterialsDescription.java @@ -14,124 +14,127 @@ * The stored Materials Description is immutable once created. */ public class MaterialsDescription implements Map { - private final Map materialsDescription; - private MaterialsDescription(Builder builder) { - this.materialsDescription = Collections.unmodifiableMap(new HashMap<>(builder.materialsDescription)); - } + private final Map materialsDescription; + + private MaterialsDescription(Builder builder) { + this.materialsDescription = + Collections.unmodifiableMap(new HashMap<>(builder.materialsDescription)); + } + + /** + * @return a new builder instance + */ + public static Builder builder() { + return new Builder(); + } + + /** + * @return the materials description map + */ + public Map getMaterialsDescription() { + return this.materialsDescription; + } + + @Override + public int size() { + return materialsDescription.size(); + } + + @Override + public boolean isEmpty() { + return materialsDescription.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return materialsDescription.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return materialsDescription.containsValue(value); + } + + @Override + public String get(Object key) { + return materialsDescription.get(key); + } + + @Override + public String put(String key, String value) { + throw new UnsupportedOperationException("This map is immutable"); + } + + @Override + public String remove(Object key) { + return materialsDescription.remove(key); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("This map is immutable"); + } + + @Override + public void clear() { + materialsDescription.clear(); + } + + @Override + public Set keySet() { + return materialsDescription.keySet(); + } + + @Override + public Collection values() { + return materialsDescription.values(); + } + + @Override + public Set> entrySet() { + return materialsDescription.entrySet(); + } + + /** + * Builder for MaterialsDescription. + */ + public static class Builder { + + private final Map materialsDescription = new HashMap<>(); /** - * @return a new builder instance + * @param key the key to add + * @param value the value to add + * @return a reference to this object so that method calls can be chained together. + * @throws IllegalArgumentException if key or value is null */ - public static Builder builder() { - return new Builder(); + public Builder put(String key, String value) { + if (key == null || value == null) { + throw new IllegalArgumentException("Key and value must not be null"); + } + materialsDescription.put(key, value); + return this; } /** - * @return the materials description map + * @param description the map of key-value pairs to add + * @return a reference to this object so that method calls can be chained together. + * @throws IllegalArgumentException if description is null */ - public Map getMaterialsDescription() { - return this.materialsDescription; - } - - @Override - public int size() { - return materialsDescription.size(); - } - - @Override - public boolean isEmpty() { - return materialsDescription.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return materialsDescription.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return materialsDescription.containsValue(value); - } - - @Override - public String get(Object key) { - return materialsDescription.get(key); - } - - @Override - public String put(String key, String value) { - throw new UnsupportedOperationException("This map is immutable"); - } - - @Override - public String remove(Object key) { - return materialsDescription.remove(key); - } - - @Override - public void putAll(Map m) { - throw new UnsupportedOperationException("This map is immutable"); - } - - @Override - public void clear() { - materialsDescription.clear(); - } - - @Override - public Set keySet() { - return materialsDescription.keySet(); - } - - @Override - public Collection values() { - return materialsDescription.values(); - } - - @Override - public Set> entrySet() { - return materialsDescription.entrySet(); + public Builder putAll(Map description) { + if (description == null) { + throw new IllegalArgumentException("Description must not be null"); + } + materialsDescription.putAll(description); + return this; } /** - * Builder for MaterialsDescription. + * @return the built MaterialsDescription */ - public static class Builder { - private final Map materialsDescription = new HashMap<>(); - - /** - * @param key the key to add - * @param value the value to add - * @return a reference to this object so that method calls can be chained together. - * @throws IllegalArgumentException if key or value is null - */ - public Builder put(String key, String value) { - if (key == null || value == null) { - throw new IllegalArgumentException("Key and value must not be null"); - } - materialsDescription.put(key, value); - return this; - } - - /** - * @param description the map of key-value pairs to add - * @return a reference to this object so that method calls can be chained together. - * @throws IllegalArgumentException if description is null - */ - public Builder putAll(Map description) { - if (description == null) { - throw new IllegalArgumentException("Description must not be null"); - } - materialsDescription.putAll(description); - return this; - } - - /** - * @return the built MaterialsDescription - */ - public MaterialsDescription build() { - return new MaterialsDescription(this); - } - } -} \ No newline at end of file + public MaterialsDescription build() { + return new MaterialsDescription(this); + } + } +} diff --git a/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java index 1cd82673a..9ad240322 100644 --- a/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java +++ b/src/main/java/software/amazon/encryption/s3/materials/RawKeyring.java @@ -9,6 +9,7 @@ * This is an abstract base class for keyrings that use raw cryptographic keys (AES + RSA) */ public abstract class RawKeyring extends S3Keyring { + protected final MaterialsDescription _materialsDescription; protected RawKeyring(Builder builder) { @@ -23,12 +24,16 @@ protected RawKeyring(Builder builder) { * @param materials the encryption materials to modify * @return modified encryption materials with the keyring's materials description or original encryption materials if no materials description is set */ - public EncryptionMaterials modifyMaterialsForRawKeyring(EncryptionMaterials materials) { + public EncryptionMaterials modifyMaterialsForRawKeyring( + EncryptionMaterials materials + ) { warnIfEncryptionContextIsPresent(materials); if (_materialsDescription != null && !_materialsDescription.isEmpty()) { - materials = materials.toBuilder() - .materialsDescription(_materialsDescription) - .build(); + materials = + materials + .toBuilder() + .materialsDescription(_materialsDescription) + .build(); } return materials; } @@ -44,24 +49,38 @@ public EncryptionMaterials modifyMaterialsForRawKeyring(EncryptionMaterials mate */ public void warnIfEncryptionContextIsPresent(EncryptionMaterials materials) { - materials.s3Request().overrideConfiguration() + materials + .s3Request() + .overrideConfiguration() .flatMap(overrideConfiguration -> - overrideConfiguration.executionAttributes() - .getOptionalAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT)) - .ifPresent(ctx -> LogFactory.getLog(getClass()).warn("Usage of Encryption Context provides no " + - "security benefit in " + getClass().getSimpleName() + ".Additionally, this Encryption Context WILL NOT be " + - "stored in the material description. Provide a MaterialDescription in the Keyring's builder instead.")); + overrideConfiguration + .executionAttributes() + .getOptionalAttribute(S3EncryptionClient.ENCRYPTION_CONTEXT) + ) + .ifPresent(ctx -> + LogFactory + .getLog(getClass()) + .warn( + "Usage of Encryption Context provides no " + + "security benefit in " + + getClass().getSimpleName() + + ".Additionally, this Encryption Context WILL NOT be " + + "stored in the material description. Provide a MaterialDescription in the Keyring's builder instead." + ) + ); } /** * Abstract builder for RawKeyring implementations. * Provides common functionality for setting materials description on raw keyrings. - * + * * @param the type of keyring being built * @param the type of builder */ - public static abstract class Builder> - extends S3Keyring.Builder { + public abstract static class Builder< + KeyringT extends RawKeyring, BuilderT extends Builder + > + extends S3Keyring.Builder { protected MaterialsDescription _materialsDescription; @@ -72,11 +91,13 @@ protected Builder() { /** * Sets the materials description for this keyring. * Materials description provides additional metadata for raw keyrings. - * + * * @param materialsDescription the materials description to associate with this keyring. * @return a reference to this object so that method calls can be chained together. */ - public BuilderT materialsDescription(MaterialsDescription materialsDescription) { + public BuilderT materialsDescription( + MaterialsDescription materialsDescription + ) { _materialsDescription = materialsDescription; return builder(); } diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java index fd181aaef..b94ffd3d1 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientMatDescTest.java @@ -1,5 +1,23 @@ package software.amazon.encryption.s3; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import software.amazon.awssdk.core.ResponseBytes; @@ -15,26 +33,8 @@ import software.amazon.encryption.s3.materials.PartialRsaKeyPair; import software.amazon.encryption.s3.materials.RsaKeyring; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import java.nio.charset.StandardCharsets; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static software.amazon.encryption.s3.S3EncryptionClient.withAdditionalConfiguration; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; - public class S3EncryptionClientMatDescTest { + private static SecretKey AES_KEY; private static KeyPair RSA_KEY_PAIR; @@ -51,172 +51,205 @@ public static void setUp() throws NoSuchAlgorithmException { @Test public void testAesMaterialsDescriptionInObjectMetadata() { - AesKeyring aesKeyring = AesKeyring.builder() + AesKeyring aesKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("version", "1.0").build() + ) .build(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(aesKeyring) .build(); final String input = "Testing Materials Description in Object Metadata!"; - final String objectKey = appendTestSuffix("test-aes-materials-description-in-object-metadata"); + final String objectKey = appendTestSuffix( + "test-aes-materials-description-in-object-metadata" + ); - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input) + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) ); - ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes responseBytes = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, responseBytes.asUtf8String()); JsonNodeParser parser = JsonNodeParser.create(); - JsonNode matDescNode = parser.parse(responseBytes.response().metadata().get("x-amz-matdesc")); + JsonNode matDescNode = parser.parse( + responseBytes.response().metadata().get("x-amz-matdesc") + ); assertEquals("1.0", matDescNode.asObject().get("version").asString()); deleteObject(BUCKET, objectKey, client); - } @Test public void testRsaMaterialsDescriptionInObjectMetadata() { - PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); - RsaKeyring rsaKeyring = RsaKeyring.builder() + PartialRsaKeyPair keyPair = new PartialRsaKeyPair( + RSA_KEY_PAIR.getPrivate(), + RSA_KEY_PAIR.getPublic() + ); + RsaKeyring rsaKeyring = RsaKeyring + .builder() .wrappingKeyPair(keyPair) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .put("admin", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "1.0") + .put("admin", "yes") + .build() + ) .build(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(rsaKeyring) .build(); final String input = "Testing Materials Description in Instruction File!"; - final String objectKey = appendTestSuffix("test-rsa-materials-description-in-instruction-file"); + final String objectKey = appendTestSuffix( + "test-rsa-materials-description-in-instruction-file" + ); - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input) + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) ); - ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes responseBytes = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, responseBytes.asUtf8String()); JsonNodeParser parser = JsonNodeParser.create(); - JsonNode matDescNode = parser.parse(responseBytes.response().metadata().get("x-amz-matdesc")); + JsonNode matDescNode = parser.parse( + responseBytes.response().metadata().get("x-amz-matdesc") + ); assertEquals("1.0", matDescNode.asObject().get("version").asString()); assertEquals("yes", matDescNode.asObject().get("admin").asString()); deleteObject(BUCKET, objectKey, client); - } @Test public void testAesMaterialsDescriptionInInstructionFile() { - AesKeyring aesKeyring = AesKeyring.builder() + AesKeyring aesKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("version", "1.0").build() + ) .build(); - S3Client wrappedClient= S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(aesKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .instructionFileClient(wrappedClient) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .enableInstructionFilePutObject(true) + .instructionFileClient(wrappedClient) + .build() + ) .build(); final String input = "Testing Materials Description in Instruction File!"; - final String objectKey = appendTestSuffix("test-aes-materials-description-in-instruction-file"); + final String objectKey = appendTestSuffix( + "test-aes-materials-description-in-instruction-file" + ); - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input) + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) ); - ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes responseBytes = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, responseBytes.asUtf8String()); - S3Client defaultClient= S3Client.create(); + S3Client defaultClient = S3Client.create(); - ResponseBytes directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes directInstGetResponse = + defaultClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); String instructionFileContent = directInstGetResponse.asUtf8String(); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFileContent); - JsonNode matDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + JsonNode matDescNode = parser.parse( + instructionFileNode.asObject().get("x-amz-matdesc").asString() + ); assertEquals("1.0", matDescNode.asObject().get("version").asString()); deleteObject(BUCKET, objectKey, client); - } @Test public void testRsaMaterialsDescriptionInInstructionFile() { - PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); + PartialRsaKeyPair keyPair = new PartialRsaKeyPair( + RSA_KEY_PAIR.getPrivate(), + RSA_KEY_PAIR.getPublic() + ); - RsaKeyring rsaKeyring = RsaKeyring.builder() + RsaKeyring rsaKeyring = RsaKeyring + .builder() .wrappingKeyPair(keyPair) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .put("admin", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "1.0") + .put("admin", "yes") + .build() + ) .build(); - S3Client wrappedClient= S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(rsaKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .instructionFileClient(wrappedClient) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .enableInstructionFilePutObject(true) + .instructionFileClient(wrappedClient) + .build() + ) .build(); final String input = "Testing Materials Description in Instruction File!"; - final String objectKey = appendTestSuffix("test-rsa-materials-description-in-instruction-file"); + final String objectKey = appendTestSuffix( + "test-rsa-materials-description-in-instruction-file" + ); - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input) + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) ); - ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes responseBytes = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, responseBytes.asUtf8String()); - S3Client defaultClient= S3Client.create(); + S3Client defaultClient = S3Client.create(); - ResponseBytes directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes directInstGetResponse = + defaultClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); String instructionFileContent = directInstGetResponse.asUtf8String(); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFileContent); - JsonNode matDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + JsonNode matDescNode = parser.parse( + instructionFileNode.asObject().get("x-amz-matdesc").asString() + ); assertEquals("1.0", matDescNode.asObject().get("version").asString()); assertEquals("yes", matDescNode.asObject().get("admin").asString()); @@ -225,91 +258,113 @@ public void testRsaMaterialsDescriptionInInstructionFile() { @Test public void testAesKeyringMatDescOverridesPutObjectEncryptionContext() { - AesKeyring aesKeyring = AesKeyring.builder() + AesKeyring aesKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("version", "1.0").build() + ) .build(); - S3Client wrappedClient= S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(aesKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .enableInstructionFilePutObject(true) - .instructionFileClient(wrappedClient) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .enableInstructionFilePutObject(true) + .instructionFileClient(wrappedClient) + .build() + ) .build(); - final String input = "Testing Materials Description in Instruction File and not Encryption Context!"; - final String objectKey = appendTestSuffix("test-aes-materials-description-in-instruction-file-and-not-encryption-context"); + final String input = + "Testing Materials Description in Instruction File and not Encryption Context!"; + final String objectKey = appendTestSuffix( + "test-aes-materials-description-in-instruction-file-and-not-encryption-context" + ); final Map encryptionContext = new HashMap(); encryptionContext.put("admin", "yes"); - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) - .build(), RequestBody.fromString(input) + client.putObject( + builder -> + builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) + .build(), + RequestBody.fromString(input) ); - ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes responseBytes = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, responseBytes.asUtf8String()); - S3Client defaultClient= S3Client.create(); + S3Client defaultClient = S3Client.create(); - ResponseBytes directInstGetResponse = defaultClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes directInstGetResponse = + defaultClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); String instructionFileContent = directInstGetResponse.asUtf8String(); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFileContent); - JsonNode matDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); + JsonNode matDescNode = parser.parse( + instructionFileNode.asObject().get("x-amz-matdesc").asString() + ); assertEquals("1.0", matDescNode.asObject().get("version").asString()); assertNull(matDescNode.asObject().get("admin")); - } @Test public void testRsaKeyringMatDescOverridesPutObjectEncryptionContext() { - PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); - RsaKeyring rsaKeyring = RsaKeyring.builder() + PartialRsaKeyPair keyPair = new PartialRsaKeyPair( + RSA_KEY_PAIR.getPrivate(), + RSA_KEY_PAIR.getPublic() + ); + RsaKeyring rsaKeyring = RsaKeyring + .builder() .wrappingKeyPair(keyPair) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("version", "1.0").build() + ) .build(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(rsaKeyring) .build(); - final String input = "Testing Materials Description in Instruction File and not Encryption Context!"; - final String objectKey = appendTestSuffix("test-rsa-materials-description-in-instruction-file-and-not-encryption-context"); + final String input = + "Testing Materials Description in Instruction File and not Encryption Context!"; + final String objectKey = appendTestSuffix( + "test-rsa-materials-description-in-instruction-file-and-not-encryption-context" + ); final Map encryptionContext = new HashMap(); encryptionContext.put("admin", "yes"); - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) - .build(), RequestBody.fromString(input) + client.putObject( + builder -> + builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration(withAdditionalConfiguration(encryptionContext)) + .build(), + RequestBody.fromString(input) ); - ResponseBytes responseBytes = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes responseBytes = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, responseBytes.asUtf8String()); JsonNodeParser parser = JsonNodeParser.create(); - JsonNode matDescNode = parser.parse(responseBytes.response().metadata().get("x-amz-matdesc")); + JsonNode matDescNode = parser.parse( + responseBytes.response().metadata().get("x-amz-matdesc") + ); assertEquals("1.0", matDescNode.asObject().get("version").asString()); assertNull(matDescNode.asObject().get("admin")); - } - } diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java index 02ab636ce..813ee62ac 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java @@ -2,6 +2,16 @@ // 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; +import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; + import com.amazonaws.AmazonClientException; import com.amazonaws.services.s3.AmazonS3Encryption; import com.amazonaws.services.s3.AmazonS3EncryptionClient; @@ -15,6 +25,15 @@ import com.amazonaws.services.s3.model.EncryptionMaterials; import com.amazonaws.services.s3.model.EncryptionMaterialsProvider; import com.amazonaws.services.s3.model.StaticEncryptionMaterialsProvider; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -33,27 +52,8 @@ import software.amazon.encryption.s3.materials.PartialRsaKeyPair; import software.amazon.encryption.s3.materials.RsaKeyring; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static software.amazon.encryption.s3.S3EncryptionClient.withCustomInstructionFileSuffix; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.BUCKET; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.appendTestSuffix; -import static software.amazon.encryption.s3.utils.S3EncryptionClientTestResources.deleteObject; - public class S3EncryptionClientReEncryptInstructionFileTest { + private static SecretKey AES_KEY; private static SecretKey AES_KEY_TWO; private static KeyPair RSA_KEY_PAIR; @@ -74,49 +74,64 @@ public static void setUp() throws NoSuchAlgorithmException { @Test public void testAesReEncryptInstructionFileFailsWithSameMaterialsDescription() { - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-with-same-materials-description-test"); - final String input = "Testing re-encryption of instruction file with AES Keyring"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - AesKeyring newKeyring = AesKeyring.builder() + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "aes-re-encrypt-instruction-file-with-same-materials-description-test" + ); + final String input = + "Testing re-encryption of instruction file with AES Keyring"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - try { - client.reEncryptInstructionFile(reEncryptInstructionFileRequest); - throw new RuntimeException("Expected failure"); - } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("New keyring must have new materials description!")); - } + try { + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + throw new RuntimeException("Expected failure"); + } catch (S3EncryptionClientException e) { + assertTrue( + e + .getMessage() + .contains("New keyring must have new materials description!") + ); + } deleteObject(BUCKET, objectKey, client); } @@ -126,64 +141,87 @@ public void testRsaReEncryptInstructionFileWithCustomSuffixFailsWithSameMaterial PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-with-custom-suffix-and-same-materials-description-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-with-custom-suffix-and-same-materials-description-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .instructionFileSuffix("third-party-access-instruction-file") - .newKeyring(thirdPartyKeyring) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .instructionFileSuffix("third-party-access-instruction-file") + .newKeyring(thirdPartyKeyring) + .build(); try { client.reEncryptInstructionFile(reEncryptInstructionFileRequest); throw new RuntimeException("Expected failure"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("New keyring must have new materials description!")); + assertTrue( + e + .getMessage() + .contains("New keyring must have new materials description!") + ); } deleteObject(BUCKET, objectKey, client); @@ -191,40 +229,55 @@ public void testRsaReEncryptInstructionFileWithCustomSuffixFailsWithSameMaterial @Test public void testAesReEncryptInstructionFileRejectsCustomInstructionFileSuffix() { - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-with-custom-suffix-test"); + final String objectKey = appendTestSuffix( + "aes-re-encrypt-instruction-file-with-custom-suffix-test" + ); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); try { - ReEncryptInstructionFileRequest.builder() + ReEncryptInstructionFileRequest + .builder() .bucket(BUCKET) .key(objectKey) .newKeyring(newKeyring) .instructionFileSuffix("custom-suffix") .build(); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Custom Instruction file suffix is not applicable for AES keyring!")); + assertTrue( + e + .getMessage() + .contains( + "Custom Instruction file suffix is not applicable for AES keyring!" + ) + ); } deleteObject(BUCKET, objectKey, client); @@ -232,59 +285,86 @@ public void testAesReEncryptInstructionFileRejectsCustomInstructionFileSuffix() @Test public void testAesKeyringReEncryptInstructionFile() { - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-test"); - final String input = "Testing re-encryption of instruction file with AES Keyring"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "aes-re-encrypt-instruction-file-test" + ); + final String input = + "Testing re-encryption of instruction file with AES Keyring"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + ResponseBytes instructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); String instructionFileContent = instructionFile.asUtf8String(); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFileContent); - String originalIv = instructionFileNode.asObject().get("x-amz-iv").asString(); - String originalEncryptedDataKeyAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); - String originalEncryptedDataKey = instructionFileNode.asObject().get("x-amz-key-v2").asString(); - JsonNode originalMatDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); - assertEquals("no", originalMatDescNode.asObject().get("rotated").asString()); - - AesKeyring newKeyring = AesKeyring.builder() + String originalIv = instructionFileNode + .asObject() + .get("x-amz-iv") + .asString(); + String originalEncryptedDataKeyAlgorithm = instructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); + String originalEncryptedDataKey = instructionFileNode + .asObject() + .get("x-amz-key-v2") + .asString(); + JsonNode originalMatDescNode = parser.parse( + instructionFileNode.asObject().get("x-amz-matdesc").asString() + ); + assertEquals( + "no", + originalMatDescNode.asObject().get("rotated").asString() + ); + + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertEquals(BUCKET, response.bucket()); assertEquals(objectKey, response.key()); @@ -293,47 +373,68 @@ public void testAesKeyringReEncryptInstructionFile() { S3Client rotatedWrappedClient = S3Client.create(); - S3EncryptionClient rotatedClient = S3EncryptionClient.builder() + S3EncryptionClient rotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(rotatedWrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(rotatedWrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); try { - client.getObjectAsBytes(GetObjectRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .build()); + client.getObjectAsBytes( + GetObjectRequest.builder().bucket(BUCKET).key(objectKey).build() + ); throw new RuntimeException("Expected exception"); } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("Unable to AES/GCM unwrap")); } - ResponseBytes getResponse = rotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes getResponse = + rotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, getResponse.asUtf8String()); - ResponseBytes reEncryptedInstructionFile = rotatedWrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes reEncryptedInstructionFile = + rotatedWrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); - String newInstructionFileContent = reEncryptedInstructionFile.asUtf8String(); + String newInstructionFileContent = + reEncryptedInstructionFile.asUtf8String(); JsonNode newInstructionFileNode = parser.parse(newInstructionFileContent); - String postReEncryptionIv = newInstructionFileNode.asObject().get("x-amz-iv").asString(); - String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); - String postReEncryptionEncryptedDataKey = newInstructionFileNode.asObject().get("x-amz-key-v2").asString(); - JsonNode postReEncryptionMatDescNode = parser.parse(newInstructionFileNode.asObject().get("x-amz-matdesc").asString()); - - assertEquals("yes", postReEncryptionMatDescNode.asObject().get("rotated").asString()); + String postReEncryptionIv = newInstructionFileNode + .asObject() + .get("x-amz-iv") + .asString(); + String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); + String postReEncryptionEncryptedDataKey = newInstructionFileNode + .asObject() + .get("x-amz-key-v2") + .asString(); + JsonNode postReEncryptionMatDescNode = parser.parse( + newInstructionFileNode.asObject().get("x-amz-matdesc").asString() + ); + + assertEquals( + "yes", + postReEncryptionMatDescNode.asObject().get("rotated").asString() + ); assertEquals(originalIv, postReEncryptionIv); - assertEquals(originalEncryptedDataKeyAlgorithm, postReEncryptionEncryptedDataKeyAlgorithm); + assertEquals( + originalEncryptedDataKeyAlgorithm, + postReEncryptionEncryptedDataKeyAlgorithm + ); assertNotEquals(originalEncryptedDataKey, postReEncryptionEncryptedDataKey); deleteObject(BUCKET, objectKey, client); @@ -344,72 +445,101 @@ public void testRsaKeyringReEncryptInstructionFile() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring oldKeyring = RsaKeyring.builder() + RsaKeyring oldKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + ResponseBytes instructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); String instructionFileContent = instructionFile.asUtf8String(); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFileContent); - String originalIv = instructionFileNode.asObject().get("x-amz-iv").asString(); - String originalEncryptedDataKeyAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); - String originalEncryptedDataKey = instructionFileNode.asObject().get("x-amz-key-v2").asString(); - JsonNode originalMatDescNode = parser.parse(instructionFileNode.asObject().get("x-amz-matdesc").asString()); - assertEquals("no", originalMatDescNode.asObject().get("rotated").asString()); + String originalIv = instructionFileNode + .asObject() + .get("x-amz-iv") + .asString(); + String originalEncryptedDataKeyAlgorithm = instructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); + String originalEncryptedDataKey = instructionFileNode + .asObject() + .get("x-amz-key-v2") + .asString(); + JsonNode originalMatDescNode = parser.parse( + instructionFileNode.asObject().get("x-amz-matdesc").asString() + ); + assertEquals( + "no", + originalMatDescNode.asObject().get("rotated").asString() + ); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertEquals(BUCKET, response.bucket()); assertEquals(objectKey, response.key()); @@ -418,47 +548,68 @@ public void testRsaKeyringReEncryptInstructionFile() { S3Client rotatedWrappedClient = S3Client.create(); - S3EncryptionClient rotatedClient = S3EncryptionClient.builder() + S3EncryptionClient rotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(rotatedWrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(rotatedWrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); try { - client.getObjectAsBytes(GetObjectRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .build()); + client.getObjectAsBytes( + GetObjectRequest.builder().bucket(BUCKET).key(objectKey).build() + ); throw new RuntimeException("Expected exception"); } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); } - ResponseBytes getResponse = rotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes getResponse = + rotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, getResponse.asUtf8String()); - ResponseBytes reEncryptedInstructionFile = rotatedWrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes reEncryptedInstructionFile = + rotatedWrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); - String newInstructionFileContent = reEncryptedInstructionFile.asUtf8String(); + String newInstructionFileContent = + reEncryptedInstructionFile.asUtf8String(); JsonNode newInstructionFileNode = parser.parse(newInstructionFileContent); - String postReEncryptionIv = newInstructionFileNode.asObject().get("x-amz-iv").asString(); - String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); - String postReEncryptionEncryptedDataKey = newInstructionFileNode.asObject().get("x-amz-key-v2").asString(); - JsonNode postReEncryptionMatDescNode = parser.parse(newInstructionFileNode.asObject().get("x-amz-matdesc").asString()); - - assertEquals("yes", postReEncryptionMatDescNode.asObject().get("rotated").asString()); + String postReEncryptionIv = newInstructionFileNode + .asObject() + .get("x-amz-iv") + .asString(); + String postReEncryptionEncryptedDataKeyAlgorithm = newInstructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); + String postReEncryptionEncryptedDataKey = newInstructionFileNode + .asObject() + .get("x-amz-key-v2") + .asString(); + JsonNode postReEncryptionMatDescNode = parser.parse( + newInstructionFileNode.asObject().get("x-amz-matdesc").asString() + ); + + assertEquals( + "yes", + postReEncryptionMatDescNode.asObject().get("rotated").asString() + ); assertEquals(originalIv, postReEncryptionIv); - assertEquals(originalEncryptedDataKeyAlgorithm, postReEncryptionEncryptedDataKeyAlgorithm); + assertEquals( + originalEncryptedDataKeyAlgorithm, + postReEncryptionEncryptedDataKeyAlgorithm + ); assertNotEquals(originalEncryptedDataKey, postReEncryptionEncryptedDataKey); deleteObject(BUCKET, objectKey, client); @@ -469,242 +620,347 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffix() { PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-with-custom-suffix-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-with-custom-suffix-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .instructionFileSuffix("third-party-access-instruction-file") - .newKeyring(thirdPartyKeyring) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .instructionFileSuffix("third-party-access-instruction-file") + .newKeyring(thirdPartyKeyring) + .build(); - S3EncryptionClient thirdPartyClient = S3EncryptionClient.builder() + S3EncryptionClient thirdPartyClient = S3EncryptionClient + .builder() .keyring(thirdPartyKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse reEncryptInstructionFileResponse = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); assertEquals(BUCKET, reEncryptInstructionFileResponse.bucket()); assertEquals(objectKey, reEncryptInstructionFileResponse.key()); - assertEquals("third-party-access-instruction-file", reEncryptInstructionFileResponse.instructionFileSuffix()); + assertEquals( + "third-party-access-instruction-file", + reEncryptInstructionFileResponse.instructionFileSuffix() + ); assertFalse(reEncryptInstructionFileResponse.enforceRotation()); - ResponseBytes clientInstructionFile= wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes clientInstructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); JsonNodeParser parser = JsonNodeParser.create(); String clientInstructionFileContent = clientInstructionFile.asUtf8String(); - JsonNode clientInstructionFileNode = parser.parse(clientInstructionFileContent); - String clientIv = clientInstructionFileNode.asObject().get("x-amz-iv").asString(); - String clientEncryptedDataKeyAlgorithm = clientInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); - String clientEncryptedDataKey = clientInstructionFileNode.asObject().get("x-amz-key-v2").asString(); - JsonNode clientMatDescNode = parser.parse(clientInstructionFileNode.asObject().get("x-amz-matdesc").asString()); + JsonNode clientInstructionFileNode = parser.parse( + clientInstructionFileContent + ); + String clientIv = clientInstructionFileNode + .asObject() + .get("x-amz-iv") + .asString(); + String clientEncryptedDataKeyAlgorithm = clientInstructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); + String clientEncryptedDataKey = clientInstructionFileNode + .asObject() + .get("x-amz-key-v2") + .asString(); + JsonNode clientMatDescNode = parser.parse( + clientInstructionFileNode.asObject().get("x-amz-matdesc").asString() + ); assertEquals("yes", clientMatDescNode.asObject().get("isOwner").asString()); - assertEquals("admin", clientMatDescNode.asObject().get("access-level").asString()); - - ResponseBytes thirdPartyInstFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".third-party-access-instruction-file") - .build()); + assertEquals( + "admin", + clientMatDescNode.asObject().get("access-level").asString() + ); + + ResponseBytes thirdPartyInstFile = + wrappedClient.getObjectAsBytes(builder -> + builder + .bucket(BUCKET) + .key(objectKey + ".third-party-access-instruction-file") + .build() + ); String thirdPartyInstructionFileContent = thirdPartyInstFile.asUtf8String(); - JsonNode thirdPartyInstructionFileNode = parser.parse(thirdPartyInstructionFileContent); - String thirdPartyIv = thirdPartyInstructionFileNode.asObject().get("x-amz-iv").asString(); - String thirdPartyEncryptedDataKeyAlgorithm = thirdPartyInstructionFileNode.asObject().get("x-amz-wrap-alg").asString(); - String thirdPartyEncryptedDataKey = thirdPartyInstructionFileNode.asObject().get("x-amz-key-v2").asString(); - JsonNode thirdPartyMatDescNode = parser.parse(thirdPartyInstructionFileNode.asObject().get("x-amz-matdesc").asString()); - assertEquals("no", thirdPartyMatDescNode.asObject().get("isOwner").asString()); - assertEquals("user", thirdPartyMatDescNode.asObject().get("access-level").asString()); + JsonNode thirdPartyInstructionFileNode = parser.parse( + thirdPartyInstructionFileContent + ); + String thirdPartyIv = thirdPartyInstructionFileNode + .asObject() + .get("x-amz-iv") + .asString(); + String thirdPartyEncryptedDataKeyAlgorithm = thirdPartyInstructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); + String thirdPartyEncryptedDataKey = thirdPartyInstructionFileNode + .asObject() + .get("x-amz-key-v2") + .asString(); + JsonNode thirdPartyMatDescNode = parser.parse( + thirdPartyInstructionFileNode.asObject().get("x-amz-matdesc").asString() + ); + assertEquals( + "no", + thirdPartyMatDescNode.asObject().get("isOwner").asString() + ); + assertEquals( + "user", + thirdPartyMatDescNode.asObject().get("access-level").asString() + ); assertEquals(clientIv, thirdPartyIv); - assertEquals(clientEncryptedDataKeyAlgorithm, thirdPartyEncryptedDataKeyAlgorithm); + assertEquals( + clientEncryptedDataKeyAlgorithm, + thirdPartyEncryptedDataKeyAlgorithm + ); assertNotEquals(clientEncryptedDataKey, thirdPartyEncryptedDataKey); try { - ResponseBytes thirdPartyDecryptObject = thirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes thirdPartyDecryptObject = + thirdPartyClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); throw new RuntimeException("Expected exception"); } catch (S3EncryptionClientException e) { assertTrue(e.getMessage().contains("Unable to RSA-OAEP-SHA1 unwrap")); } - ResponseBytes thirdPartyDecryptedObject = thirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) - .build()); + ResponseBytes thirdPartyDecryptedObject = + thirdPartyClient.getObjectAsBytes(builder -> + builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration( + withCustomInstructionFileSuffix( + ".third-party-access-instruction-file" + ) + ) + .build() + ); assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); - ResponseBytes clientDecryptedObject = client.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes clientDecryptedObject = + client.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, clientDecryptedObject.asUtf8String()); deleteObject(BUCKET, objectKey, client); - } @Test public void testReEncryptInstructionFileV2AesToV3() { - final String input = "Testing re-encryption of instruction file with AES keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-aes-to-v3-re-encrypt-instruction-file-test"); + final String input = + "Testing re-encryption of instruction file with AES keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-aes-to-v3-re-encrypt-instruction-file-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY).addDescription("rotated", "no") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); v2OriginalClient.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider newMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY_TWO) - .addDescription("rotated", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY_TWO).addDescription("rotated", "yes") ); - CryptoConfigurationV2 newCryptoConfig = - new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 newCryptoConfig = new CryptoConfigurationV2( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2RotatedClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2RotatedClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(newCryptoConfig) .withEncryptionMaterialsProvider(newMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3RotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - String v2DecryptObject = v2RotatedClient.getObjectAsString(BUCKET, objectKey); + String v2DecryptObject = v2RotatedClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v2DecryptObject); deleteObject(BUCKET, objectKey, v3RotatedClient); - } @Test - public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() throws IOException{ - final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); + public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() + throws IOException { + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); @@ -714,118 +970,164 @@ public void testReEncryptInstructionFileWithCustomSuffixV2RsaToV3() throws IOExc PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) .build(); - S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient.builder() + S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient + .builder() .keyring(thirdPartyKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider thirdPartyMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) - .addDescription("isOwner", "no") - .addDescription("access-level", "user") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("isOwner", "no") + .addDescription("access-level", "user") ); - CryptoConfigurationV2 thirdPartyCryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 thirdPartyCryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2ThirdPartyRotatedClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2ThirdPartyRotatedClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(thirdPartyCryptoConfig) .withEncryptionMaterialsProvider(thirdPartyMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3OriginalClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3OriginalClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - String v2DecryptObject = v2OriginalClient.getObjectAsString(BUCKET, objectKey); + String v2DecryptObject = v2OriginalClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v2DecryptObject); - ResponseBytes thirdPartyDecryptedObject = v3ThirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) - .build()); + ResponseBytes thirdPartyDecryptedObject = + v3ThirdPartyClient.getObjectAsBytes(builder -> + builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration( + withCustomInstructionFileSuffix( + ".third-party-access-instruction-file" + ) + ) + .build() + ); assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); - EncryptedGetObjectRequest request = new EncryptedGetObjectRequest(BUCKET, objectKey) + EncryptedGetObjectRequest request = new EncryptedGetObjectRequest( + BUCKET, + objectKey + ) .withInstructionFileSuffix("third-party-access-instruction-file"); - String v2ThirdPartyDecryptObject = IOUtils.toString(v2ThirdPartyRotatedClient.getObject(request).getObjectContent(), StandardCharsets.UTF_8); + String v2ThirdPartyDecryptObject = IOUtils.toString( + v2ThirdPartyRotatedClient.getObject(request).getObjectContent(), + StandardCharsets.UTF_8 + ); assertEquals(input, v2ThirdPartyDecryptObject); deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testReEncryptInstructionFileV2RsaToV3() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-rsa-to-v3-re-encrypt-instruction-file-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR).addDescription("rotated", "no") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); @@ -835,204 +1137,269 @@ public void testReEncryptInstructionFileV2RsaToV3() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider newMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) - .addDescription("rotated", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("rotated", "yes") ); - CryptoConfigurationV2 newCryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 newCryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2RotatedClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2RotatedClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(newCryptoConfig) .withEncryptionMaterialsProvider(newMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3RotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - String v2DecryptObject = v2RotatedClient.getObjectAsString(BUCKET, objectKey); + String v2DecryptObject = v2RotatedClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v2DecryptObject); deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testReEncryptInstructionFileUpgradesV1AesToV3() { - final String input = "Testing re-encryption of instruction file, upgrading legacy V1 AES to V3"; - final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-test"); + final String input = + "Testing re-encryption of instruction file, upgrading legacy V1 AES to V3"; + final String objectKey = appendTestSuffix( + "v1-aes-to-v3-re-encrypt-instruction-file-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1Client = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); v1Client.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) .enableLegacyUnauthenticatedModes(true) .enableLegacyWrappingAlgorithms(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider newMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY_TWO) - .addDescription("rotated", "yes") - .addDescription("isLegacy", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") ); - CryptoConfiguration newCryptoConfig = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration newCryptoConfig = new CryptoConfiguration( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(newCryptoConfig) .withEncryptionMaterials(newMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3RotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes instructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); - String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String wrappingAlgorithm = instructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); assertEquals("AES/GCM", wrappingAlgorithm); - String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); + String v1DecryptObject = v1RotatedClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v1DecryptObject); deleteObject(BUCKET, objectKey, v3RotatedClient); - } @Test - public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaToV3() throws IOException { - final String input = "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); + public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaToV3() + throws IOException { + final String input = + "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -1042,124 +1409,171 @@ public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaToV3() thro PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) .build(); - S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient.builder() + S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient + .builder() .keyring(thirdPartyKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider thirdPartyMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) - .addDescription("isOwner", "no") - .addDescription("access-level", "user") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("isOwner", "no") + .addDescription("access-level", "user") ); - CryptoConfiguration thirdPartyCryptoConfig = - new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration thirdPartyCryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1ThirdPartyRotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1ThirdPartyRotatedClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(thirdPartyCryptoConfig) .withEncryptionMaterials(thirdPartyMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3OriginalClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3OriginalClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - String v2DecryptObject = v1OriginalClient.getObjectAsString(BUCKET, objectKey); + String v2DecryptObject = v1OriginalClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v2DecryptObject); - ResponseBytes thirdPartyDecryptedObject = v3ThirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) - .build()); + ResponseBytes thirdPartyDecryptedObject = + v3ThirdPartyClient.getObjectAsBytes(builder -> + builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration( + withCustomInstructionFileSuffix( + ".third-party-access-instruction-file" + ) + ) + .build() + ); assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); - EncryptedGetObjectRequest request = new EncryptedGetObjectRequest(BUCKET, objectKey) + EncryptedGetObjectRequest request = new EncryptedGetObjectRequest( + BUCKET, + objectKey + ) .withInstructionFileSuffix("third-party-access-instruction-file"); - String v1ThirdPartyDecryptObject = IOUtils.toString(v1ThirdPartyRotatedClient.getObject(request).getObjectContent(), StandardCharsets.UTF_8); + String v1ThirdPartyDecryptObject = IOUtils.toString( + v1ThirdPartyRotatedClient.getObject(request).getObjectContent(), + StandardCharsets.UTF_8 + ); assertEquals(input, v1ThirdPartyDecryptObject); deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testReEncryptInstructionFileUpgradesV1RsaToV3() { - final String input = "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-test"); + final String input = + "Testing re-encryption of instruction file, upgrading legacy V1 RSA to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -1169,230 +1583,311 @@ public void testReEncryptInstructionFileUpgradesV1RsaToV3() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider newMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) - .addDescription("rotated", "yes") - .addDescription("isLegacy", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") ); - CryptoConfiguration newCryptoConfig = - new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration newCryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(newCryptoConfig) .withEncryptionMaterials(newMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3RotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); + String v1DecryptObject = v1RotatedClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v1DecryptObject); - ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes instructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); - String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String wrappingAlgorithm = instructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); assertEquals("RSA-OAEP-SHA1", wrappingAlgorithm); - deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testReEncryptInstructionFileUpgradesV1AesEncryptionOnlyToV3() { - final String input = "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only AES to V3"; - final String objectKey = appendTestSuffix("v1-aes-encryption-only-to-v3-re-encrypt-instruction-file-test"); + final String input = + "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only AES to V3"; + final String objectKey = appendTestSuffix( + "v1-aes-encryption-only-to-v3-re-encrypt-instruction-file-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); v1OriginalClient.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) .enableLegacyUnauthenticatedModes(true) .enableLegacyWrappingAlgorithms(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider newMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY_TWO) - .addDescription("rotated", "yes") - .addDescription("isLegacy", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") ); - CryptoConfiguration newCryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration newCryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(newCryptoConfig) .withEncryptionMaterials(newMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3RotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes instructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); - String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String wrappingAlgorithm = instructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); assertEquals("AES/GCM", wrappingAlgorithm); try { - String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); - throw new RuntimeException("V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to AES/GCM algorithm upgrade"); + String v1DecryptObject = v1RotatedClient.getObjectAsString( + BUCKET, + objectKey + ); + throw new RuntimeException( + "V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to AES/GCM algorithm upgrade" + ); } catch (AmazonClientException e) { - assertTrue(e.getMessage().contains("An exception was thrown when attempting to decrypt the Content Encryption Key")); + assertTrue( + e + .getMessage() + .contains( + "An exception was thrown when attempting to decrypt the Content Encryption Key" + ) + ); } deleteObject(BUCKET, objectKey, v3RotatedClient); - } + @Test - public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaEncryptionOnlyToV3() throws IOException { - final String input = "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only RSA to V3"; - final String objectKey = appendTestSuffix("v1-rsa-encryption-only-to-v3-re-encrypt-instruction-file-with-custom-suffix-test"); + public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaEncryptionOnlyToV3() + throws IOException { + final String input = + "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only RSA to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-encryption-only-to-v3-re-encrypt-instruction-file-with-custom-suffix-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -1402,127 +1897,184 @@ public void testReEncryptInstructionFileWithCustomSuffixUpgradesV1RsaEncryptionO PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) .build(); - S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient.builder() + S3EncryptionClient v3ThirdPartyClient = S3EncryptionClient + .builder() .keyring(thirdPartyKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider thirdPartyMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) - .addDescription("isOwner", "no") - .addDescription("access-level", "user") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("isOwner", "no") + .addDescription("access-level", "user") ); - CryptoConfiguration thirdPartyCryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration thirdPartyCryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1ThirdPartyRotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1ThirdPartyRotatedClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(thirdPartyCryptoConfig) .withEncryptionMaterials(thirdPartyMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3OriginalClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3OriginalClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); - String v1DecryptObject = v1OriginalClient.getObjectAsString(BUCKET, objectKey); + String v1DecryptObject = v1OriginalClient.getObjectAsString( + BUCKET, + objectKey + ); assertEquals(input, v1DecryptObject); - ResponseBytes thirdPartyDecryptedObject = v3ThirdPartyClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .overrideConfiguration(withCustomInstructionFileSuffix(".third-party-access-instruction-file")) - .build()); + ResponseBytes thirdPartyDecryptedObject = + v3ThirdPartyClient.getObjectAsBytes(builder -> + builder + .bucket(BUCKET) + .key(objectKey) + .overrideConfiguration( + withCustomInstructionFileSuffix( + ".third-party-access-instruction-file" + ) + ) + .build() + ); assertEquals(input, thirdPartyDecryptedObject.asUtf8String()); - EncryptedGetObjectRequest request = new EncryptedGetObjectRequest(BUCKET, objectKey) + EncryptedGetObjectRequest request = new EncryptedGetObjectRequest( + BUCKET, + objectKey + ) .withInstructionFileSuffix("third-party-access-instruction-file"); try { - String v1ThirdPartyDecryptObject = IOUtils.toString(v1ThirdPartyRotatedClient.getObject(request).getObjectContent(), StandardCharsets.UTF_8); - throw new RuntimeException("V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to RSA algorithm upgrade"); + String v1ThirdPartyDecryptObject = IOUtils.toString( + v1ThirdPartyRotatedClient.getObject(request).getObjectContent(), + StandardCharsets.UTF_8 + ); + throw new RuntimeException( + "V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to RSA algorithm upgrade" + ); } catch (SecurityException e) { - assertTrue(e.getMessage().contains("The content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted.")); + assertTrue( + e + .getMessage() + .contains( + "The content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted." + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test - public void testReEncryptInstructionFileUpgradesV1RsaEncryptionOnlyToV3() throws IOException { - final String input = "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only RSA to V3"; - final String objectKey = appendTestSuffix("v1-rsa-encryption-only-to-v3-re-encrypt-instruction-file-test"); + public void testReEncryptInstructionFileUpgradesV1RsaEncryptionOnlyToV3() + throws IOException { + final String input = + "Testing re-encryption of instruction file, upgrading legacy V1 Encryption Only RSA to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-encryption-only-to-v3-re-encrypt-instruction-file-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -1532,102 +2084,143 @@ public void testReEncryptInstructionFileUpgradesV1RsaEncryptionOnlyToV3() throws PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); EncryptionMaterialsProvider newMaterialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR_TWO) - .addDescription("rotated", "yes") - .addDescription("isLegacy", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR_TWO) + .addDescription("rotated", "yes") + .addDescription("isLegacy", "no") ); - CryptoConfiguration newCryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + CryptoConfiguration newCryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1RotatedClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(newCryptoConfig) .withEncryptionMaterials(newMaterialsProvider) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); - ResponseBytes v3DecryptObject = v3RotatedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build()); + ResponseBytes v3DecryptObject = + v3RotatedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey).build() + ); assertEquals(input, v3DecryptObject.asUtf8String()); try { - String v1DecryptObject = v1RotatedClient.getObjectAsString(BUCKET, objectKey); - throw new RuntimeException("V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to RSA algorithm upgrade"); + String v1DecryptObject = v1RotatedClient.getObjectAsString( + BUCKET, + objectKey + ); + throw new RuntimeException( + "V1 client with EncryptionOnly cannot decrypt content after V3 re-encryption due to RSA algorithm upgrade" + ); } catch (SecurityException e) { - assertTrue(e.getMessage().contains("The content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted.")); + assertTrue( + e + .getMessage() + .contains( + "The content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted." + ) + ); } - ResponseBytes instructionFile = wrappedClient.getObjectAsBytes(builder -> builder - .bucket(BUCKET) - .key(objectKey + ".instruction") - .build()); + ResponseBytes instructionFile = + wrappedClient.getObjectAsBytes(builder -> + builder.bucket(BUCKET).key(objectKey + ".instruction").build() + ); JsonNodeParser parser = JsonNodeParser.create(); JsonNode instructionFileNode = parser.parse(instructionFile.asUtf8String()); - String wrappingAlgorithm = instructionFileNode.asObject().get("x-amz-wrap-alg").asString(); + String wrappingAlgorithm = instructionFileNode + .asObject() + .get("x-amz-wrap-alg") + .asString(); assertEquals("RSA-OAEP-SHA1", wrappingAlgorithm); deleteObject(BUCKET, objectKey, v3OriginalClient); @@ -1635,46 +2228,58 @@ public void testReEncryptInstructionFileUpgradesV1RsaEncryptionOnlyToV3() throws @Test public void testAesKeyringReEncryptInstructionFileEnforceRotation() { - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-enforce-rotation-test"); - final String input = "Testing re-encryption of instruction file with AES Keyring and enforce rotation enabled"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - AesKeyring newKeyring = AesKeyring.builder() + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "aes-re-encrypt-instruction-file-enforce-rotation-test" + ); + final String input = + "Testing re-encryption of instruction file with AES Keyring and enforce rotation enabled"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -1685,49 +2290,67 @@ public void testAesKeyringReEncryptInstructionFileEnforceRotation() { @Test public void testAesKeyringReEncryptInstructionFileEnforceRotationWithSameKey() { - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("aes-re-encrypt-instruction-file-enforce-rotation-with-same-key-test"); - final String input = "Testing re-encryption of instruction file with AES keyring and enforce rotation enabled"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - AesKeyring newKeyring = AesKeyring.builder() + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "aes-re-encrypt-instruction-file-enforce-rotation-with-same-key-test" + ); + final String input = + "Testing re-encryption of instruction file with AES keyring and enforce rotation enabled"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, client); @@ -1738,59 +2361,73 @@ public void testRsaKeyringReEncryptInstructionFileEnforceRotation() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring oldKeyring = RsaKeyring.builder() + RsaKeyring oldKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-enforce-rotation-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -1804,54 +2441,73 @@ public void testRsaKeyringReEncryptInstructionFileEnforceRotationWithSameKey() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring oldKeyring = RsaKeyring.builder() + RsaKeyring oldKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-with-same-key-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - RsaKeyring newKeyring = RsaKeyring.builder() + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-enforce-rotation-with-same-key-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, client); @@ -1862,62 +2518,82 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotatio PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-with-custom-suffix-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-enforce-rotation-with-custom-suffix-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .enforceRotation(true) - .instructionFileSuffix("third-party-access-instruction-file") - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .enforceRotation(true) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); try { - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -1927,61 +2603,86 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotatio } @Test - public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotationWithSameKey(){ + public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotationWithSameKey() { PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient client = S3EncryptionClient.builder() + S3EncryptionClient client = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - final String objectKey = appendTestSuffix("rsa-re-encrypt-instruction-file-enforce-rotation-with-custom-suffix-and-same-key-test"); - final String input = "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; - - client.putObject(builder -> builder - .bucket(BUCKET) - .key(objectKey) - .build(), RequestBody.fromString(input)); - - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file-enforce-rotation-with-custom-suffix-and-same-key-test" + ); + final String input = + "Testing re-encryption of instruction file with RSA Keyring and enforce rotation enabled"; + + client.putObject( + builder -> builder.bucket(BUCKET).key(objectKey).build(), + RequestBody.fromString(input) + ); + + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .enforceRotation(true) - .instructionFileSuffix("third-party-access-instruction-file") - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .enforceRotation(true) + .instructionFileSuffix("third-party-access-instruction-file") + .build(); try { - ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, client); @@ -1989,70 +2690,98 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixEnforceRotatio @Test public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotation() { - final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-test"); - final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + final String objectKey = appendTestSuffix( + "v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-test" + ); + final String input = + "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.AuthenticatedEncryption + ) .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1Client = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); v1Client.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) .enableLegacyUnauthenticatedModes(true) .enableLegacyWrappingAlgorithms(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -2063,73 +2792,107 @@ public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotation() @Test public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationWithSameKey() { - final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-test"); - final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + final String objectKey = appendTestSuffix( + "v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-test" + ); + final String input = + "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1Client = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); v1Client.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) .enableLegacyUnauthenticatedModes(true) .enableLegacyWrappingAlgorithms(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3RotatedClient); @@ -2137,68 +2900,94 @@ public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationWi @Test public void testAesKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameKey() { - final String objectKey = appendTestSuffix("v2-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-test"); - final String input = "Testing re-encryption of instruction file from V2 to V3 with AES Keyring and enforce rotation enabled"; + final String objectKey = appendTestSuffix( + "v2-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-test" + ); + final String input = + "Testing re-encryption of instruction file from V2 to V3 with AES Keyring and enforce rotation enabled"; EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY).addDescription("rotated", "no") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); v2OriginalClient.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3RotatedClient); @@ -2206,65 +2995,85 @@ public void testAesKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameK @Test public void testAesKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { - final String objectKey = appendTestSuffix("v2-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-test"); - final String input = "Testing re-encryption of instruction file from V2 to V3 with AES Keyring and enforce rotation enabled"; + final String objectKey = appendTestSuffix( + "v2-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-test" + ); + final String input = + "Testing re-encryption of instruction file from V2 to V3 with AES Keyring and enforce rotation enabled"; EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY).addDescription("rotated", "no") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); v2OriginalClient.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -2275,19 +3084,25 @@ public void testAesKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { @Test public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV2ToV3EnforceRotation() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); @@ -2297,78 +3112,102 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV2ToV3EnforceR PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); } deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV2ToV3EnforceRotationWithSameKey() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); @@ -2378,69 +3217,97 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV2ToV3EnforceR PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR).addDescription("rotated", "no") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); @@ -2450,51 +3317,64 @@ public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -2505,18 +3385,23 @@ public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotation() { @Test public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameKey() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; - final String objectKey = appendTestSuffix("v2-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V2 to V3"; + final String objectKey = appendTestSuffix( + "v2-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR).addDescription("rotated", "no") ); - CryptoConfigurationV2 cryptoConfig = - new CryptoConfigurationV2(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfigurationV2 cryptoConfig = new CryptoConfigurationV2( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2.encryptionBuilder() + AmazonS3EncryptionV2 v2OriginalClient = AmazonS3EncryptionClientV2 + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterialsProvider(materialsProvider) .build(); @@ -2526,46 +3411,64 @@ public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameK PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .build()) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) .build(); - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); @@ -2573,19 +3476,25 @@ public void testRsaKeyringReEncryptInstructionFileV2ToV3EnforceRotationWithSameK @Test public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotation() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -2595,57 +3504,76 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotation() { PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -2656,19 +3584,25 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotation() { @Test public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameKey() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -2678,52 +3612,76 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameK PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); @@ -2731,19 +3689,25 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameK @Test public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotation() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.AuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.AuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -2753,59 +3717,77 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceR PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -2816,19 +3798,25 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceR @Test public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotationWithSameKey() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.StrictAuthenticatedEncryption) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.StrictAuthenticatedEncryption + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -2838,74 +3826,103 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceR PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotationEncryptionOnly() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-encryption-only-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-encryption-only-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -2915,61 +3932,80 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceR PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey thirdPartyPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey thirdPartyPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair thirdPartyPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(thirdPartyPublicKey) .privateKey(thirdPartyPrivateKey) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(thirdPartyPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { - fail("Enforce rotation should not throw exception"); + fail("Enforce rotation should not throw exception"); } deleteObject(BUCKET, objectKey, v3OriginalClient); @@ -2977,19 +4013,25 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceR @Test public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceRotationWithSameKeyEncryptionOnly() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-encryption-only-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-with-custom-suffix-enforce-rotation-same-key-encryption-only-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("isOwner", "yes") - .addDescription("access-level", "admin") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("isOwner", "yes") + .addDescription("access-level", "admin") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -2999,74 +4041,103 @@ public void testRsaKeyringReEncryptInstructionFileWithCustomSuffixV1ToV3EnforceR PublicKey clientPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey clientPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair clientPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(clientPublicKey) .privateKey(clientPrivateKey) .build(); - RsaKeyring clientKeyring = RsaKeyring.builder() + RsaKeyring clientKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(clientKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - RsaKeyring thirdPartyKeyring = RsaKeyring.builder() + RsaKeyring thirdPartyKeyring = RsaKeyring + .builder() .wrappingKeyPair(clientPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("isOwner", "no") - .put("access-level", "user") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(thirdPartyKeyring) - .instructionFileSuffix("third-party-access-instruction-file") - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(thirdPartyKeyring) + .instructionFileSuffix("third-party-access-instruction-file") + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); - } @Test public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationEncryptionOnly() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-encryption-only-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-encryption-only-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -3076,57 +4147,76 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationEncryptio PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); - PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(newPublicKey) .privateKey(newPrivateKey) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(newPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -3137,19 +4227,25 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationEncryptio @Test public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameKeyEncryptionOnly() { - final String input = "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; - final String objectKey = appendTestSuffix("v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-encryption-only-test"); + final String input = + "Testing re-encryption of instruction file with RSA keyrings from V1 to V3"; + final String objectKey = appendTestSuffix( + "v1-rsa-to-v3-re-encrypt-instruction-file-enforce-rotation-same-key-encryption-only-test" + ); EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(RSA_KEY_PAIR) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(RSA_KEY_PAIR) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = - new CryptoConfiguration(CryptoMode.EncryptionOnly) - .withStorageMode(CryptoStorageMode.InstructionFile); + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) + .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1OriginalClient = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); @@ -3159,52 +4255,76 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameK PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); - PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair.builder() + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() .publicKey(originalPublicKey) .privateKey(originalPrivateKey) .build(); - RsaKeyring originalKeyring = RsaKeyring.builder() + RsaKeyring originalKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(originalKeyring) .enableLegacyWrappingAlgorithms(true) .enableLegacyUnauthenticatedModes(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - RsaKeyring newKeyring = RsaKeyring.builder() + RsaKeyring newKeyring = RsaKeyring + .builder() .wrappingKeyPair(originalPartialRsaKeyPair) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3OriginalClient); @@ -3212,70 +4332,98 @@ public void testRsaKeyringReEncryptInstructionFileV1ToV3EnforceRotationWithSameK @Test public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationEncryptionOnly() { - final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-encryption-only-test"); - final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + final String objectKey = appendTestSuffix( + "v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-encryption-only-test" + ); + final String input = + "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1Client = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); v1Client.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) .enableLegacyUnauthenticatedModes(true) .enableLegacyWrappingAlgorithms(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY_TWO) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); assertTrue(response.enforceRotation()); } catch (S3EncryptionClientException e) { fail("Enforce rotation should not throw exception"); @@ -3286,76 +4434,109 @@ public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationEn @Test public void testAesKeyringReEncryptInstructionFileV1ToV3UpgradeEnforceRotationWithSameKeyEncryptionOnly() { - final String objectKey = appendTestSuffix("v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-encryption-only-test"); - final String input = "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; + final String objectKey = appendTestSuffix( + "v1-aes-to-v3-re-encrypt-instruction-file-with-enforce-rotation-same-key-encryption-only-test" + ); + final String input = + "Testing re-encryption of instruction file from V1 to V3 with AES Keyring and enforce rotation enabled"; EncryptionMaterialsProvider materialsProvider = - new StaticEncryptionMaterialsProvider(new EncryptionMaterials(AES_KEY) - .addDescription("rotated", "no") - .addDescription("isLegacy", "yes") + new StaticEncryptionMaterialsProvider( + new EncryptionMaterials(AES_KEY) + .addDescription("rotated", "no") + .addDescription("isLegacy", "yes") ); - CryptoConfiguration cryptoConfig = new CryptoConfiguration(CryptoMode.EncryptionOnly) + CryptoConfiguration cryptoConfig = new CryptoConfiguration( + CryptoMode.EncryptionOnly + ) .withStorageMode(CryptoStorageMode.InstructionFile); - AmazonS3Encryption v1Client = AmazonS3EncryptionClient.encryptionBuilder() + AmazonS3Encryption v1Client = AmazonS3EncryptionClient + .encryptionBuilder() .withCryptoConfiguration(cryptoConfig) .withEncryptionMaterials(materialsProvider) .build(); v1Client.putObject(BUCKET, objectKey, input); - AesKeyring oldKeyring = AesKeyring.builder() + AesKeyring oldKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) .enableLegacyWrappingAlgorithms(true) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "no") - .put("isLegacy", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "no") + .put("isLegacy", "yes") + .build() + ) .build(); S3Client wrappedClient = S3Client.create(); - S3EncryptionClient v3OriginalClient = S3EncryptionClient.builder() + S3EncryptionClient v3OriginalClient = S3EncryptionClient + .builder() .keyring(oldKeyring) .enableLegacyUnauthenticatedModes(true) .enableLegacyWrappingAlgorithms(true) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) .build(); - AesKeyring newKeyring = AesKeyring.builder() + AesKeyring newKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("rotated", "yes") - .put("isLegacy", "no") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("rotated", "yes") + .put("isLegacy", "no") + .build() + ) .build(); - S3EncryptionClient v3RotatedClient = S3EncryptionClient.builder() + S3EncryptionClient v3RotatedClient = S3EncryptionClient + .builder() .keyring(newKeyring) - .instructionFileConfig(InstructionFileConfig.builder() - .instructionFileClient(wrappedClient) - .enableInstructionFilePutObject(true) - .build()) - .build(); - - ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = ReEncryptInstructionFileRequest.builder() - .bucket(BUCKET) - .key(objectKey) - .newKeyring(newKeyring) - .enforceRotation(true) - .build(); + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .enableInstructionFilePutObject(true) + .build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .enforceRotation(true) + .build(); try { - ReEncryptInstructionFileResponse response = v3OriginalClient.reEncryptInstructionFile(reEncryptInstructionFileRequest); + ReEncryptInstructionFileResponse response = + v3OriginalClient.reEncryptInstructionFile( + reEncryptInstructionFileRequest + ); fail("Enforce rotation should throw exception"); } catch (S3EncryptionClientException e) { - assertTrue(e.getMessage().contains("Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key")); + assertTrue( + e + .getMessage() + .contains( + "Re-encryption failed due to enforced rotation! Old keyring is still able to decrypt the newly encrypted data key" + ) + ); } deleteObject(BUCKET, objectKey, v3RotatedClient); } - } diff --git a/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java b/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java index 29cd1ac21..6a5230718 100644 --- a/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java +++ b/src/test/java/software/amazon/encryption/s3/materials/MaterialsDescriptionTest.java @@ -1,23 +1,23 @@ package software.amazon.encryption.s3.materials; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class MaterialsDescriptionTest { + private static SecretKey AES_KEY; private static KeyPair RSA_KEY_PAIR; @@ -34,10 +34,14 @@ public static void setUp() throws NoSuchAlgorithmException { @Test public void testSimpleMaterialsDescription() { - MaterialsDescription materialsDescription = MaterialsDescription.builder() + MaterialsDescription materialsDescription = MaterialsDescription + .builder() .put("version", "1.0") .build(); - assertEquals("1.0", materialsDescription.getMaterialsDescription().get("version")); + assertEquals( + "1.0", + materialsDescription.getMaterialsDescription().get("version") + ); assertEquals(1, materialsDescription.getMaterialsDescription().size()); try { materialsDescription.getMaterialsDescription().put("version", "2.0"); @@ -58,40 +62,60 @@ public void testMaterialsDescriptionPutAll() { Map description = new HashMap<>(); description.put("version", "1.0"); description.put("next-version", "2.0"); - MaterialsDescription materialsDescription = MaterialsDescription.builder() + MaterialsDescription materialsDescription = MaterialsDescription + .builder() .putAll(description) .build(); assertEquals(2, materialsDescription.getMaterialsDescription().size()); - assertTrue(materialsDescription.getMaterialsDescription().containsKey("version")); - assertTrue(materialsDescription.getMaterialsDescription().containsKey("next-version")); - assertEquals("1.0", materialsDescription.getMaterialsDescription().get("version")); - assertEquals("2.0", materialsDescription.getMaterialsDescription().get("next-version")); + assertTrue( + materialsDescription.getMaterialsDescription().containsKey("version") + ); + assertTrue( + materialsDescription.getMaterialsDescription().containsKey("next-version") + ); + assertEquals( + "1.0", + materialsDescription.getMaterialsDescription().get("version") + ); + assertEquals( + "2.0", + materialsDescription.getMaterialsDescription().get("next-version") + ); } @Test public void testMaterialsDescriptionAesKeyring() { - AesKeyring aesKeyring = AesKeyring.builder() + AesKeyring aesKeyring = AesKeyring + .builder() .wrappingKey(AES_KEY) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .put("admin", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "1.0") + .put("admin", "yes") + .build() + ) .build(); assertNotNull(aesKeyring); } @Test public void testMaterialsDescriptionRsaKeyring() { - PartialRsaKeyPair keyPair = new PartialRsaKeyPair(RSA_KEY_PAIR.getPrivate(), RSA_KEY_PAIR.getPublic()); - RsaKeyring rsaKeyring = RsaKeyring.builder() + PartialRsaKeyPair keyPair = new PartialRsaKeyPair( + RSA_KEY_PAIR.getPrivate(), + RSA_KEY_PAIR.getPublic() + ); + RsaKeyring rsaKeyring = RsaKeyring + .builder() .wrappingKeyPair(keyPair) - .materialsDescription(MaterialsDescription.builder() - .put("version", "1.0") - .put("admin", "yes") - .build()) + .materialsDescription( + MaterialsDescription + .builder() + .put("version", "1.0") + .put("admin", "yes") + .build() + ) .build(); assertNotNull(rsaKeyring); - } - -} \ No newline at end of file +} From b50ee55db61c44f4ae1e1cfb86d99d6ca5ce2ffb Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Mon, 21 Jul 2025 15:50:12 -0700 Subject: [PATCH 05/10] added fixes based on PR --- .../encryption/s3/S3EncryptionClient.java | 4 + .../s3/internal/InstructionFileConfig.java | 2 +- .../ReEncryptInstructionFileRequest.java | 5 +- ...ionClientReEncryptInstructionFileTest.java | 75 +++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index d65a75696..63e9cb890 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -222,6 +222,10 @@ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstru .key(reEncryptInstructionFileRequest.key()) .build(); + if (!_instructionFileConfig.isInstructionFilePutEnabled()) { + throw new S3EncryptionClientException("Instruction file put operations must be enabled to re-encrypt instruction files"); + } + ResponseInputStream response = this.getObject(request); ContentMetadataDecodingStrategy decodingStrategy = new ContentMetadataDecodingStrategy(_instructionFileConfig); ContentMetadata contentMetadata = decodingStrategy.decode(request, response.response()); diff --git a/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java b/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java index d9eed1cf0..fe89287fc 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java +++ b/src/main/java/software/amazon/encryption/s3/internal/InstructionFileConfig.java @@ -45,7 +45,7 @@ public enum InstructionFileClientType { ASYNC } - boolean isInstructionFilePutEnabled() { + public boolean isInstructionFilePutEnabled() { return _enableInstructionFilePut; } diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java index 72c824d5d..539f2e799 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java @@ -7,6 +7,7 @@ import software.amazon.encryption.s3.S3EncryptionClientException; import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.RawKeyring; +import software.amazon.encryption.s3.materials.RsaKeyring; /** * Request object for re-encrypting instruction files in S3. @@ -162,10 +163,10 @@ public ReEncryptInstructionFileRequest build() { if (newKeyring == null) { throw new S3EncryptionClientException("New keyring must be provided!"); } - if (newKeyring instanceof AesKeyring) { + if (!(newKeyring instanceof RsaKeyring)) { if (!instructionFileSuffix.equals(DEFAULT_INSTRUCTION_FILE_SUFFIX)) { throw new S3EncryptionClientException( - "Custom Instruction file suffix is not applicable for AES keyring!" + "Custom Instruction file suffix is only applicable for RSA keyring!" ); } } diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java index 813ee62ac..ab000d03d 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java @@ -283,6 +283,81 @@ public void testAesReEncryptInstructionFileRejectsCustomInstructionFileSuffix() deleteObject(BUCKET, objectKey, client); } + @Test + public void testReEncryptInstructionFileFailsWhenInstructionFilePutNotEnabled() { + PublicKey originalPublicKey = RSA_KEY_PAIR.getPublic(); + PrivateKey originalPrivateKey = RSA_KEY_PAIR.getPrivate(); + + PartialRsaKeyPair originalPartialRsaKeyPair = PartialRsaKeyPair + .builder() + .publicKey(originalPublicKey) + .privateKey(originalPrivateKey) + .build(); + + RsaKeyring oldKeyring = RsaKeyring + .builder() + .wrappingKeyPair(originalPartialRsaKeyPair) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "no").build() + ) + .build(); + + S3Client wrappedClient = S3Client.create(); + S3EncryptionClient client = S3EncryptionClient + .builder() + .keyring(oldKeyring) + .instructionFileConfig( + InstructionFileConfig + .builder() + .instructionFileClient(wrappedClient) + .build() + ) + .build(); + + final String objectKey = appendTestSuffix( + "rsa-re-encrypt-instruction-file" + ); + + PublicKey newPublicKey = RSA_KEY_PAIR_TWO.getPublic(); + PrivateKey newPrivateKey = RSA_KEY_PAIR_TWO.getPrivate(); + + PartialRsaKeyPair newPartialRsaKeyPair = PartialRsaKeyPair + .builder() + .publicKey(newPublicKey) + .privateKey(newPrivateKey) + .build(); + + RsaKeyring newKeyring = RsaKeyring + .builder() + .wrappingKeyPair(newPartialRsaKeyPair) + .materialsDescription( + MaterialsDescription.builder().put("rotated", "yes").build() + ) + .build(); + + ReEncryptInstructionFileRequest reEncryptInstructionFileRequest = + ReEncryptInstructionFileRequest + .builder() + .bucket(BUCKET) + .key(objectKey) + .newKeyring(newKeyring) + .build(); + + try { + ReEncryptInstructionFileResponse response = + client.reEncryptInstructionFile(reEncryptInstructionFileRequest); + } catch (S3EncryptionClientException e) { + System.out.println(e.getMessage()); + assertTrue( + e + .getMessage() + .contains( + "Instruction file put operations must be enabled to re-encrypt instruction files" + ) + ); + } + } + @Test public void testAesKeyringReEncryptInstructionFile() { AesKeyring oldKeyring = AesKeyring From a0bf49b9375390a078c7e0df38c30c1f2a83ca68 Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Mon, 21 Jul 2025 15:55:43 -0700 Subject: [PATCH 06/10] moved the validation check for instruction file put operations to the top --- .../software/amazon/encryption/s3/S3EncryptionClient.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java index 63e9cb890..8b44bcda3 100644 --- a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java +++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java @@ -216,16 +216,16 @@ public static Consumer withAdditionalCo * @throws S3EncryptionClientException if the new keyring has the same materials description as the current one */ public ReEncryptInstructionFileResponse reEncryptInstructionFile(ReEncryptInstructionFileRequest reEncryptInstructionFileRequest) { + if (!_instructionFileConfig.isInstructionFilePutEnabled()) { + throw new S3EncryptionClientException("Instruction file put operations must be enabled to re-encrypt instruction files"); + } + //Build request to retrieve the encrypted object and its associated instruction file final GetObjectRequest request = GetObjectRequest.builder() .bucket(reEncryptInstructionFileRequest.bucket()) .key(reEncryptInstructionFileRequest.key()) .build(); - if (!_instructionFileConfig.isInstructionFilePutEnabled()) { - throw new S3EncryptionClientException("Instruction file put operations must be enabled to re-encrypt instruction files"); - } - ResponseInputStream response = this.getObject(request); ContentMetadataDecodingStrategy decodingStrategy = new ContentMetadataDecodingStrategy(_instructionFileConfig); ContentMetadata contentMetadata = decodingStrategy.decode(request, response.response()); From 4a17e3b4e809afc0f3412906e9075f8d5393090f Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Mon, 21 Jul 2025 15:57:25 -0700 Subject: [PATCH 07/10] Removed debugger System.out.println() --- .../s3/S3EncryptionClientReEncryptInstructionFileTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java index ab000d03d..9ec7ae7d6 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java @@ -347,7 +347,6 @@ public void testReEncryptInstructionFileFailsWhenInstructionFilePutNotEnabled() ReEncryptInstructionFileResponse response = client.reEncryptInstructionFile(reEncryptInstructionFileRequest); } catch (S3EncryptionClientException e) { - System.out.println(e.getMessage()); assertTrue( e .getMessage() From 1a4359d5720f106cabc511aaa41924da2f67624f Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Mon, 21 Jul 2025 16:08:58 -0700 Subject: [PATCH 08/10] fixed error message for one of the test cases to match expected one --- .../s3/S3EncryptionClientReEncryptInstructionFileTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java index 9ec7ae7d6..dbf07c654 100644 --- a/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java +++ b/src/test/java/software/amazon/encryption/s3/S3EncryptionClientReEncryptInstructionFileTest.java @@ -275,7 +275,7 @@ public void testAesReEncryptInstructionFileRejectsCustomInstructionFileSuffix() e .getMessage() .contains( - "Custom Instruction file suffix is not applicable for AES keyring!" + "Custom Instruction file suffix is only applicable for RSA keyring!" ) ); } From 7092675505b595c1ec3f398a9a3caf4d0b7907ea Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Mon, 21 Jul 2025 16:17:47 -0700 Subject: [PATCH 09/10] removed unsued import --- .../encryption/s3/internal/ReEncryptInstructionFileRequest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java index 539f2e799..a1fdd5bbc 100644 --- a/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java +++ b/src/main/java/software/amazon/encryption/s3/internal/ReEncryptInstructionFileRequest.java @@ -5,7 +5,6 @@ import static software.amazon.encryption.s3.S3EncryptionClientUtilities.DEFAULT_INSTRUCTION_FILE_SUFFIX; import software.amazon.encryption.s3.S3EncryptionClientException; -import software.amazon.encryption.s3.materials.AesKeyring; import software.amazon.encryption.s3.materials.RawKeyring; import software.amazon.encryption.s3.materials.RsaKeyring; From dd7780462e93c3102e6428270b952b5c568273e7 Mon Sep 17 00:00:00 2001 From: Anirav Kareddy Date: Thu, 24 Jul 2025 14:24:15 -0700 Subject: [PATCH 10/10] made examples for reEncryptInstructionFile be clearer --- .../ReEncryptInstructionFileExample.java | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java index a29aa33b3..933039245 100644 --- a/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java +++ b/src/examples/java/software/amazon/encryption/s3/examples/ReEncryptInstructionFileExample.java @@ -81,17 +81,19 @@ public static void simpleAesKeyringReEncryptInstructionFile( // Generate the original AES key for initial encryption SecretKey originalAesKey = generateAesKey(); + // Sample metadata for AES keyring identification and context - not used for encryption/decryption purposes + // Helps distinguish between the old and new AES keyrings during the reEncryptInstructionFile operation + MaterialsDescription originalMaterialsDescription = MaterialsDescription + .builder() + .put("version", "1.0") + .put("rotated", "no") + .build(); + // Create the original AES keyring with materials description AesKeyring oldKeyring = AesKeyring .builder() .wrappingKey(originalAesKey) - .materialsDescription( - MaterialsDescription - .builder() - .put("version", "1.0") - .put("rotated", "no") - .build() - ) + .materialsDescription(originalMaterialsDescription) .build(); // Create a default S3 client for instruction file operations @@ -120,17 +122,19 @@ public static void simpleAesKeyringReEncryptInstructionFile( // Generate a new AES key for re-encryption (rotating wrapping key) SecretKey newAesKey = generateAesKey(); + // Sample metadata for rotated AES keyring identification and context - not used for encryption/decryption purposes + // Helps distinguish between the old and new AES keyrings during the reEncryptInstructionFile operation + MaterialsDescription newMaterialsDescription = MaterialsDescription + .builder() + .put("version", "2.0") + .put("rotated", "yes") + .build(); + // Create a new keyring with the new AES key and updated materials description AesKeyring newKeyring = AesKeyring .builder() .wrappingKey(newAesKey) - .materialsDescription( - MaterialsDescription - .builder() - .put("version", "2.0") - .put("rotated", "yes") - .build() - ) + .materialsDescription(newMaterialsDescription) .build(); // Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key @@ -216,17 +220,19 @@ public static void simpleRsaKeyringReEncryptInstructionFile( .privateKey(originalPrivateKey) .build(); + // Sample metadata for RSA keyring identification and context - not used for encryption/decryption purposes + // Helps distinguish between the old and new RSA keyrings during the reEncryptInstructionFile operation + MaterialsDescription originalMaterialsDescription = MaterialsDescription + .builder() + .put("version", "1.0") + .put("rotated", "no") + .build(); + // Create the original RSA keyring with materials description RsaKeyring originalKeyring = RsaKeyring .builder() .wrappingKeyPair(originalPartialRsaKeyPair) - .materialsDescription( - MaterialsDescription - .builder() - .put("version", "1.0") - .put("rotated", "no") - .build() - ) + .materialsDescription(originalMaterialsDescription) .build(); // Create a default S3 client for instruction file operations @@ -264,17 +270,19 @@ public static void simpleRsaKeyringReEncryptInstructionFile( .privateKey(newPrivateKey) .build(); + // Sample metadata for rotated RSA keyring identification and context - not used for encryption/decryption purposes + // Helps distinguish between the old and new RSA keyrings during the reEncryptInstructionFile operation + MaterialsDescription newMaterialsDescription = MaterialsDescription + .builder() + .put("version", "2.0") + .put("rotated", "yes") + .build(); + // Create the new RSA keyring with updated materials description RsaKeyring newKeyring = RsaKeyring .builder() .wrappingKeyPair(newPartialRsaKeyPair) - .materialsDescription( - MaterialsDescription - .builder() - .put("version", "2.0") - .put("rotated", "yes") - .build() - ) + .materialsDescription(newMaterialsDescription) .build(); // Create the re-encryption of instruction file request to re-encrypt the encrypted data key with the new wrapping key @@ -334,7 +342,12 @@ public static void simpleRsaKeyringReEncryptInstructionFile( /** * This example demonstrates generating a custom instruction file to enable access to encrypted object by a third party. - * This enables secure sharing of encrypted objects without sharing private keys. + * It showcases a scenario where: + * 1. The original client encrypts and uploads an object to S3. + * 2. The original client wants to share this encrypted object with a third party client without sharing their private key. + * 3. A new instruction file is created specifically for the third party client, containing the data key encrypted with the third party's public key. + * 4. The third party client can then access and decrypt the object using their own private key and custom instruction file. + * 5. The original client can still access and decrypt the object using their own private key and instruction file. * * @param bucket The name of the Amazon S3 bucket to perform operations on. * @throws NoSuchAlgorithmException if RSA algorithm is not available @@ -361,17 +374,19 @@ public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix( .privateKey(clientPrivateKey) .build(); + // Sample metadata for client keyring identification and context - not used for encryption/decryption purposes + // Helps distinguish between the client and third party RSA keyrings during the reEncryptInstructionFile operation + MaterialsDescription clientMaterialsDescription = MaterialsDescription + .builder() + .put("isOwner", "yes") + .put("access-level", "admin") + .build(); + // Create the client's RSA keyring with materials description RsaKeyring clientKeyring = RsaKeyring .builder() .wrappingKeyPair(clientPartialRsaKeyPair) - .materialsDescription( - MaterialsDescription - .builder() - .put("isOwner", "yes") - .put("access-level", "admin") - .build() - ) + .materialsDescription(clientMaterialsDescription) .build(); // Create a default S3 client for instruction file operations @@ -409,19 +424,21 @@ public static void simpleRsaKeyringReEncryptInstructionFileWithCustomSuffix( .privateKey(thirdPartyPrivateKey) .build(); + // Sample metadata for third party keyring identification and context - not used for encryption/decryption purposes + // Helps distinguish between the client and third party RSA keyrings during the reEncryptInstructionFile operation + MaterialsDescription thirdPartyMaterialsDescription = MaterialsDescription + .builder() + .put("isOwner", "no") + .put("access-level", "user") + .build(); + // Create RSA keyring with third party's public key and updated materials description for re-encryption request RsaKeyring sharedKeyring = RsaKeyring .builder() .wrappingKeyPair( PartialRsaKeyPair.builder().publicKey(thirdPartyPublicKey).build() ) - .materialsDescription( - MaterialsDescription - .builder() - .put("isOwner", "no") - .put("access-level", "user") - .build() - ) + .materialsDescription(thirdPartyMaterialsDescription) .build(); // Create RSA keyring with third party's public and private keys for decryption purposes with updated materials description