diff --git a/aws-java-sdk-s3/pom.xml b/aws-java-sdk-s3/pom.xml
deleted file mode 100644
index 579789748..000000000
--- a/aws-java-sdk-s3/pom.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
- 4.0.0
-
- com.amazonaws
- aws-java-sdk-pom
- 1.12.184-SNAPSHOT
-
- com.amazonaws
- aws-java-sdk-s3
- AWS Java SDK for Amazon S3
- The AWS Java SDK for Amazon S3 module holds the client classes that are used for communicating with Amazon Simple Storage Service
- https://aws.amazon.com/sdkforjava
-
-
-
-
- aws-java-sdk-kms
- com.amazonaws
- false
- ${awsjavasdk.version}
-
-
- aws-java-sdk-core
- com.amazonaws
- false
- ${awsjavasdk.version}
-
-
- aws-java-sdk-test-utils
- com.amazonaws
- false
- test
- ${awsjavasdk.version}
-
-
- jmespath-java
- com.amazonaws
- false
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
-
-
-
-
-
-
-
-
- versiondiff
-
-
-
- com.github.siom79.japicmp
- japicmp-maven-plugin
- 0.5.0
-
-
- verify
-
- cmp
-
-
-
-
-
-
- com.amazonaws
- aws-java-sdk-s3
- RELEASE
-
-
-
-
- ${project.build.directory}/${project.artifactId}-${project.version}.jar
-
-
-
- true
- public
- false
- false
- false
-
-
-
-
-
-
-
-
diff --git a/pom.xml b/pom.xml
index b89d07d56..f2cbe5a45 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,13 +5,13 @@
4.0.0
software.amazon.encryption
- s3-client
+ s3
3.0-SNAPSHOT
jar
AWS S3 Encryption Client
The AWS S3 Encryption Client provides client-side encryption for S3
- https://github.com/aws/aws-s33c-java
+ https://github.com/aws/aws-s3-encryption-client-java
@@ -51,7 +51,7 @@
software.amazon.awssdk
bom
- 2.17.154
+ 2.17.204
true
pom
import
@@ -80,14 +80,14 @@
software.amazon.awssdk
s3
- 2.17.154
+ 2.17.204
true
software.amazon.awssdk
kms
- 2.17.154
+ 2.17.204
true
diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java
new file mode 100644
index 000000000..44a2245f7
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClient.java
@@ -0,0 +1,224 @@
+package software.amazon.encryption.s3;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.core.exception.SdkClientException;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.core.sync.ResponseTransformer;
+import software.amazon.awssdk.http.AbortableInputStream;
+import software.amazon.awssdk.protocols.jsoncore.JsonNode;
+import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
+import software.amazon.awssdk.protocols.jsoncore.JsonWriter;
+import software.amazon.awssdk.protocols.jsoncore.JsonWriter.JsonGenerationException;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectResponse;
+import software.amazon.awssdk.services.s3.model.InvalidObjectStateException;
+import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectResponse;
+import software.amazon.awssdk.services.s3.model.S3Exception;
+import software.amazon.awssdk.utils.IoUtils;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+import software.amazon.encryption.s3.materials.DecryptionMaterials;
+import software.amazon.encryption.s3.materials.DecryptMaterialsRequest;
+import software.amazon.encryption.s3.materials.EncryptionMaterialsRequest;
+import software.amazon.encryption.s3.materials.EncryptedDataKey;
+import software.amazon.encryption.s3.materials.EncryptionMaterials;
+import software.amazon.encryption.s3.materials.MaterialsManager;
+
+public class S3EncryptionClient implements S3Client {
+
+ private final S3Client _wrappedClient;
+ private final MaterialsManager _materialsManager;
+
+ public S3EncryptionClient(S3Client client, MaterialsManager materialsManager) {
+ _wrappedClient = client;
+ _materialsManager = materialsManager;
+ }
+
+ @Override
+ public PutObjectResponse putObject(PutObjectRequest putObjectRequest, RequestBody requestBody)
+ throws AwsServiceException, SdkClientException, S3Exception {
+
+ // TODO: This is proof-of-concept code and needs to be refactored
+
+ // Get content encryption key
+ EncryptionMaterials materials = _materialsManager.getEncryptionMaterials(EncryptionMaterialsRequest.builder()
+ .build());
+ SecretKey contentKey = materials.dataKey();
+ // Encrypt content
+ byte[] iv = new byte[12]; // default GCM IV length
+ new SecureRandom().nextBytes(iv);
+
+ final String contentEncryptionAlgorithm = "AES/GCM/NoPadding";
+ final Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(contentEncryptionAlgorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, contentKey, new GCMParameterSpec(128, iv));
+ } catch (NoSuchAlgorithmException
+ | NoSuchPaddingException
+ | InvalidAlgorithmParameterException
+ | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ }
+
+ byte[] ciphertext;
+ try {
+ byte[] input = IoUtils.toByteArray(requestBody.contentStreamProvider().newStream());
+ ciphertext = cipher.doFinal(input);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalBlockSizeException e) {
+ throw new RuntimeException(e);
+ } catch (BadPaddingException e) {
+ throw new RuntimeException(e);
+ }
+
+ // Save content metadata into request
+ Base64.Encoder encoder = Base64.getEncoder();
+ Map metadata = new HashMap<>(putObjectRequest.metadata());
+ EncryptedDataKey edk = materials.encryptedDataKeys().get(0);
+ metadata.put("x-amz-key-v2", encoder.encodeToString(edk.ciphertext()));
+ metadata.put("x-amz-iv", encoder.encodeToString(iv));
+ metadata.put("x-amz-matdesc", /* TODO: JSON encoded */ "{}");
+ metadata.put("x-amz-cek-alg", contentEncryptionAlgorithm);
+ metadata.put("x-amz-tag-len", /* TODO: take from algo suite */ "128");
+ metadata.put("x-amz-wrap-alg", edk.keyProviderId());
+
+ try (JsonWriter jsonWriter = JsonWriter.create()) {
+ jsonWriter.writeStartObject();
+ for (Entry entry : materials.encryptionContext().entrySet()) {
+ jsonWriter.writeFieldName(entry.getKey()).writeValue(entry.getValue());
+ }
+ jsonWriter.writeEndObject();
+
+ String jsonEncryptionContext = new String(jsonWriter.getBytes(), StandardCharsets.UTF_8);
+ metadata.put("x-amz-matdesc", jsonEncryptionContext);
+ } catch (JsonGenerationException e) {
+ throw new RuntimeException(e);
+ }
+
+ putObjectRequest = putObjectRequest.toBuilder().metadata(metadata).build();
+
+ return _wrappedClient.putObject(putObjectRequest, RequestBody.fromBytes(ciphertext));
+ }
+
+ @Override
+ public T getObject(GetObjectRequest getObjectRequest, ResponseTransformer responseTransformer)
+ throws NoSuchKeyException, InvalidObjectStateException, AwsServiceException, SdkClientException, S3Exception {
+
+ // TODO: This is proof-of-concept code and needs to be refactored
+
+ ResponseInputStream objectStream = _wrappedClient.getObject(getObjectRequest);
+ byte[] output;
+ try {
+ output = IoUtils.toByteArray(objectStream);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ GetObjectResponse response = objectStream.response();
+ Map metadata = response.metadata();
+
+ // Build encrypted data key
+ Base64.Decoder decoder = Base64.getDecoder();
+ byte[] edkCiphertext = decoder.decode(metadata.get("x-amz-key-v2"));
+ String keyProviderId = metadata.get("x-amz-wrap-alg");
+ EncryptedDataKey edk = EncryptedDataKey.builder()
+ .ciphertext(edkCiphertext)
+ .keyProviderId(keyProviderId)
+ .build();
+ List encryptedDataKeys = Collections.singletonList(edk);
+
+ // Get encryption context
+ final Map encryptionContext = new HashMap<>();
+ final String jsonEncryptionContext = metadata.get("x-amz-matdesc");
+ try {
+ JsonNodeParser parser = JsonNodeParser.create();
+ JsonNode objectNode = parser.parse(jsonEncryptionContext);
+
+ for (Map.Entry entry : objectNode.asObject().entrySet()) {
+ encryptionContext.put(entry.getKey(), entry.getValue().asString());
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ // Get decryption materials
+ final String contentEncryptionAlgorithm = metadata.get("x-amz-cek-alg");
+ AlgorithmSuite algorithmSuite = null;
+ if (contentEncryptionAlgorithm.equals("AES/GCM/NoPadding")) {
+ algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;;
+ }
+
+ if (algorithmSuite == null) {
+ throw new RuntimeException("Unknown content encryption algorithm: " + contentEncryptionAlgorithm);
+ }
+
+ DecryptMaterialsRequest request = DecryptMaterialsRequest.builder()
+ .algorithmSuite(algorithmSuite)
+ .encryptedDataKeys(encryptedDataKeys)
+ .encryptionContext(encryptionContext)
+ .build();
+ DecryptionMaterials materials = _materialsManager.decryptMaterials(request);
+
+ // Get content encryption information
+ SecretKey contentKey = new SecretKeySpec(materials.plaintextDataKey(), "AES");
+ final int tagLength = Integer.parseInt(metadata.get("x-amz-tag-len"));
+ byte[] iv = decoder.decode(metadata.get("x-amz-iv"));
+ final Cipher cipher;
+ byte[] plaintext;
+ try {
+ cipher = Cipher.getInstance(contentEncryptionAlgorithm);
+ cipher.init(Cipher.DECRYPT_MODE, contentKey, new GCMParameterSpec(tagLength, iv));
+ plaintext = cipher.doFinal(output);
+ } catch (NoSuchAlgorithmException
+ | NoSuchPaddingException
+ | InvalidAlgorithmParameterException
+ | InvalidKeyException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalBlockSizeException e) {
+ throw new RuntimeException(e);
+ } catch (BadPaddingException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ return responseTransformer.transform(response,
+ AbortableInputStream.create(new ByteArrayInputStream(plaintext)));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String serviceName() {
+ return _wrappedClient.serviceName();
+ }
+
+ @Override
+ public void close() {
+ _wrappedClient.close();
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/S3EncryptionClientException.java b/src/main/java/software/amazon/encryption/s3/S3EncryptionClientException.java
new file mode 100644
index 000000000..c3effb04e
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/S3EncryptionClientException.java
@@ -0,0 +1,17 @@
+package software.amazon.encryption.s3;
+
+import software.amazon.awssdk.core.exception.SdkClientException;
+
+public class S3EncryptionClientException extends SdkClientException {
+
+ public S3EncryptionClientException(String message) {
+ super(SdkClientException.builder()
+ .message(message));
+ }
+
+ public S3EncryptionClientException(String message, Throwable cause) {
+ super(SdkClientException.builder()
+ .message(message)
+ .cause(cause));
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/algorithms/AlgorithmSuite.java b/src/main/java/software/amazon/encryption/s3/algorithms/AlgorithmSuite.java
new file mode 100644
index 000000000..7d5ce6129
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/algorithms/AlgorithmSuite.java
@@ -0,0 +1,57 @@
+package software.amazon.encryption.s3.algorithms;
+
+
+public enum AlgorithmSuite {
+ ALG_AES_256_GCM_IV12_TAG16_NO_KDF(0x0078,
+ "AES",
+ 256,
+ "AES/GCM/NoPadding",
+ 128,
+ 96,
+ 128,
+ Constants.GCM_MAX_CONTENT_LENGTH_BITS);
+
+ private int _id;
+ private String _dataKeyAlgorithm;
+ private int _dataKeyLengthBits;
+ private String _cipherName;
+ private int _cipherBlockSizeBits;
+ private int _cipherNonceLengthBits;
+ private int _cipherTagLengthBits;
+ private long _cipherMaxContentLengthBits;
+
+ AlgorithmSuite(int id,
+ String dataKeyAlgorithm,
+ int dataKeyLengthBits,
+ String cipherName,
+ int cipherBlockSizeBits,
+ int cipherNonceLengthBits,
+ int cipherTagLengthBits,
+ long cipherMaxContentLengthBits
+ ) {
+ this._id = id;
+ this._dataKeyAlgorithm = dataKeyAlgorithm;
+ this._dataKeyLengthBits = dataKeyLengthBits;
+ this._cipherName = cipherName;
+ this._cipherBlockSizeBits = cipherBlockSizeBits;
+ this._cipherNonceLengthBits = cipherNonceLengthBits;
+ this._cipherTagLengthBits = cipherTagLengthBits;
+ this._cipherMaxContentLengthBits = cipherMaxContentLengthBits;
+ }
+
+ public String dataKeyAlgorithm() {
+ return _dataKeyAlgorithm;
+ }
+
+ public int dataKeyLengthBits() {
+ return _dataKeyLengthBits;
+ }
+
+ public String cipherName() {
+ return _cipherName;
+ }
+
+ public int nonceLengthBytes() {
+ return _cipherNonceLengthBits / 8;
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/algorithms/Constants.java b/src/main/java/software/amazon/encryption/s3/algorithms/Constants.java
new file mode 100644
index 000000000..d5466183e
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/algorithms/Constants.java
@@ -0,0 +1,6 @@
+package software.amazon.encryption.s3.algorithms;
+
+class Constants {
+ // Maximum length of the content that can be encrypted in GCM mode.
+ static final long GCM_MAX_CONTENT_LENGTH_BITS = (1L<<39) - 256;
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/AESKeyring.java b/src/main/java/software/amazon/encryption/s3/materials/AESKeyring.java
new file mode 100644
index 000000000..cdcd22445
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/AESKeyring.java
@@ -0,0 +1,146 @@
+package software.amazon.encryption.s3.materials;
+
+import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import software.amazon.encryption.s3.S3EncryptionClientException;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+/**
+ * AESKeyring will use an AES key to wrap the data key used to encrypt content.
+ */
+public class AESKeyring implements Keyring {
+
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String KEY_PROVIDER_ID = "AES/GCM";
+ private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+ private static final int NONCE_LENGTH_BYTES = 12;
+ private static final int TAG_LENGTH_BYTES = 16;
+ private static final int TAG_LENGTH_BITS = TAG_LENGTH_BYTES * 8;
+
+ private final SecretKey _wrappingKey;
+ private final SecureRandom _secureRandom;
+ private final DataKeyGenerator _dataKeyGenerator;
+
+ private AESKeyring(Builder builder) {
+ _wrappingKey = builder._wrappingKey;
+ _secureRandom = builder._secureRandom;
+ _dataKeyGenerator = builder._dataKeyGenerator;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public EncryptionMaterials onEncrypt(EncryptionMaterials materials) {
+ if (materials.plaintextDataKey() == null) {
+ SecretKey dataKey = _dataKeyGenerator.generateDataKey(materials.algorithmSuite());
+ materials = materials.toBuilder()
+ .plaintextDataKey(dataKey.getEncoded())
+ .build();
+ }
+
+ try {
+ byte[] nonce = new byte[NONCE_LENGTH_BYTES];
+ _secureRandom.nextBytes(nonce);
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BITS, nonce);
+
+ final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, _wrappingKey, gcmParameterSpec, _secureRandom);
+
+ AlgorithmSuite algorithmSuite = materials.algorithmSuite();
+ cipher.updateAAD(algorithmSuite.cipherName().getBytes(StandardCharsets.UTF_8));
+ byte[] ciphertext = cipher.doFinal(materials.plaintextDataKey());
+
+ // The encrypted data key is the nonce prepended to the ciphertext
+ byte[] encodedBytes = new byte[nonce.length + ciphertext.length];
+ System.arraycopy(nonce, 0, encodedBytes, 0, nonce.length);
+ System.arraycopy(ciphertext, 0, encodedBytes, nonce.length, ciphertext.length);
+
+ EncryptedDataKey encryptedDataKey = EncryptedDataKey.builder()
+ .keyProviderId(KEY_PROVIDER_ID)
+ .ciphertext(encodedBytes)
+ .build();
+
+ List encryptedDataKeys = new ArrayList<>(materials.encryptedDataKeys());
+ encryptedDataKeys.add(encryptedDataKey);
+
+ return materials.toBuilder()
+ .encryptedDataKeys(encryptedDataKeys)
+ .build();
+ } catch (Exception e) {
+ throw new S3EncryptionClientException("Unable to " + CIPHER_ALGORITHM + " wrap", e);
+ }
+ }
+
+ @Override
+ public DecryptionMaterials onDecrypt(final DecryptionMaterials materials, List encryptedDataKeys) {
+ if (materials.plaintextDataKey() != null) {
+ return materials;
+ }
+
+ for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
+ if (!encryptedDataKey.keyProviderId().equals(KEY_PROVIDER_ID)) {
+ continue;
+ }
+
+ byte[] encodedBytes = encryptedDataKey.ciphertext();
+ byte[] nonce = new byte[NONCE_LENGTH_BYTES];
+ byte[] ciphertext = new byte[encodedBytes.length - nonce.length];
+
+ System.arraycopy(encodedBytes, 0, nonce, 0, nonce.length);
+ System.arraycopy(encodedBytes, nonce.length, ciphertext, 0, ciphertext.length);
+
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BITS, nonce);
+ try {
+ final Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, _wrappingKey, gcmParameterSpec);
+
+ AlgorithmSuite algorithmSuite = materials.algorithmSuite();
+ cipher.updateAAD(algorithmSuite.cipherName().getBytes(StandardCharsets.UTF_8));
+ byte[] plaintext = cipher.doFinal(ciphertext);
+
+ return materials.toBuilder().plaintextDataKey(plaintext).build();
+ } catch (Exception e) {
+ throw new S3EncryptionClientException("Unable to " + CIPHER_ALGORITHM + " unwrap", e);
+ }
+ }
+
+ return materials;
+ }
+
+ public static class Builder {
+ private SecretKey _wrappingKey;
+ private SecureRandom _secureRandom = new SecureRandom();
+ private DataKeyGenerator _dataKeyGenerator = new DefaultDataKeyGenerator();
+
+ private Builder() {}
+
+ public Builder wrappingKey(SecretKey wrappingKey) {
+ if (!wrappingKey.getAlgorithm().equals(KEY_ALGORITHM)) {
+ throw new S3EncryptionClientException("Invalid algorithm '" + wrappingKey.getAlgorithm() + "', expecting " + KEY_ALGORITHM);
+ }
+ _wrappingKey = wrappingKey;
+ return this;
+ }
+
+ public Builder secureRandom(SecureRandom secureRandom) {
+ _secureRandom = secureRandom;
+ return this;
+ }
+
+ public Builder dataKeyGenerator(DataKeyGenerator dataKeyGenerator) {
+ _dataKeyGenerator = dataKeyGenerator;
+ return this;
+ }
+
+ public AESKeyring build() {
+ return new AESKeyring(this);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/software/amazon/encryption/s3/materials/DataKeyGenerator.java b/src/main/java/software/amazon/encryption/s3/materials/DataKeyGenerator.java
new file mode 100644
index 000000000..4b7b2ac05
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/DataKeyGenerator.java
@@ -0,0 +1,9 @@
+package software.amazon.encryption.s3.materials;
+
+import javax.crypto.SecretKey;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+@FunctionalInterface
+public interface DataKeyGenerator {
+ SecretKey generateDataKey(AlgorithmSuite algorithmSuite);
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/DecryptMaterialsRequest.java b/src/main/java/software/amazon/encryption/s3/materials/DecryptMaterialsRequest.java
new file mode 100644
index 000000000..499bccfd6
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/DecryptMaterialsRequest.java
@@ -0,0 +1,68 @@
+package software.amazon.encryption.s3.materials;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+public class DecryptMaterialsRequest {
+
+ private final AlgorithmSuite _algorithmSuite;
+ private final List _encryptedDataKeys;
+ private final Map _encryptionContext;
+
+ private DecryptMaterialsRequest(Builder builder) {
+ this._algorithmSuite = builder._algorithmSuite;
+ this._encryptedDataKeys = builder._encryptedDataKeys;
+ this._encryptionContext = builder._encryptionContext;
+ }
+
+ static public Builder builder() {
+ return new Builder();
+ }
+
+ public AlgorithmSuite algorithmSuite() {
+ return _algorithmSuite;
+ }
+
+ public List encryptedDataKeys() {
+ return _encryptedDataKeys;
+ }
+
+ public Map encryptionContext() {
+ return _encryptionContext;
+ }
+
+ static public class Builder {
+
+ private AlgorithmSuite _algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;
+ private Map _encryptionContext = Collections.emptyMap();
+ private List _encryptedDataKeys = Collections.emptyList();
+
+ private Builder() {
+ }
+
+ public Builder algorithmSuite(AlgorithmSuite algorithmSuite) {
+ _algorithmSuite = algorithmSuite;
+ return this;
+ }
+
+ public Builder encryptionContext(Map encryptionContext) {
+ _encryptionContext = encryptionContext == null
+ ? Collections.emptyMap()
+ : Collections.unmodifiableMap(encryptionContext);
+ return this;
+ }
+
+ public Builder encryptedDataKeys(List encryptedDataKeys) {
+ _encryptedDataKeys = encryptedDataKeys == null
+ ? Collections.emptyList()
+ : Collections.unmodifiableList(encryptedDataKeys);
+ return this;
+ }
+
+ public DecryptMaterialsRequest build() {
+ return new DecryptMaterialsRequest(this);
+ }
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/DecryptionMaterials.java b/src/main/java/software/amazon/encryption/s3/materials/DecryptionMaterials.java
new file mode 100644
index 000000000..5390e95a6
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/DecryptionMaterials.java
@@ -0,0 +1,83 @@
+package software.amazon.encryption.s3.materials;
+
+import java.util.Collections;
+import java.util.Map;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+final public class DecryptionMaterials {
+
+ // Identifies what sort of crypto algorithms we want to use
+ private final AlgorithmSuite _algorithmSuite;
+
+ // Additional information passed into encrypted that is required on decryption as well
+ // Should NOT contain sensitive information
+ private final Map _encryptionContext;
+
+ private final byte[] _plaintextDataKey;
+
+ private DecryptionMaterials(Builder builder) {
+ this._algorithmSuite = builder._algorithmSuite;
+ this._encryptionContext = builder._encryptionContext;
+ this._plaintextDataKey = builder._plaintextDataKey;
+ }
+
+ static public Builder builder() {
+ return new Builder();
+ }
+
+ public AlgorithmSuite algorithmSuite() {
+ return _algorithmSuite;
+ }
+
+ public Map encryptionContext() {
+ return _encryptionContext;
+ }
+
+ public byte[] plaintextDataKey() {
+ return _plaintextDataKey;
+ }
+
+ public SecretKey dataKey() {
+ return new SecretKeySpec(_plaintextDataKey, "AES");
+ }
+
+ public Builder toBuilder() {
+ return new Builder()
+ .algorithmSuite(_algorithmSuite)
+ .encryptionContext(_encryptionContext)
+ .plaintextDataKey(_plaintextDataKey);
+ }
+
+ static public class Builder {
+
+ private AlgorithmSuite _algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;
+ private Map _encryptionContext = Collections.emptyMap();
+ private byte[] _plaintextDataKey = null;
+
+ private Builder() {
+ }
+
+ public Builder algorithmSuite(AlgorithmSuite algorithmSuite) {
+ _algorithmSuite = algorithmSuite;
+ return this;
+ }
+
+ public Builder encryptionContext(Map encryptionContext) {
+ _encryptionContext = encryptionContext == null
+ ? Collections.emptyMap()
+ : Collections.unmodifiableMap(encryptionContext);
+ return this;
+ }
+
+ public Builder plaintextDataKey(byte[] plaintextDataKey) {
+ _plaintextDataKey = plaintextDataKey == null ? null : plaintextDataKey.clone();
+ return this;
+ }
+
+ public DecryptionMaterials build() {
+ return new DecryptionMaterials(this);
+ }
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/DefaultDataKeyGenerator.java b/src/main/java/software/amazon/encryption/s3/materials/DefaultDataKeyGenerator.java
new file mode 100644
index 000000000..920256c02
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/DefaultDataKeyGenerator.java
@@ -0,0 +1,22 @@
+package software.amazon.encryption.s3.materials;
+
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import software.amazon.encryption.s3.S3EncryptionClientException;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+public class DefaultDataKeyGenerator implements DataKeyGenerator {
+
+ public SecretKey generateDataKey(AlgorithmSuite algorithmSuite) {
+ KeyGenerator generator;
+ try {
+ generator = KeyGenerator.getInstance(algorithmSuite.dataKeyAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ throw new S3EncryptionClientException("Unable to generate a(n) " + algorithmSuite.dataKeyAlgorithm() + " data key", e);
+ }
+
+ generator.init(algorithmSuite.dataKeyLengthBits());
+ return generator.generateKey();
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/DefaultMaterialsManager.java b/src/main/java/software/amazon/encryption/s3/materials/DefaultMaterialsManager.java
new file mode 100644
index 000000000..7e6562323
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/DefaultMaterialsManager.java
@@ -0,0 +1,31 @@
+package software.amazon.encryption.s3.materials;
+
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+public class DefaultMaterialsManager implements MaterialsManager {
+ private final Keyring _keyring;
+
+
+ public DefaultMaterialsManager(Keyring keyring) {
+ _keyring = keyring;
+ }
+
+ public EncryptionMaterials getEncryptionMaterials(EncryptionMaterialsRequest request) {
+ EncryptionMaterials materials = EncryptionMaterials.builder()
+ .algorithmSuite(AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF)
+ .encryptionContext(request.encryptionContext())
+ .build();
+
+ return _keyring.onEncrypt(materials);
+ }
+
+ public DecryptionMaterials decryptMaterials(DecryptMaterialsRequest request) {
+ DecryptionMaterials materials = DecryptionMaterials.builder()
+ .algorithmSuite(request.algorithmSuite())
+ .encryptionContext(request.encryptionContext())
+ .build();
+
+ return _keyring.onDecrypt(materials, request.encryptedDataKeys());
+ }
+
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/EncryptedDataKey.java b/src/main/java/software/amazon/encryption/s3/materials/EncryptedDataKey.java
new file mode 100644
index 000000000..4d61a4173
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/EncryptedDataKey.java
@@ -0,0 +1,62 @@
+package software.amazon.encryption.s3.materials;
+
+public class EncryptedDataKey {
+
+ // forms the "domain" of the key e.g. "aws-kms"
+ private final String _keyProviderId;
+
+ // a unique identifer e.g. an ARN
+ private final byte[] _keyProviderInfo;
+ private final byte[] _ciphertext;
+
+ private EncryptedDataKey(Builder builder) {
+ this._keyProviderId = builder._keyProviderId;
+ this._keyProviderInfo = builder._keyProviderInfo;
+ this._ciphertext = builder._ciphertext;
+ }
+
+ static public Builder builder() {
+ return new Builder();
+ }
+
+ public String keyProviderId() {
+ return _keyProviderId;
+ }
+
+ public byte[] keyProviderInfo() {
+ return _keyProviderInfo;
+ }
+
+ public byte[] ciphertext() {
+ return _ciphertext;
+ }
+
+ static public class Builder {
+
+ private String _keyProviderId = null;
+ private byte[] _keyProviderInfo = null;
+ private byte[] _ciphertext = null;
+
+ private Builder() {
+ }
+
+ public Builder keyProviderId(String keyProviderId) {
+ _keyProviderId = keyProviderId;
+ return this;
+ }
+
+ public Builder keyProviderInfo(byte[] keyProviderInfo) {
+ _keyProviderInfo = keyProviderInfo == null ? null : keyProviderInfo.clone();
+ return this;
+ }
+
+ public Builder ciphertext(byte[] ciphertext) {
+ _ciphertext = ciphertext == null ? null : ciphertext.clone();
+ return this;
+ }
+
+ public EncryptedDataKey build() {
+ return new EncryptedDataKey(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
new file mode 100644
index 000000000..2c35a24d6
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterials.java
@@ -0,0 +1,99 @@
+package software.amazon.encryption.s3.materials;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import software.amazon.encryption.s3.algorithms.AlgorithmSuite;
+
+final public class EncryptionMaterials {
+
+ // Identifies what sort of crypto algorithms we want to use
+ private final AlgorithmSuite _algorithmSuite;
+
+ // Additional information passed into encrypted that is required on decryption as well
+ // Should NOT contain sensitive information
+ private final Map _encryptionContext;
+
+ private final List _encryptedDataKeys;
+ private final byte[] _plaintextDataKey;
+
+ private EncryptionMaterials(Builder builder) {
+ this._algorithmSuite = builder._algorithmSuite;
+ this._encryptionContext = builder._encryptionContext;
+ this._encryptedDataKeys = builder._encryptedDataKeys;
+ this._plaintextDataKey = builder._plaintextDataKey;
+ }
+
+ static public Builder builder() {
+ return new Builder();
+ }
+
+ public AlgorithmSuite algorithmSuite() {
+ return _algorithmSuite;
+ }
+
+ public Map encryptionContext() {
+ return _encryptionContext;
+ }
+
+ public List encryptedDataKeys() {
+ return _encryptedDataKeys;
+ }
+
+ public byte[] plaintextDataKey() {
+ return _plaintextDataKey;
+ }
+
+ public SecretKey dataKey() {
+ return new SecretKeySpec(_plaintextDataKey, "AES");
+ }
+
+ public Builder toBuilder() {
+ return new Builder()
+ .algorithmSuite(_algorithmSuite)
+ .encryptionContext(_encryptionContext)
+ .encryptedDataKeys(_encryptedDataKeys)
+ .plaintextDataKey(_plaintextDataKey);
+ }
+
+ static public class Builder {
+
+ private AlgorithmSuite _algorithmSuite = AlgorithmSuite.ALG_AES_256_GCM_IV12_TAG16_NO_KDF;
+ private Map _encryptionContext = Collections.emptyMap();
+ private List _encryptedDataKeys = Collections.emptyList();
+ private byte[] _plaintextDataKey = null;
+
+ private Builder() {
+ }
+
+ public Builder algorithmSuite(AlgorithmSuite algorithmSuite) {
+ _algorithmSuite = algorithmSuite;
+ return this;
+ }
+
+ public Builder encryptionContext(Map encryptionContext) {
+ _encryptionContext = encryptionContext == null
+ ? Collections.emptyMap()
+ : Collections.unmodifiableMap(encryptionContext);
+ return this;
+ }
+
+ public Builder encryptedDataKeys(List encryptedDataKeys) {
+ _encryptedDataKeys = encryptedDataKeys == null
+ ? Collections.emptyList()
+ : Collections.unmodifiableList(encryptedDataKeys);
+ return this;
+ }
+
+ public Builder plaintextDataKey(byte[] plaintextDataKey) {
+ _plaintextDataKey = plaintextDataKey == null ? null : plaintextDataKey.clone();
+ return this;
+ }
+
+ public EncryptionMaterials build() {
+ return new EncryptionMaterials(this);
+ }
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterialsRequest.java b/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterialsRequest.java
new file mode 100644
index 000000000..85364e4db
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/EncryptionMaterialsRequest.java
@@ -0,0 +1,40 @@
+package software.amazon.encryption.s3.materials;
+
+import java.util.Collections;
+import java.util.Map;
+
+final public class EncryptionMaterialsRequest {
+
+ private final Map _encryptionContext;
+
+ private EncryptionMaterialsRequest(Builder builder) {
+ this._encryptionContext = builder._encryptionContext;
+ }
+
+ static public Builder builder() {
+ return new Builder();
+ }
+
+ public Map encryptionContext() {
+ return _encryptionContext;
+ }
+
+ static public class Builder {
+
+ private Map _encryptionContext = Collections.emptyMap();
+
+ private Builder() {
+ }
+
+ public Builder encryptionContext(Map encryptionContext) {
+ _encryptionContext = encryptionContext == null
+ ? Collections.emptyMap()
+ : Collections.unmodifiableMap(encryptionContext);
+ return this;
+ }
+
+ public EncryptionMaterialsRequest build() {
+ return new EncryptionMaterialsRequest(this);
+ }
+ }
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/Keyring.java b/src/main/java/software/amazon/encryption/s3/materials/Keyring.java
new file mode 100644
index 000000000..0e313bab9
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/Keyring.java
@@ -0,0 +1,12 @@
+package software.amazon.encryption.s3.materials;
+
+import java.util.List;
+
+/**
+ * Keyring defines the interface for wrapping data keys. A {@link MaterialsManager} will use
+ * keyrings to encrypt and decrypt data keys.
+ */
+public interface Keyring {
+ EncryptionMaterials onEncrypt(final EncryptionMaterials materials);
+ DecryptionMaterials onDecrypt(final DecryptionMaterials materials, final List encryptedDataKeys);
+}
diff --git a/src/main/java/software/amazon/encryption/s3/materials/MaterialsManager.java b/src/main/java/software/amazon/encryption/s3/materials/MaterialsManager.java
new file mode 100644
index 000000000..71277b563
--- /dev/null
+++ b/src/main/java/software/amazon/encryption/s3/materials/MaterialsManager.java
@@ -0,0 +1,6 @@
+package software.amazon.encryption.s3.materials;
+
+public interface MaterialsManager {
+ EncryptionMaterials getEncryptionMaterials(EncryptionMaterialsRequest request);
+ DecryptionMaterials decryptMaterials(DecryptMaterialsRequest request);
+}