diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..0b8f497e1
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/test/resources/aws-encryption-sdk-test-vectors"]
+ path = src/test/resources/aws-encryption-sdk-test-vectors
+ url = https://github.com/awslabs/private-aws-encryption-sdk-test-vectors-staging.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fde413990..c1d196c08 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 1.9.0 -- 2021-05-27
+
+* feat: Improvements to the message decryption process.
+
+ See https://github.com/aws/aws-encryption-sdk-java/security/advisories/GHSA-55xh-53m6-936r
+
## 1.7.0 -- 2020-09-24
* feat: Updates to the AWS Encryption SDK. bdb31dc
diff --git a/README.md b/README.md
index 07d6d0e82..7c71b5f8e 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ You can get the latest release from Maven:
com.amazonawsaws-encryption-sdk-java
- 1.7.0
+ 1.9.0
```
diff --git a/codebuild/corretto11.yml b/codebuild/corretto11.yml
index f6d492de7..e065929cf 100644
--- a/codebuild/corretto11.yml
+++ b/codebuild/corretto11.yml
@@ -6,4 +6,4 @@ phases:
java: corretto11
build:
commands:
- - mvn install -Dgpg.skip=true '-DtestVectorZip=https://github.com/awslabs/aws-encryption-sdk-test-vectors/raw/master/vectors/awses-decrypt/python-1.3.8.zip'
\ No newline at end of file
+ - mvn install -Dgpg.skip=true "-DtestVectorZip=file://$CODEBUILD_SRC_DIR/src/test/resources/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-2.2.0.zip"
diff --git a/codebuild/corretto8.yml b/codebuild/corretto8.yml
index 1124560f0..71e236f59 100644
--- a/codebuild/corretto8.yml
+++ b/codebuild/corretto8.yml
@@ -6,4 +6,4 @@ phases:
java: corretto8
build:
commands:
- - mvn install -Dgpg.skip=true '-DtestVectorZip=https://github.com/awslabs/aws-encryption-sdk-test-vectors/raw/master/vectors/awses-decrypt/python-1.3.8.zip'
\ No newline at end of file
+ - mvn install -Dgpg.skip=true "-DtestVectorZip=file://$CODEBUILD_SRC_DIR/src/test/resources/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-2.2.0.zip"
diff --git a/codebuild/openjdk11.yml b/codebuild/openjdk11.yml
index 03c8e648f..208f52a28 100644
--- a/codebuild/openjdk11.yml
+++ b/codebuild/openjdk11.yml
@@ -6,4 +6,4 @@ phases:
java: openjdk11
build:
commands:
- - mvn install -Dgpg.skip=true '-DtestVectorZip=https://github.com/awslabs/aws-encryption-sdk-test-vectors/raw/master/vectors/awses-decrypt/python-1.3.8.zip'
\ No newline at end of file
+ - mvn install -Dgpg.skip=true "-DtestVectorZip=file://$CODEBUILD_SRC_DIR/src/test/resources/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-2.2.0.zip"
diff --git a/codebuild/openjdk8.yml b/codebuild/openjdk8.yml
index 7b9be0751..e80b43dd5 100644
--- a/codebuild/openjdk8.yml
+++ b/codebuild/openjdk8.yml
@@ -6,4 +6,4 @@ phases:
java: openjdk8
build:
commands:
- - mvn install -Dgpg.skip=true '-DtestVectorZip=https://github.com/awslabs/aws-encryption-sdk-test-vectors/raw/master/vectors/awses-decrypt/python-1.3.8.zip'
\ No newline at end of file
+ - mvn install -Dgpg.skip=true "-DtestVectorZip=file://$CODEBUILD_SRC_DIR/src/test/resources/aws-encryption-sdk-test-vectors/vectors/awses-decrypt/python-2.2.0.zip"
diff --git a/pom.xml b/pom.xml
index ddf5df164..5ccb449df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.amazonawsaws-encryption-sdk-java
- 1.7.0
+ 1.9.0jaraws-encryption-sdk-java
@@ -60,9 +60,9 @@
- junit
- junit
- 4.13.1
+ org.junit.vintage
+ junit-vintage-engine
+ 5.7.1test
diff --git a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java
index 8f9ac0676..dbc3f1259 100644
--- a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java
+++ b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java
@@ -3,6 +3,8 @@
package com.amazonaws.crypto.examples;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.GeneralSecurityException;
@@ -125,10 +127,16 @@ private static void standardDecrypt(final String kmsArn, final String fileName)
// use an encryption context. For an example, see the other SDK samples.
final FileInputStream in = new FileInputStream(fileName + ".encrypted");
final FileOutputStream out = new FileOutputStream(fileName + ".decrypted");
- final CryptoOutputStream> decryptingStream = crypto.createDecryptingStream(provider, out);
+ // Since we are using a signing algorithm suite, we avoid streaming decryption directly to the output file,
+ // to ensure that the trailing signature is verified before writing any untrusted plaintext to disk.
+ final ByteArrayOutputStream plaintextBuffer = new ByteArrayOutputStream();
+ final CryptoOutputStream> decryptingStream = crypto.createDecryptingStream(provider, plaintextBuffer);
IOUtils.copy(in, decryptingStream);
in.close();
decryptingStream.close();
+ final ByteArrayInputStream plaintextReader = new ByteArrayInputStream(plaintextBuffer.toByteArray());
+ IOUtils.copy(plaintextReader, out);
+ out.close();
}
private static void escrowDecrypt(final String fileName) throws Exception {
diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java
index 37c6436c6..bcf637d95 100644
--- a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java
+++ b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java
@@ -14,6 +14,7 @@
import javax.crypto.spec.SecretKeySpec;
import com.amazonaws.encryptionsdk.AwsCrypto;
+import com.amazonaws.encryptionsdk.CryptoAlgorithm;
import com.amazonaws.encryptionsdk.CryptoInputStream;
import com.amazonaws.encryptionsdk.MasterKey;
import com.amazonaws.encryptionsdk.jce.JceMasterKey;
@@ -49,7 +50,12 @@ public static void main(String[] args) throws IOException {
// Instantiate the SDK with a specific commitment policy.
// ForbidEncryptAllowDecrypt is the only available policy in 1.7.0.
- final AwsCrypto crypto = AwsCrypto.builder().withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt).build();
+ // This also chooses to encrypt with an algorithm suite that doesn't include signing for faster decryption,
+ // since this use case assumes that the contexts that encrypt and decrypt are equally trusted.
+ final AwsCrypto crypto = AwsCrypto.builder()
+ .withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt)
+ .withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY)
+ .build();
// Create an encryption context to identify this ciphertext
Map context = Collections.singletonMap("Example", "FileStreaming");
@@ -65,14 +71,16 @@ public static void main(String[] args) throws IOException {
out.close();
// Decrypt the file. Verify the encryption context before returning the plaintext.
+ // Since we encrypted using an unsigned algorithm suite, we can use the recommended
+ // createUnsignedMessageDecryptingStream method that only accepts unsigned messages.
in = new FileInputStream(srcFile + ".encrypted");
- CryptoInputStream decryptingStream = crypto.createDecryptingStream(masterKey, in);
+ CryptoInputStream decryptingStream = crypto.createUnsignedMessageDecryptingStream(masterKey, in);
// Does it contain the expected encryption context?
if (!"FileStreaming".equals(decryptingStream.getCryptoResult().getEncryptionContext().get("Example"))) {
throw new IllegalStateException("Bad encryption context");
}
- // Return the plaintext data
+ // Write the plaintext data to disk.
out = new FileOutputStream(srcFile + ".decrypted");
IOUtils.copy(decryptingStream, out);
decryptingStream.close();
diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java
index baf37a150..a4fd6f6a3 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java
@@ -11,12 +11,8 @@
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.BadCiphertextException;
-import com.amazonaws.encryptionsdk.internal.DecryptionHandler;
-import com.amazonaws.encryptionsdk.internal.EncryptionHandler;
-import com.amazonaws.encryptionsdk.internal.LazyMessageCryptoHandler;
-import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler;
-import com.amazonaws.encryptionsdk.internal.ProcessingSummary;
-import com.amazonaws.encryptionsdk.internal.Utils;
+import com.amazonaws.encryptionsdk.internal.*;
+import com.amazonaws.encryptionsdk.model.CiphertextHeaders;
import com.amazonaws.encryptionsdk.model.EncryptionMaterials;
import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest;
@@ -88,6 +84,12 @@ public class AwsCrypto {
private final CommitmentPolicy commitmentPolicy_;
+ /**
+ * The maximum number of encrypted data keys to unwrap (resp. wrap) on decrypt (resp. encrypt), if positive.
+ * If zero, do not limit EDKs.
+ */
+ private final int maxEncryptedDataKeys_;
+
/**
* @deprecated This constructor implicitly configures the Aws Crypto client with a commitment policy that
* allows reading encrypted messages without commitment values.
@@ -97,6 +99,7 @@ public class AwsCrypto {
@Deprecated
public AwsCrypto() {
commitmentPolicy_ = CommitmentPolicy.ForbidEncryptAllowDecrypt;
+ maxEncryptedDataKeys_ = CiphertextHeaders.NO_MAX_ENCRYPTED_DATA_KEYS;
}
private AwsCrypto(Builder builder) {
@@ -113,15 +116,24 @@ private AwsCrypto(Builder builder) {
encryptionAlgorithm_ = builder.encryptionAlgorithm_;
encryptionFrameSize_ = builder.encryptionFrameSize_;
commitmentPolicy_ = builder.commitmentPolicy_;
+ maxEncryptedDataKeys_ = builder.maxEncryptedDataKeys_;
}
public static class Builder {
private CryptoAlgorithm encryptionAlgorithm_;
private int encryptionFrameSize_ = getDefaultFrameSize();
private CommitmentPolicy commitmentPolicy_;
+ private int maxEncryptedDataKeys_ = CiphertextHeaders.NO_MAX_ENCRYPTED_DATA_KEYS;
private Builder() {}
+ private Builder(final AwsCrypto client) {
+ encryptionAlgorithm_ = client.encryptionAlgorithm_;
+ encryptionFrameSize_ = client.encryptionFrameSize_;
+ commitmentPolicy_ = client.commitmentPolicy_;
+ maxEncryptedDataKeys_ = client.maxEncryptedDataKeys_;
+ }
+
/**
* Sets the {@link CryptoAlgorithm} to encrypt with.
* The Aws Crypto client will use the last crypto algorithm set with
@@ -161,6 +173,21 @@ public Builder withCommitmentPolicy(CommitmentPolicy commitmentPolicy) {
return this;
}
+ /**
+ * Sets the maximum number of encrypted data keys that this Aws Crypto client will wrap when
+ * encrypting, or unwrap when decrypting, a single message.
+ *
+ * @param maxEncryptedDataKeys The maximum number of encrypted data keys; must be positive
+ * @return The Builder, for method chaining
+ */
+ public Builder withMaxEncryptedDataKeys(int maxEncryptedDataKeys) {
+ if (maxEncryptedDataKeys < 1) {
+ throw new IllegalArgumentException("maxEncryptedDataKeys must be positive");
+ }
+ this.maxEncryptedDataKeys_ = maxEncryptedDataKeys;
+ return this;
+ }
+
public AwsCrypto build() {
return new AwsCrypto(this);
}
@@ -170,6 +197,10 @@ public static Builder builder() {
return new Builder();
}
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
/**
* Returns the {@link CryptoAlgorithm} to be used for encryption when none is explicitly
* selected. Currently it is {@link CryptoAlgorithm#ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384}.
@@ -329,9 +360,10 @@ public > CryptoResult encryptData(
.setPlaintext(plaintext)
.build();
+ EncryptionMaterials encryptionMaterials = checkMaxEncryptedDataKeys(checkAlgorithm(materialsManager.getMaterialsForEncrypt(request)));
final MessageCryptoHandler cryptoHandler = new EncryptionHandler(
getEncryptionFrameSize(),
- checkAlgorithm(materialsManager.getMaterialsForEncrypt(request))
+ encryptionMaterials
);
final int outSizeEstimate = cryptoHandler.estimateOutputSize(plaintext.length);
@@ -449,7 +481,7 @@ public > CryptoResult encryptString(final Mast
public > CryptoResult decryptData(final MasterKeyProvider provider,
final byte[] ciphertext) {
return decryptData(Utils.assertNonNull(provider, "provider"), new
- ParsedCiphertext(ciphertext));
+ ParsedCiphertext(ciphertext, maxEncryptedDataKeys_));
}
/**
@@ -465,7 +497,7 @@ public > CryptoResult decryptData(final Master
final byte[] ciphertext
) {
return decryptData(Utils.assertNonNull(materialsManager, "materialsManager"),
- new ParsedCiphertext(ciphertext));
+ new ParsedCiphertext(ciphertext, maxEncryptedDataKeys_));
}
/**
@@ -487,7 +519,10 @@ public > CryptoResult decryptData(
) {
Utils.assertNonNull(materialsManager, "materialsManager");
- final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager, ciphertext);
+ final MessageCryptoHandler cryptoHandler =
+ DecryptionHandler.create(materialsManager, ciphertext, commitmentPolicy_,
+ SignaturePolicy.AllowEncryptAllowDecrypt,
+ maxEncryptedDataKeys_);
final byte[] ciphertextBytes = ciphertext.getCiphertext();
final int contentLen = ciphertextBytes.length - ciphertext.getOffset();
@@ -669,16 +704,92 @@ public CryptoInputStream> createEncryptingStream(
return createEncryptingStream(materialsManager, is, EMPTY_MAP);
}
+ /**
+ * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the
+ * underlying {@link OutputStream}. This version only accepts unsigned messages.
+ *
+ * @see #decryptData(MasterKeyProvider, byte[])
+ * @see javax.crypto.CipherOutputStream
+ */
+ public > CryptoOutputStream createUnsignedMessageDecryptingStream(
+ final MasterKeyProvider provider, final OutputStream os) {
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptForbidDecrypt,
+ maxEncryptedDataKeys_);
+ return new CryptoOutputStream(os, cryptoHandler);
+ }
+
+ /**
+ * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the
+ * underlying {@link InputStream}. This version only accepts unsigned messages.
+ *
+ * @see #decryptData(MasterKeyProvider, byte[])
+ * @see javax.crypto.CipherInputStream
+ */
+ public > CryptoInputStream createUnsignedMessageDecryptingStream(
+ final MasterKeyProvider provider, final InputStream is) {
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptForbidDecrypt,
+ maxEncryptedDataKeys_);
+ return new CryptoInputStream(is, cryptoHandler);
+ }
+
+ /**
+ * Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the
+ * underlying {@link OutputStream}. This version only accepts unsigned messages.
+ *
+ * @see #decryptData(CryptoMaterialsManager, byte[])
+ * @see javax.crypto.CipherOutputStream
+ */
+ public CryptoOutputStream> createUnsignedMessageDecryptingStream(
+ final CryptoMaterialsManager materialsManager, final OutputStream os
+ ) {
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptForbidDecrypt,
+ maxEncryptedDataKeys_);
+ return new CryptoOutputStream(os, cryptoHandler);
+ }
+
+ /**
+ * Returns a {@link CryptoInputStream} which decrypts the data after reading it from the
+ * underlying {@link InputStream}. This version only accepts unsigned messages.
+ *
+ * @see #encryptData(CryptoMaterialsManager, byte[], Map)
+ * @see javax.crypto.CipherInputStream
+ */
+ public CryptoInputStream> createUnsignedMessageDecryptingStream(
+ final CryptoMaterialsManager materialsManager, final InputStream is
+ ) {
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptForbidDecrypt,
+ maxEncryptedDataKeys_);
+ return new CryptoInputStream(is, cryptoHandler);
+ }
+
/**
* Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the
* underlying {@link OutputStream}.
+ *
+ * Note that if the encrypted message includes a trailing signature, by necessity it cannot be verified until
+ * after the decrypted plaintext has been released to the underlying {@link OutputStream}! This behavior can
+ * be avoided by using the non-streaming #decryptData(MasterKeyProvider, byte[]) method instead, or
+ * #createUnsignedMessageDecryptingStream(MasterKeyProvider, OutputStream) if you do not need to decrypt
+ * signed messages.
*
* @see #decryptData(MasterKeyProvider, byte[])
+ * @see #createUnsignedMessageDecryptingStream(MasterKeyProvider, OutputStream)
* @see javax.crypto.CipherOutputStream
*/
public > CryptoOutputStream createDecryptingStream(
final MasterKeyProvider provider, final OutputStream os) {
- final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider);
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptAllowDecrypt,
+ maxEncryptedDataKeys_);
return new CryptoOutputStream(os, cryptoHandler);
}
@@ -686,12 +797,22 @@ public > CryptoOutputStream createDecryptingStream(
* Returns a {@link CryptoInputStream} which decrypts the data after reading it from the
* underlying {@link InputStream}.
*
+ * Note that if the encrypted message includes a trailing signature, by necessity it cannot be verified until
+ * after the decrypted plaintext has been produced from the {@link InputStream}! This behavior can
+ * be avoided by using the non-streaming #decryptData(MasterKeyProvider, byte[]) method instead, or
+ * #createUnsignedMessageDecryptingStream(MasterKeyProvider, InputStream) if you do not need to decrypt
+ * signed messages.
+ *
* @see #decryptData(MasterKeyProvider, byte[])
+ * @see #createUnsignedMessageDecryptingStream(MasterKeyProvider, InputStream)
* @see javax.crypto.CipherInputStream
*/
public > CryptoInputStream createDecryptingStream(
final MasterKeyProvider provider, final InputStream is) {
- final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider);
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptAllowDecrypt,
+ maxEncryptedDataKeys_);
return new CryptoInputStream(is, cryptoHandler);
}
@@ -699,13 +820,23 @@ public > CryptoInputStream createDecryptingStream(
* Returns a {@link CryptoOutputStream} which decrypts the data prior to passing it onto the
* underlying {@link OutputStream}.
*
+ * Note that if the encrypted message includes a trailing signature, by necessity it cannot be verified until
+ * after the decrypted plaintext has been released to the underlying {@link OutputStream}! This behavior can
+ * be avoided by using the non-streaming #decryptData(CryptoMaterialsManager, byte[]) method instead, or
+ * #createUnsignedMessageDecryptingStream(CryptoMaterialsManager, OutputStream) if you do not need to decrypt
+ * signed messages.
+ *
* @see #decryptData(CryptoMaterialsManager, byte[])
+ * @see #createUnsignedMessageDecryptingStream(CryptoMaterialsManager, OutputStream)
* @see javax.crypto.CipherOutputStream
*/
public CryptoOutputStream> createDecryptingStream(
final CryptoMaterialsManager materialsManager, final OutputStream os
) {
- final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager);
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptAllowDecrypt,
+ maxEncryptedDataKeys_);
return new CryptoOutputStream(os, cryptoHandler);
}
@@ -713,13 +844,23 @@ public CryptoOutputStream> createDecryptingStream(
* Returns a {@link CryptoInputStream} which decrypts the data after reading it from the
* underlying {@link InputStream}.
*
- * @see #encryptData(CryptoMaterialsManager, byte[], Map)
+ * Note that if the encrypted message includes a trailing signature, by necessity it cannot be verified until
+ * after the decrypted plaintext has been produced from the {@link InputStream}! This behavior can
+ * be avoided by using the non-streaming #decryptData(CryptoMaterialsManager, byte[]) method instead, or
+ * #createUnsignedMessageDecryptingStream(CryptoMaterialsManager, InputStream) if you do not need to decrypt
+ * signed messages.
+ *
+ * @see #decryptData(CryptoMaterialsManager, byte[])
+ * @see #createUnsignedMessageDecryptingStream(CryptoMaterialsManager, InputStream)
* @see javax.crypto.CipherInputStream
*/
public CryptoInputStream> createDecryptingStream(
final CryptoMaterialsManager materialsManager, final InputStream is
) {
- final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager);
+ final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager,
+ commitmentPolicy_,
+ SignaturePolicy.AllowEncryptAllowDecrypt,
+ maxEncryptedDataKeys_);
return new CryptoInputStream(is, cryptoHandler);
}
@@ -741,7 +882,7 @@ private MessageCryptoHandler getEncryptingStreamHandler(
return new EncryptionHandler(
getEncryptionFrameSize(),
- checkAlgorithm(materialsManager.getMaterialsForEncrypt(requestBuilder.build()))
+ checkMaxEncryptedDataKeys(checkAlgorithm(materialsManager.getMaterialsForEncrypt(requestBuilder.build())))
);
});
}
@@ -756,4 +897,11 @@ private EncryptionMaterials checkAlgorithm(EncryptionMaterials result) {
return result;
}
+
+ private EncryptionMaterials checkMaxEncryptedDataKeys(EncryptionMaterials materials) {
+ if (maxEncryptedDataKeys_ > 0 && materials.getEncryptedDataKeys().size() > maxEncryptedDataKeys_) {
+ throw new AwsCryptoException("Encrypted data keys exceed maxEncryptedDataKeys");
+ }
+ return materials;
+ }
}
diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java
index 935e4fbc0..c71dca1e9 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java
@@ -230,6 +230,8 @@ public int available() throws IOException {
*
* If the input size set here is exceeded, an exception will be thrown, and the encyption or decryption will fail.
*
+ * If this method is called multiple times, the smallest bound will be used.
+ *
* @param size Maximum input size.
*/
public void setMaxInputLength(long size) {
diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java
index de17f4a2f..785403152 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java
@@ -161,6 +161,8 @@ public void close() throws IOException, BadCiphertextException {
*
* If the size set here is exceeded, an exception will be thrown, and the encyption or decryption will fail.
*
+ * If this method is called multiple times, the smallest bound will be used.
+ *
* @param size Maximum input size.
*/
public void setMaxInputLength(long size) {
diff --git a/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java b/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java
index 7fa1584fb..eda7d95b8 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java
@@ -133,6 +133,8 @@ public DefaultCryptoMaterialsManager(MasterKeyProvider> mkp) {
} catch (final IllegalStateException ex) {
throw new AwsCryptoException(ex);
}
+ } else if (request.getEncryptionContext().containsKey(Constants.EC_PUBLIC_KEY_FIELD)) {
+ throw new AwsCryptoException("Trailing signature public key found for non-signed algorithm");
}
return DecryptionMaterials.newBuilder()
diff --git a/src/main/java/com/amazonaws/encryptionsdk/ParsedCiphertext.java b/src/main/java/com/amazonaws/encryptionsdk/ParsedCiphertext.java
index 4be303563..13f0926b1 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/ParsedCiphertext.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/ParsedCiphertext.java
@@ -33,15 +33,28 @@ public class ParsedCiphertext extends CiphertextHeaders {
* Parses {@code ciphertext}. Please note that this does not make a defensive copy of
* {@code ciphertext} and that any changes made to the backing array will be reflected here as
* well.
+ *
+ * @param ciphertext The ciphertext to parse
+ * @param maxEncryptedDataKeys The maximum number of encrypted data keys to parse.
+ * Zero indicates no maximum.
*/
- public ParsedCiphertext(final byte[] ciphertext) {
+ public ParsedCiphertext(final byte[] ciphertext, final int maxEncryptedDataKeys) {
ciphertext_ = Utils.assertNonNull(ciphertext, "ciphertext");
- offset_ = deserialize(ciphertext_, 0);
+ offset_ = deserialize(ciphertext_, 0, maxEncryptedDataKeys);
if (!this.isComplete()) {
throw new BadCiphertextException("Incomplete ciphertext.");
}
}
+ /**
+ * Parses {@code ciphertext} without enforcing a max EDK count. Please note that this does
+ * not make a defensive copy of {@code ciphertext} and that any changes made to the
+ * backing array will be reflected here as well.
+ */
+ public ParsedCiphertext(final byte[] ciphertext) {
+ this(ciphertext, CiphertextHeaders.NO_MAX_ENCRYPTED_DATA_KEYS);
+ }
+
/**
* Returns the raw ciphertext backing this object. This is not a defensive copy and so
* must not be modified by callers.
diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java
index 8caca55a7..fadba1905 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java
@@ -16,12 +16,7 @@
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
-import com.amazonaws.encryptionsdk.CryptoAlgorithm;
-import com.amazonaws.encryptionsdk.CryptoMaterialsManager;
-import com.amazonaws.encryptionsdk.DataKey;
-import com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager;
-import com.amazonaws.encryptionsdk.MasterKey;
-import com.amazonaws.encryptionsdk.MasterKeyProvider;
+import com.amazonaws.encryptionsdk.*;
import com.amazonaws.encryptionsdk.exception.AwsCryptoException;
import com.amazonaws.encryptionsdk.exception.BadCiphertextException;
import com.amazonaws.encryptionsdk.model.CiphertextFooters;
@@ -44,6 +39,13 @@
*/
public class DecryptionHandler> implements MessageCryptoHandler {
private final CryptoMaterialsManager materialsManager_;
+ /**
+ * The maximum number of encrypted data keys to parse, if positive.
+ * If zero, do not limit EDKs.
+ */
+ private final int maxEncryptedDataKeys_;
+ private final CommitmentPolicy commitmentPolicy_;
+ private final SignaturePolicy signaturePolicy_;
private final CiphertextHeaders ciphertextHeaders_;
private final CiphertextFooters ciphertextFooters_;
@@ -67,22 +69,43 @@ public class DecryptionHandler> implements MessageCryptoH
// These ctors are private to ensure type safety - we must ensure construction using a CMM results in a
// DecryptionHandler>, not a DecryptionHandler, since the CryptoMaterialsManager is not itself
// genericized.
- private DecryptionHandler(final CryptoMaterialsManager materialsManager) {
+ private DecryptionHandler(final CryptoMaterialsManager materialsManager, final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy, final int maxEncryptedDataKeys) {
Utils.assertNonNull(materialsManager, "materialsManager");
+ Utils.assertNonNull(commitmentPolicy, "commitmentPolicy");
+ Utils.assertNonNull(signaturePolicy, "signaturePolicy");
this.materialsManager_ = materialsManager;
+ this.maxEncryptedDataKeys_ = maxEncryptedDataKeys;
+ this.commitmentPolicy_ = commitmentPolicy;
+ this.signaturePolicy_ = signaturePolicy;
ciphertextHeaders_ = new CiphertextHeaders();
ciphertextFooters_ = new CiphertextFooters();
}
- private DecryptionHandler(final CryptoMaterialsManager materialsManager, final CiphertextHeaders headers)
+ private DecryptionHandler(final CryptoMaterialsManager materialsManager, final CiphertextHeaders headers,
+ final CommitmentPolicy commitmentPolicy, final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys)
throws AwsCryptoException
{
Utils.assertNonNull(materialsManager, "materialsManager");
+ Utils.assertNonNull(commitmentPolicy, "commitmentPolicy");
+ Utils.assertNonNull(signaturePolicy, "signaturePolicy");
materialsManager_ = materialsManager;
ciphertextHeaders_ = headers;
+ maxEncryptedDataKeys_ = maxEncryptedDataKeys;
+ commitmentPolicy_ = commitmentPolicy;
+ signaturePolicy_ = signaturePolicy;
ciphertextFooters_ = new CiphertextFooters();
+ if (headers instanceof ParsedCiphertext) {
+ ciphertextBytesSupplied_ = ((ParsedCiphertext)headers).getOffset();
+ } else {
+ // This is a little more expensive, hence the public create(...) methods
+ // that take a CiphertextHeaders instead of a ParsedCiphertext are
+ // deprecated.
+ ciphertextBytesSupplied_ = headers.toByteArray().length;
+ }
readHeaderFields(headers);
updateTrailingSignature(headers);
}
@@ -97,16 +120,62 @@ private DecryptionHandler(final CryptoMaterialsManager materialsManager, final C
* @param customerMasterKeyProvider
* the master key provider to use in picking a master key from
* the key blobs encoded in the provided ciphertext.
+ * @param commitmentPolicy The commitment policy to enforce during decryption
+ * @param signaturePolicy The signature policy to enforce during decryption
+ * @param maxEncryptedDataKeys
+ * The maximum number of encrypted data keys to unwrap during decryption; zero indicates no maximum
* @throws AwsCryptoException
* if the master key is null.
*/
@SuppressWarnings("unchecked")
public static > DecryptionHandler create(
- final MasterKeyProvider customerMasterKeyProvider
+ final MasterKeyProvider customerMasterKeyProvider,
+ final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys
+ ) throws AwsCryptoException {
+ Utils.assertNonNull(customerMasterKeyProvider, "customerMasterKeyProvider");
+
+ return (DecryptionHandler)create(new DefaultCryptoMaterialsManager(customerMasterKeyProvider),
+ commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
+ }
+
+ /**
+ * Create a decryption handler using the provided master key and already parsed {@code headers}.
+ *
+ *
+ * Note the methods in the provided master key are used in decrypting the encrypted data key
+ * parsed from the ciphertext headers.
+ *
+ * @param customerMasterKeyProvider
+ * the master key provider to use in picking a master key from the key blobs encoded
+ * in the provided ciphertext.
+ * @param headers
+ * already parsed headers which will not be passed into
+ * {@link #processBytes(byte[], int, int, byte[], int)}
+ * @param commitmentPolicy The commitment policy to enforce during decryption
+ * @param signaturePolicy The signature policy to enforce during decryption
+ * @param maxEncryptedDataKeys
+ * The maximum number of encrypted data keys to unwrap during decryption; zero indicates no maximum
+ * @throws AwsCryptoException
+ * if the master key is null.
+ * @deprecated This version may have to recalculate the number of bytes already parsed, which adds
+ * a performance penalty. Use {@link #create(CryptoMaterialsManager, ParsedCiphertext,
+ * CommitmentPolicy, SignaturePolicy, int)} instead, which makes the parsed byte count directly
+ * available instead.
+ */
+ @SuppressWarnings("unchecked")
+ @Deprecated
+ public static > DecryptionHandler create(
+ final MasterKeyProvider customerMasterKeyProvider, final CiphertextHeaders headers,
+ final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys
) throws AwsCryptoException {
Utils.assertNonNull(customerMasterKeyProvider, "customerMasterKeyProvider");
- return (DecryptionHandler)create(new DefaultCryptoMaterialsManager(customerMasterKeyProvider));
+ return (DecryptionHandler) create(new DefaultCryptoMaterialsManager(customerMasterKeyProvider), headers,
+ commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
@@ -122,16 +191,24 @@ public static > DecryptionHandler create(
* @param headers
* already parsed headers which will not be passed into
* {@link #processBytes(byte[], int, int, byte[], int)}
+ * @param commitmentPolicy The commitment policy to enforce during decryption
+ * @param signaturePolicy The signature policy to enforce during decryption
+ * @param maxEncryptedDataKeys
+ * The maximum number of encrypted data keys to unwrap during decryption; zero indicates no maximum
* @throws AwsCryptoException
* if the master key is null.
*/
@SuppressWarnings("unchecked")
public static > DecryptionHandler create(
- final MasterKeyProvider customerMasterKeyProvider, final CiphertextHeaders headers
+ final MasterKeyProvider customerMasterKeyProvider, final ParsedCiphertext headers,
+ final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys
) throws AwsCryptoException {
Utils.assertNonNull(customerMasterKeyProvider, "customerMasterKeyProvider");
- return (DecryptionHandler) create(new DefaultCryptoMaterialsManager(customerMasterKeyProvider), headers);
+ return (DecryptionHandler) create(new DefaultCryptoMaterialsManager(customerMasterKeyProvider), headers,
+ commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
@@ -144,13 +221,54 @@ public static > DecryptionHandler create(
* @param materialsManager
* the materials manager to use in decrypting the data key from the key blobs encoded
* in the provided ciphertext.
+ * @param commitmentPolicy The commitment policy to enforce during decryption
+ * @param signaturePolicy The signature policy to enforce during decryption
+ * @param maxEncryptedDataKeys
+ * The maximum number of encrypted data keys to unwrap during decryption; zero indicates no maximum
+ * @throws AwsCryptoException
+ * if the master key is null.
+ */
+ public static DecryptionHandler> create(
+ final CryptoMaterialsManager materialsManager,
+ final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys
+ ) throws AwsCryptoException {
+ return new DecryptionHandler(materialsManager, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
+ }
+
+ /**
+ * Create a decryption handler using the provided materials manager and already parsed {@code headers}.
+ *
+ *
+ * Note the methods in the provided materials manager are used in decrypting the encrypted data key
+ * parsed from the ciphertext headers.
+ *
+ * @param materialsManager
+ * the materials manager to use in decrypting the data key from the key blobs encoded
+ * in the provided ciphertext.
+ * @param headers
+ * already parsed headers which will not be passed into
+ * {@link #processBytes(byte[], int, int, byte[], int)}
+ * @param commitmentPolicy The commitment policy to enforce during decryption
+ * @param signaturePolicy The signature policy to enforce during decryption
+ * @param maxEncryptedDataKeys
+ * The maximum number of encrypted data keys to unwrap during decryption; zero indicates no maximum
* @throws AwsCryptoException
* if the master key is null.
+ * @deprecated This version may have to recalculate the number of bytes already parsed, which adds
+ * a performance penalty. Use {@link #create(CryptoMaterialsManager, ParsedCiphertext,
+ * CommitmentPolicy, SignaturePolicy, int)} instead, which makes the parsed byte count directly
+ * available instead.
*/
+ @Deprecated
public static DecryptionHandler> create(
- final CryptoMaterialsManager materialsManager
+ final CryptoMaterialsManager materialsManager, final CiphertextHeaders headers,
+ final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys
) throws AwsCryptoException {
- return new DecryptionHandler(materialsManager);
+ return new DecryptionHandler(materialsManager, headers, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
@@ -166,13 +284,20 @@ public static DecryptionHandler> create(
* @param headers
* already parsed headers which will not be passed into
* {@link #processBytes(byte[], int, int, byte[], int)}
+ * @param commitmentPolicy The commitment policy to enforce during decryption
+ * @param signaturePolicy The signature policy to enforce during decryption
+ * @param maxEncryptedDataKeys
+ * The maximum number of encrypted data keys to unwrap during decryption; zero indicates no maximum
* @throws AwsCryptoException
* if the master key is null.
*/
public static DecryptionHandler> create(
- final CryptoMaterialsManager materialsManager, final CiphertextHeaders headers
+ final CryptoMaterialsManager materialsManager, final ParsedCiphertext headers,
+ final CommitmentPolicy commitmentPolicy,
+ final SignaturePolicy signaturePolicy,
+ final int maxEncryptedDataKeys
) throws AwsCryptoException {
- return new DecryptionHandler(materialsManager, headers);
+ return new DecryptionHandler(materialsManager, headers, commitmentPolicy, signaturePolicy, maxEncryptedDataKeys);
}
/**
@@ -208,6 +333,15 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int
final int outOff)
throws BadCiphertextException, AwsCryptoException {
+ // We should arguably check if we are already complete_ here as other handlers
+ // like FrameDecryptionHandler and BlockDecryptionHandler do.
+ // However, adding that now could potentially break customers who have extra trailing
+ // bytes in their decryption streams.
+ // The handlers are also inconsistent in general with this check. Even those that
+ // do raise an exception here if already complete will not complain if
+ // a single call to processBytes() completes the message and provides extra trailing bytes:
+ // in that case they will just indicate that they didn't process the extra bytes instead.
+
if (len < 0 || off < 0) {
throw new AwsCryptoException(String.format(
"Invalid values for input offset: %d and length: %d", off, len));
@@ -235,8 +369,8 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int
System.arraycopy(in, off, bytesToParse, unparsedBytes_.length, len);
int totalParsedBytes = 0;
- if (ciphertextHeadersParsed_ == false) {
- totalParsedBytes += ciphertextHeaders_.deserialize(bytesToParse, 0);
+ if (!ciphertextHeadersParsed_) {
+ totalParsedBytes += ciphertextHeaders_.deserialize(bytesToParse, 0, maxEncryptedDataKeys_);
// When ciphertext headers are complete, we have the data
// key and cipher mode to initialize the underlying cipher
if (ciphertextHeaders_.isComplete() == true) {
@@ -265,7 +399,6 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int
updateTrailingSignature(bytesToParse, totalParsedBytes, contentResult.getBytesProcessed());
actualOutLen = contentResult.getBytesWritten();
totalParsedBytes += contentResult.getBytesProcessed();
-
}
if (contentCryptoHandler_.isComplete()) {
actualOutLen += contentCryptoHandler_.doFinal(out, outOff + actualOutLen);
@@ -277,7 +410,13 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int
// the footer of the message.
if (cryptoAlgo_.getTrailingSignatureLength() > 0) {
totalParsedBytes += ciphertextFooters_.deserialize(bytesToParse, totalParsedBytes);
- if (ciphertextFooters_.isComplete() && trailingSig_ != null) {
+ if (ciphertextFooters_.isComplete()) {
+ // reset unparsed bytes as parsing of the ciphertext footer is
+ // complete.
+ // This isn't strictly necessary since processing any further data
+ // should be an error.
+ unparsedBytes_ = new byte[0];
+
try {
if (!trailingSig_.verify(ciphertextFooters_.getMAuth())) {
throw new BadCiphertextException("Bad trailing signature");
@@ -286,6 +425,12 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int
throw new BadCiphertextException("Bad trailing signature", ex);
}
complete_ = true;
+ } else {
+ // If there aren't enough bytes to parse the ciphertext
+ // footer, we don't have any more bytes to continue parsing.
+ // But first copy the leftover bytes to unparsed bytes.
+ unparsedBytes_ = Arrays.copyOfRange(bytesToParse, totalParsedBytes, bytesToParse.length);
+ return new ProcessingSummary(actualOutLen, len);
}
} else {
complete_ = true;
@@ -308,15 +453,20 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int
*/
@Override
public int doFinal(final byte[] out, final int outOff) throws BadCiphertextException {
+ // This is an unfortunate special case we have to support for backwards-compatibility.
+ if (ciphertextBytesSupplied_ == 0) {
+ return 0;
+ }
+
// check if cryptohandler for content has been created. There are cases
// when it might not have been created such as when doFinal() is called
// before the ciphertext headers are fully received and parsed.
if (contentCryptoHandler_ == null) {
- return 0;
+ throw new BadCiphertextException("Unable to process entire ciphertext.");
} else {
int result = contentCryptoHandler_.doFinal(out, outOff);
- if (!ciphertextHeaders_.isComplete() || !contentCryptoHandler_.isComplete()) {
+ if (!complete_) {
throw new BadCiphertextException("Unable to process entire ciphertext.");
}
@@ -385,7 +535,7 @@ public void setMaxInputLength(long size) {
throw Utils.cannotBeNegative("Max input length");
}
- if (ciphertextSizeBound_ != -1 && ciphertextSizeBound_ < size) {
+ if (ciphertextSizeBound_ == -1 || ciphertextSizeBound_ > size) {
ciphertextSizeBound_ = size;
}
@@ -393,6 +543,10 @@ public void setMaxInputLength(long size) {
checkSizeBound(0);
}
+ long getMaxInputLength() {
+ return ciphertextSizeBound_;
+ }
+
/**
* Check integrity of the header bytes by processing the parsed MAC tag in
* the headers through the cipher.
@@ -434,6 +588,17 @@ private void readHeaderFields(final CiphertextHeaders ciphertextHeaders) {
final byte[] messageId = ciphertextHeaders.getMessageId();
+ if (maxEncryptedDataKeys_ > 0 && ciphertextHeaders_.getEncryptedKeyBlobCount() > maxEncryptedDataKeys_) {
+ throw new AwsCryptoException("Ciphertext encrypted data keys exceed maxEncryptedDataKeys");
+ }
+
+ if (!signaturePolicy_.algorithmAllowedForDecrypt(cryptoAlgo_)) {
+ throw new AwsCryptoException("Configuration conflict. " +
+ "Cannot decrypt message with ID " + messageId + " because AwsCrypto.createUnsignedMessageDecryptingStream() " +
+ " accepts only unsigned messages. Algorithm ID was " +
+ cryptoAlgo_ + ".");
+ }
+
encryptionContext_ = ciphertextHeaders.getEncryptionContextMap();
DecryptionMaterialsRequest request = DecryptionMaterialsRequest.newBuilder()
diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java
index 33ab48237..5b7cd983f 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java
@@ -354,6 +354,10 @@ private void checkPlaintextSizeLimit(long additionalBytes) {
}
}
+ long getMaxInputLength() {
+ return plaintextByteLimit_;
+ }
+
/**
* Compute the MAC tag of the header bytes using the provided key, nonce, AAD, and crypto
* algorithm identifier.
diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/SignaturePolicy.java b/src/main/java/com/amazonaws/encryptionsdk/internal/SignaturePolicy.java
new file mode 100644
index 000000000..767cee6d9
--- /dev/null
+++ b/src/main/java/com/amazonaws/encryptionsdk/internal/SignaturePolicy.java
@@ -0,0 +1,20 @@
+package com.amazonaws.encryptionsdk.internal;
+
+import com.amazonaws.encryptionsdk.CryptoAlgorithm;
+
+public enum SignaturePolicy {
+ AllowEncryptAllowDecrypt {
+ @Override
+ public boolean algorithmAllowedForDecrypt(CryptoAlgorithm algorithm) {
+ return true;
+ }
+ },
+ AllowEncryptForbidDecrypt {
+ @Override
+ public boolean algorithmAllowedForDecrypt(CryptoAlgorithm algorithm) {
+ return algorithm.getTrailingSignatureLength() == 0;
+ }
+ };
+
+ public abstract boolean algorithmAllowedForDecrypt(CryptoAlgorithm algorithm);
+}
diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java b/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java
index 38a16e49c..82aa224f8 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/internal/VersionInfo.java
@@ -20,7 +20,7 @@ public class VersionInfo {
// incremented for major changes to the implementation
public static final String MAJOR_REVISION_NUM = "1";
// incremented for minor changes to the implementation
- public static final String MINOR_REVISION_NUM = "7";
+ public static final String MINOR_REVISION_NUM = "9";
// incremented for releases containing an immediate bug fix.
public static final String BUGFIX_REVISION_NUM = "0";
diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java b/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java
index fa7dbcd16..c992f9f10 100644
--- a/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java
+++ b/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java
@@ -35,6 +35,11 @@
* values supplied in the last two fields of the header.
*/
public class CiphertextHeaders {
+ /**
+ * When passed as maxEncryptedDataKeys, indicates that no maximum should be enforced (i.e., any number of EDKs are allowed).
+ */
+ public static final int NO_MAX_ENCRYPTED_DATA_KEYS = 0;
+
private static final SecureRandom RND = new SecureRandom();
private byte version_ = -1;
private byte typeVal_; // don't set this to -1 since Java byte is signed
@@ -59,6 +64,7 @@ public class CiphertextHeaders {
// internal variables
private int currKeyBlobIndex_ = 0;
private boolean isComplete_;
+ private int maxEncryptedDataKeys_ = NO_MAX_ENCRYPTED_DATA_KEYS;
/**
* Default constructor.
@@ -339,6 +345,9 @@ private int parseEncryptedDataKeyCount(final byte[] b, final int off) throws Par
if (cipherKeyCount_ < 0) {
throw new BadCiphertextException("Invalid cipher key count in ciphertext");
}
+ if (maxEncryptedDataKeys_ > 0 && cipherKeyCount_ > maxEncryptedDataKeys_) {
+ throw new AwsCryptoException("Ciphertext encrypted data keys exceed maxEncryptedDataKeys");
+ }
cipherKeyBlobs_ = Arrays.asList(new KeyBlob[cipherKeyCount_]);
return Short.SIZE / Byte.SIZE;
}
@@ -523,14 +532,18 @@ private int parseComplete(final byte[] b, final int off) throws ParseException {
* the byte array to deserialize.
* @param off
* the offset in the byte array to use for deserialization.
+ * @param maxEncryptedDataKeys
+ * the maximum number of EDKs to deserialize; zero indicates no maximum
* @return
* the number of bytes consumed in deserialization.
*/
- public int deserialize(final byte[] b, final int off) throws ParseException {
+ public int deserialize(final byte[] b, final int off, int maxEncryptedDataKeys) throws ParseException {
if (b == null) {
return 0;
}
+ maxEncryptedDataKeys_ = maxEncryptedDataKeys;
+
int parsedBytes = 0;
try {
parsedBytes += parseVersion(b, off + parsedBytes);
diff --git a/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java b/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java
index dc424c42e..ee69bce58 100644
--- a/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java
+++ b/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java
@@ -23,13 +23,17 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.amazonaws.encryptionsdk.jce.JceMasterKey;
+import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory;
+import com.amazonaws.util.IOUtils;
import org.junit.Before;
import org.junit.Test;
@@ -49,7 +53,10 @@
public class AwsCryptoTest {
private StaticMasterKey masterKeyProvider;
private AwsCrypto encryptionClient_;
+ private AwsCrypto noMaxEdksClient_;
+ private AwsCrypto maxEdksClient_;
private static final CommitmentPolicy commitmentPolicy = TestUtils.DEFAULT_TEST_COMMITMENT_POLICY;
+ private static final int MESSAGE_FORMAT_MAX_EDKS = (1 << 16) - 1;
@Before
public void init() {
@@ -57,6 +64,18 @@ public void init() {
encryptionClient_ = AwsCrypto.builder().withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt).build();
encryptionClient_.setEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256);
+
+ noMaxEdksClient_ = AwsCrypto
+ .builder()
+ .withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt)
+ .withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256)
+ .build();
+ maxEdksClient_ = AwsCrypto
+ .builder()
+ .withMaxEncryptedDataKeys(3)
+ .withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt)
+ .withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256)
+ .build();
}
private void doEncryptDecrypt(final CryptoAlgorithm cryptoAlg, final int byteSize, final int frameSize) {
@@ -950,4 +969,147 @@ public void legacyConstructAndSetCommittingCryptoAlgorithm() throws IOException
AwsCrypto client = new AwsCrypto();
client.setEncryptionAlgorithm(setCryptoAlgorithm);
}
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setNegativeMaxEdks() {
+ AwsCrypto.builder().withMaxEncryptedDataKeys(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void setZeroMaxEdks() {
+ AwsCrypto.builder().withMaxEncryptedDataKeys(0);
+ }
+
+ @Test
+ public void setValidMaxEdks() {
+ for (final int i : new int[]{1, 10, MESSAGE_FORMAT_MAX_EDKS, MESSAGE_FORMAT_MAX_EDKS + 1, Integer.MAX_VALUE}) {
+ AwsCrypto.builder().withMaxEncryptedDataKeys(i);
+ }
+ }
+
+ private MasterKeyProvider> providerWithEdks(int numEdks) {
+ List> providers = new ArrayList<>();
+ for (int i = 0; i < numEdks; i++) {
+ providers.add(masterKeyProvider);
+ }
+ return MultipleProviderFactory.buildMultiProvider(providers);
+ }
+
+ @Test
+ public void encryptDecryptWithLessThanMaxEdks() {
+ MasterKeyProvider> provider = providerWithEdks(2);
+ CryptoResult result = maxEdksClient_.encryptData(provider, new byte[] {1});
+ ParsedCiphertext ciphertext = new ParsedCiphertext(result.getResult());
+ assertEquals(ciphertext.getEncryptedKeyBlobCount(), 2);
+ maxEdksClient_.decryptData(provider, ciphertext);
+ }
+
+ @Test
+ public void encryptDecryptWithMaxEdks() {
+ MasterKeyProvider> provider = providerWithEdks(3);
+ CryptoResult result = maxEdksClient_.encryptData(provider, new byte[] {1});
+ ParsedCiphertext ciphertext = new ParsedCiphertext(result.getResult());
+ assertEquals(ciphertext.getEncryptedKeyBlobCount(), 3);
+ maxEdksClient_.decryptData(provider, ciphertext);
+ }
+
+ @Test
+ public void noEncryptWithMoreThanMaxEdks() {
+ MasterKeyProvider> provider = providerWithEdks(4);
+ assertThrows(AwsCryptoException.class, "Encrypted data keys exceed maxEncryptedDataKeys", () ->
+ maxEdksClient_.encryptData(provider, new byte[] {1}));
+ }
+
+ @Test
+ public void noDecryptWithMoreThanMaxEdks() {
+ MasterKeyProvider> provider = providerWithEdks(4);
+ CryptoResult result = noMaxEdksClient_.encryptData(provider, new byte[] {1});
+ ParsedCiphertext ciphertext = new ParsedCiphertext(result.getResult());
+ assertThrows(AwsCryptoException.class, "Ciphertext encrypted data keys exceed maxEncryptedDataKeys", () ->
+ maxEdksClient_.decryptData(provider, ciphertext));
+ }
+
+ @Test
+ public void encryptDecryptWithNoMaxEdks() {
+ MasterKeyProvider> provider = providerWithEdks(MESSAGE_FORMAT_MAX_EDKS);
+ CryptoResult result = noMaxEdksClient_.encryptData(provider, new byte[] {1});
+ ParsedCiphertext ciphertext = new ParsedCiphertext(result.getResult());
+ assertEquals(ciphertext.getEncryptedKeyBlobCount(), MESSAGE_FORMAT_MAX_EDKS);
+ noMaxEdksClient_.decryptData(provider, ciphertext);
+ }
+
+ @Test
+ public void encryptDecryptStreamWithLessThanMaxEdks() throws IOException {
+ MasterKeyProvider> provider = providerWithEdks(2);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ CryptoOutputStream> encryptStream = maxEdksClient_.createEncryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(new byte[] {1}), encryptStream);
+ encryptStream.close();
+
+ byte[] ciphertext = byteArrayOutputStream.toByteArray();
+ assertEquals(new ParsedCiphertext(ciphertext).getEncryptedKeyBlobCount(), 2);
+
+ byteArrayOutputStream.reset();
+ CryptoOutputStream> decryptStream = maxEdksClient_.createDecryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(ciphertext), decryptStream);
+ decryptStream.close();
+ }
+
+ @Test
+ public void encryptDecryptStreamWithMaxEdks() throws IOException {
+ MasterKeyProvider> provider = providerWithEdks(3);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ CryptoOutputStream> encryptStream = maxEdksClient_.createEncryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(new byte[] {1}), encryptStream);
+ encryptStream.close();
+
+ byte[] ciphertext = byteArrayOutputStream.toByteArray();
+ assertEquals(new ParsedCiphertext(ciphertext).getEncryptedKeyBlobCount(), 3);
+
+ byteArrayOutputStream.reset();
+ CryptoOutputStream> decryptStream = maxEdksClient_.createDecryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(ciphertext), decryptStream);
+ decryptStream.close();
+ }
+
+ @Test
+ public void noEncryptStreamWithMoreThanMaxEdks() {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ CryptoOutputStream> encryptStream = maxEdksClient_.createEncryptingStream(providerWithEdks(4), byteArrayOutputStream);
+ assertThrows(AwsCryptoException.class, "Encrypted data keys exceed maxEncryptedDataKeys", () ->
+ IOUtils.copy(new ByteArrayInputStream(new byte[] {1}), encryptStream));
+ }
+
+ @Test
+ public void noDecryptStreamWithMoreThanMaxEdks() throws IOException {
+ MasterKeyProvider> provider = providerWithEdks(4);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ CryptoOutputStream> encryptStream = noMaxEdksClient_.createEncryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(new byte[] {1}), encryptStream);
+ encryptStream.close();
+
+ byte[] ciphertext = byteArrayOutputStream.toByteArray();
+
+ byteArrayOutputStream.reset();
+ CryptoOutputStream> decryptStream = maxEdksClient_.createDecryptingStream(provider, byteArrayOutputStream);
+ assertThrows(AwsCryptoException.class, "Ciphertext encrypted data keys exceed maxEncryptedDataKeys", () ->
+ IOUtils.copy(new ByteArrayInputStream(ciphertext), decryptStream));
+ }
+
+ @Test
+ public void encryptDecryptStreamWithNoMaxEdks() throws IOException {
+ MasterKeyProvider> provider = providerWithEdks(MESSAGE_FORMAT_MAX_EDKS);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ CryptoOutputStream> encryptStream = noMaxEdksClient_.createEncryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(new byte[] {1}), encryptStream);
+ encryptStream.close();
+
+ byte[] ciphertext = byteArrayOutputStream.toByteArray();
+ assertEquals(new ParsedCiphertext(ciphertext).getEncryptedKeyBlobCount(), MESSAGE_FORMAT_MAX_EDKS);
+
+ byteArrayOutputStream.reset();
+ CryptoOutputStream> decryptStream = noMaxEdksClient_.createDecryptingStream(provider, byteArrayOutputStream);
+ IOUtils.copy(new ByteArrayInputStream(ciphertext), decryptStream);
+ decryptStream.close();
+ }
}
diff --git a/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java b/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java
index 39ac91b5d..b23678bbd 100644
--- a/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java
+++ b/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java
@@ -146,12 +146,13 @@ public static Collection