From 930793314224381e37a1331b685069a0bd55e6aa Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Wed, 30 Oct 2019 10:17:34 -0700 Subject: [PATCH 01/18] Create keyring trace and add to encryption and decryption materials. (#134) * Create keyring trace and add to encryption and decryption materials. *Issue #, if available:* #102 *Description of changes:* Creating a keyring trace and adding to encryption and decryption materials to allow for auditing actions a keyring has taken on encryption materials. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. --- .../encryptionsdk/keyrings/KeyringTrace.java | 62 +++++++++++ .../keyrings/KeyringTraceEntry.java | 104 ++++++++++++++++++ .../keyrings/KeyringTraceFlag.java | 49 +++++++++ .../model/DecryptionMaterials.java | 18 +++ .../model/EncryptionMaterials.java | 19 +++- .../keyrings/KeyringTraceTest.java | 65 +++++++++++ 6 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceFlag.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java new file mode 100644 index 000000000..7759d70d7 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +/** + * A keyring trace containing all of the actions that keyrings have taken on a set of encryption materials. + */ +public class KeyringTrace { + + private final List entries = new ArrayList<>(); + + /** + * Add a new entry to the keyring trace. + * + * @param keyNamespace The namespace for the key. + * @param keyName The name of the key. + * @param flags A set of one or more KeyringTraceFlag enums + * indicating what actions were taken by a keyring. + */ + public void add(String keyNamespace, String keyName, KeyringTraceFlag... flags) { + entries.add(new KeyringTraceEntry(keyNamespace, keyName, + new HashSet<>(Arrays.asList(flags)))); + } + + /** + * Gets an unmodifiable list of `KeyringTraceEntry`s ordered sequentially + * according to the order the actions were taken, with the earliest action + * corresponding to the first `KeyringTraceEntry` in the list. + * + * @return An unmodifiable list of `KeyringTraceEntry`s + */ + public List getEntries() { + return Collections.unmodifiableList(entries); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("entries", entries) + .toString(); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java new file mode 100644 index 000000000..64a14a268 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import static org.apache.commons.lang3.Validate.notBlank; +import static org.apache.commons.lang3.Validate.notEmpty; + +/** + * A representation of an action that a keyring has taken on a data key. + */ +public class KeyringTraceEntry { + + private final String keyNamespace; + private final String keyName; + private final Set flags; + + /** + * Constructs a new `KeyringTraceEntry`. + * + * @param keyNamespace The namespace for the key. + * @param keyName The name of the key. + * @param flags A set of one or more KeyringTraceFlag enums + * indicating what actions were taken by a keyring. + */ + KeyringTraceEntry(final String keyNamespace, final String keyName, final Set flags) { + notBlank(keyNamespace, "keyNamespace is required"); + notBlank(keyName, "keyName is required"); + notEmpty(flags, "At least one flag is required"); + + this.keyNamespace = keyNamespace; + this.keyName = keyName; + this.flags = Collections.unmodifiableSet(flags); + } + + /** + * Returns the key namespace. + * + * @return The key namespace. + */ + public String getKeyNamespace() { + return this.keyNamespace; + } + + /** + * Returns the key name. + * + * @return The key name. + */ + public String getKeyName() { + return this.keyName; + } + + /** + * Returns an unmodifiable set of flags that indicate + * which actions were taken by a keyring. + * + * @return The unmodifiable set of flags. + */ + public Set getFlags() { + return this.flags; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KeyringTraceEntry that = (KeyringTraceEntry) o; + return Objects.equals(keyNamespace, that.keyNamespace) && + Objects.equals(keyName, that.keyName) && + Objects.equals(flags, that.flags); + } + + @Override + public int hashCode() { + return Objects.hash(keyNamespace, keyName, flags); + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("keyNamespace", this.keyNamespace) + .append("keyName", this.keyName) + .append("flags", this.flags) + .toString(); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceFlag.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceFlag.java new file mode 100644 index 000000000..94e59bc41 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceFlag.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +/** + * Enum representing the possible actions a keyring may take on the + * different wrapping keys it manages. + */ +public enum KeyringTraceFlag { + + /** + * A flag to represent that a keyring has generated a plaintext data key. + */ + GENERATED_DATA_KEY, + + /** + * A flag to represent that a keyring has created an encrypted data key. + */ + ENCRYPTED_DATA_KEY, + + /** + * A flag to represent that a keyring has obtained the + * corresponding plaintext data key from an encrypted data key. + */ + DECRYPTED_DATA_KEY, + + /** + * A flag to represent that the keyring has cryptographically + * bound the encryption context to a newly created encrypted data key. + */ + SIGNED_ENCRYPTION_CONTEXT, + + /** + * A flag to represent that the keyring has verified that an encrypted + * data key was originally created with a particular encryption context. + */ + VERIFIED_ENCRYPTION_CONTEXT +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java index 94423b884..0c0ba52c7 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java @@ -3,14 +3,17 @@ import java.security.PublicKey; import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; public final class DecryptionMaterials { private final DataKey dataKey; private final PublicKey trailingSignatureKey; + private final KeyringTrace keyringTrace; private DecryptionMaterials(Builder b) { dataKey = b.getDataKey(); trailingSignatureKey = b.getTrailingSignatureKey(); + keyringTrace = b.getKeyringTrace(); } public DataKey getDataKey() { @@ -21,6 +24,10 @@ public PublicKey getTrailingSignatureKey() { return trailingSignatureKey; } + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + public static Builder newBuilder() { return new Builder(); } @@ -32,10 +39,12 @@ public Builder toBuilder() { public static final class Builder { private DataKey dataKey; private PublicKey trailingSignatureKey; + private KeyringTrace keyringTrace; private Builder(DecryptionMaterials result) { this.dataKey = result.getDataKey(); this.trailingSignatureKey = result.getTrailingSignatureKey(); + this.keyringTrace = result.getKeyringTrace(); } private Builder() {} @@ -58,6 +67,15 @@ public Builder setTrailingSignatureKey(PublicKey trailingSignatureKey) { return this; } + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + public Builder setKeyringTrace(KeyringTrace keyringTrace) { + this.keyringTrace = keyringTrace; + return this; + } + public DecryptionMaterials build() { return new DecryptionMaterials(this); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java index 1a40d7c36..f9b05a153 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java @@ -11,6 +11,7 @@ import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; /** * Contains the cryptographic materials needed for an encryption operation. @@ -24,6 +25,7 @@ public final class EncryptionMaterials { private final SecretKey cleartextDataKey; private final PrivateKey trailingSignatureKey; private final List masterKeys; + private final KeyringTrace keyringTrace; private EncryptionMaterials(Builder b) { this.algorithm = b.algorithm; @@ -32,6 +34,7 @@ private EncryptionMaterials(Builder b) { this.cleartextDataKey = b.cleartextDataKey; this.trailingSignatureKey = b.trailingSignatureKey; this.masterKeys = b.getMasterKeys(); + this.keyringTrace = b.keyringTrace; } public Builder toBuilder() { @@ -100,12 +103,13 @@ public List getMasterKeys() { Objects.equals(encryptedDataKeys, that.encryptedDataKeys) && Objects.equals(cleartextDataKey, that.cleartextDataKey) && Objects.equals(trailingSignatureKey, that.trailingSignatureKey) && - Objects.equals(masterKeys, that.masterKeys); + Objects.equals(masterKeys, that.masterKeys) && + Objects.equals(keyringTrace, that.keyringTrace); } @Override public int hashCode() { return Objects.hash(algorithm, encryptionContext, encryptedDataKeys, cleartextDataKey, trailingSignatureKey, - masterKeys); + masterKeys, keyringTrace); } public static class Builder { @@ -115,6 +119,7 @@ public static class Builder { private SecretKey cleartextDataKey; private PrivateKey trailingSignatureKey; private List masterKeys = Collections.emptyList(); + private KeyringTrace keyringTrace; private Builder() {} @@ -125,6 +130,7 @@ private Builder(EncryptionMaterials r) { cleartextDataKey = r.cleartextDataKey; trailingSignatureKey = r.trailingSignatureKey; setMasterKeys(r.masterKeys); + keyringTrace = r.keyringTrace; } public EncryptionMaterials build() { @@ -184,5 +190,14 @@ public Builder setMasterKeys(List masterKeys) { this.masterKeys = Collections.unmodifiableList(new ArrayList<>(masterKeys)); return this; } + + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + public Builder setKeyringTrace(KeyringTrace keyringTrace) { + this.keyringTrace = keyringTrace; + return this; + } } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java new file mode 100644 index 000000000..c67fcbe77 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import org.junit.Test; + +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class KeyringTraceTest { + + @Test + public void testOrderMaintained() { + KeyringTraceEntry entry1 = new KeyringTraceEntry("ns1", "name1", + singleton(KeyringTraceFlag.GENERATED_DATA_KEY)); + KeyringTraceEntry entry2 = new KeyringTraceEntry("ns2", "name2", + singleton(KeyringTraceFlag.DECRYPTED_DATA_KEY)); + KeyringTraceEntry entry3 = new KeyringTraceEntry("ns3", "name3", + singleton(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + + KeyringTrace trace = new KeyringTrace(); + trace.add(entry1.getKeyNamespace(), entry1.getKeyName(), entry1.getFlags().iterator().next()); + trace.add(entry2.getKeyNamespace(), entry2.getKeyName(), entry2.getFlags().iterator().next()); + trace.add(entry3.getKeyNamespace(), entry3.getKeyName(), entry3.getFlags().iterator().next()); + + assertEquals(entry1, trace.getEntries().get(0)); + assertEquals(entry2, trace.getEntries().get(1)); + assertEquals(entry3, trace.getEntries().get(2)); + } + + @Test(expected = UnsupportedOperationException.class) + public void testImmutable() { + KeyringTrace trace = new KeyringTrace(); + trace.add("namespace", "name", KeyringTraceFlag.GENERATED_DATA_KEY); + + trace.getEntries().add(new KeyringTraceEntry("ns1", "name1", + singleton(KeyringTraceFlag.GENERATED_DATA_KEY))); + } + + @Test + public void testKeyringTraceEntryEquals() { + KeyringTraceEntry entry1 = new KeyringTraceEntry("namespace", "name", + singleton(KeyringTraceFlag.GENERATED_DATA_KEY)); + KeyringTraceEntry entry2 = new KeyringTraceEntry(entry1.getKeyNamespace(), entry1.getKeyName(), + entry1.getFlags()); + KeyringTraceEntry entry3 = new KeyringTraceEntry("othernamespace", "name", + singleton(KeyringTraceFlag.GENERATED_DATA_KEY)); + + assertEquals(entry1, entry1); + assertEquals(entry1, entry2); + assertNotEquals(entry2, entry3); + } +} From 282cd6f8a7ec34ee1d659fd1b7d55aaf437fddda Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Fri, 8 Nov 2019 16:33:25 -0800 Subject: [PATCH 02/18] Merge head of master into keyring (#141) *Description of changes:* Merge head of master into keyring By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. --- CHANGELOG.md | 27 +- README.md | 52 ++-- pom.xml | 28 +- .../examples/BasicEncryptionExample.java | 88 ++++++ .../internal/AesGcmJceKeyCipher.java | 94 ++++++ .../encryptionsdk/internal/JceKeyCipher.java | 136 +++++++++ .../internal/RsaJceKeyCipher.java | 109 +++++++ .../encryptionsdk/internal/Utils.java | 21 ++ .../encryptionsdk/jce/JceMasterKey.java | 288 ++---------------- .../examples/BasicEncryptionExampleTest.java | 25 ++ .../amazonaws/encryptionsdk/UtilsTest.java | 13 + .../encryptionsdk/kms/KMSTestFixtures.java | 4 +- 12 files changed, 588 insertions(+), 297 deletions(-) create mode 100644 src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ccfc619b..1458c3127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,27 @@ # Changelog -## 1.6.1 -- Unreleased +## 1.6.1 -- 2019-10-29 + +### Deprecation Warnings +* Deprecated `AwsCrypto.encryptString()` and `AwsCrypto.decryptString()`. + Replace your calls to these methods with calls to AwsCrypto.encryptData() and AwsCrypto.decryptData(). + Unlike the deprecated methods, these methods don't perform any Base64 encoding or decoding, so they are fully compatible with other language implementations of the AWS Encryption SDK. + + If you need Base64 encoding or decoding for your application, you can add it outside of the AWS Encryption SDK. + [PR #120](https://github.com/aws/aws-encryption-sdk-java/pull/120) + +### Patches +* Correctly validate version [PR #116](https://github.com/aws/aws-encryption-sdk-java/pull/116) +* `ParsedCiphertext` now handles truncated input properly [PR #119](https://github.com/aws/aws-encryption-sdk-java/pull/119) + ### Maintenance -* Add support for standard test vectors via `testVectorZip` system property. -* No longer require use of BouncyCastle with RSA `JceMasterKey`s -* No longer use BouncyCastle for Elliptic Curve key generation and point compression/decompression +* Add support for standard test vectors via `testVectorZip` system property. [PR #127](https://github.com/aws/aws-encryption-sdk-java/pull/127) +* Remove all explicit cryptographic dependencies on BouncyCastle. The AWS Encryption SDK for Java still uses Bouncy Castle for other tasks. PRs + [#128](https://github.com/aws/aws-encryption-sdk-java/pull/128), + [#129](https://github.com/aws/aws-encryption-sdk-java/pull/129), + [#130](https://github.com/aws/aws-encryption-sdk-java/pull/130), + [#131](https://github.com/aws/aws-encryption-sdk-java/pull/131), + and [#132](https://github.com/aws/aws-encryption-sdk-java/pull/132). ## 1.6.0 -- 2019-05-31 @@ -17,7 +34,7 @@ ## 1.5.0 -- 2019-05-30 ### Minor Changes -* Add dependency on Apache Commons Codec 1.12. +* Added dependency on Apache Commons Codec 1.12. * Use org.apache.commons.codec.binary.Base64 instead of java.util.Base64 so that the SDK can be used on systems that do not have java.util.Base64 but support Java 8 language features. diff --git a/README.md b/README.md index c8ad880d0..0387070cc 100644 --- a/README.md +++ b/README.md @@ -9,26 +9,31 @@ For more details about the design and architecture of the SDK, see the [official ### Required Prerequisites To use this SDK you must have: -* **A Java 8 development environment** +* **A Java 8 or newer development environment** - If you do not have one, go to [Java SE Downloads](https://www.oracle.com/technetwork/java/javase/downloads/index.html) on the Oracle website, then download and install the Java SE Development Kit (JDK). Java 8 or higher is required. + If you do not have one, we recommend [Amazon Corretto](https://aws.amazon.com/corretto/). **Note:** If you use the Oracle JDK, you must also download and install the [Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). -* **Bouncy Castle** +* **Bouncy Castle** or **Bouncy Castle FIPS** - Bouncy Castle provides a cryptography API for Java. If you do not have Bouncy Castle, go to https://bouncycastle.org/latest_releases.html, then download the provider file that corresponds to your JDK. Or, you can pick it up from Maven: + The AWS Encryption SDK for Java uses Bouncy Castle to serialize and deserialize cryptographic objects. + It does not explicitly use Bouncy Castle (or any other [JCA Provider](https://docs.oracle.com/javase/8/docs/api/java/security/Provider.html)) for the underlying cryptography. + Instead, it uses the platform default, which you can configure or override as documented in the + [Java Cryptography Architecture (JCA) Reference Guide](https://docs.oracle.com/javase/9/security/java-cryptography-architecture-jca-reference-guide.htm#JSSEC-GUID-2BCFDD85-D533-4E6C-8CE9-29990DEB0190). - ```xml - - org.bouncycastle - bcprov-ext-jdk15on - 1.61 - - ``` + If you do not have Bouncy Castle, go to https://bouncycastle.org/latest_releases.html, then download the provider file that corresponds to your JDK. + Or, you can pick it up from Maven (groupId: `org.bouncycastle`, artifactId: `bcprov-ext-jdk15on`). + + Beginning in version 1.6.1, + the AWS Encryption SDK also works with Bouncy Castle FIPS (groupId: `org.bouncycastle`, artifactId: `bc-fips`) + as an alternative to non-FIPS Bouncy Castle. + For help installing and configuring Bouncy Castle FIPS properly, see [BC FIPS documentation](https://www.bouncycastle.org/documentation.html), + in particular, **User Guides** and **Security Policy**. ### Optional Prerequisites +#### AWS Integration You don't need an Amazon Web Services (AWS) account to use this SDK, but some of the [example code][examples] requires an AWS account, a customer master key (CMK) in AWS KMS, and the AWS SDK for Java. * **To create an AWS account**, go to [Sign In or Create an AWS Account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) and then choose **I am a new user.** Follow the instructions to create an AWS account. @@ -37,6 +42,10 @@ You don't need an Amazon Web Services (AWS) account to use this SDK, but some of * **To download and install the AWS SDK for Java**, go to [Installing the AWS SDK for Java](https://docs.aws.amazon.com/AWSSdkDocsJava/latest/DeveloperGuide/java-dg-install-sdk.html) in the AWS SDK for Java documentation and then follow the instructions on that page. +#### Amazon Corretto Crypto Provider +Many users find that the Amazon Corretto Crypto Provider (ACCP) significantly improves the performance of the AWS Encryption SDK. +For help installing and using ACCP, see the [ACCP GitHub Respository](https://github.com/corretto/amazon-corretto-crypto-provider) . + ### Download You can get the latest release from Maven: @@ -45,29 +54,10 @@ You can get the latest release from Maven: com.amazonaws aws-encryption-sdk-java - 1.6.0 + 1.6.1 ``` -Don't forget to enable the download of snapshot jars from Maven: - -```xml - - - allow-snapshots - true - - - snapshots-repo - https://oss.sonatype.org/content/repositories/snapshots - false - true - - - - -``` - ### Get Started The following code sample demonstrates how to get started: diff --git a/pom.xml b/pom.xml index 3a2f2d32c..6c485750c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.amazonaws aws-encryption-sdk-java - 1.6.0 + 1.6.1 jar aws-encryption-sdk-java @@ -118,6 +118,26 @@ 8 + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-test-source + generate-test-sources + + add-test-source + + + + src/examples/java + + + + + @@ -145,6 +165,12 @@ sign + + + --pinentry-mode + loopback + + diff --git a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java new file mode 100644 index 000000000..748cea536 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; + +/** + *

+ * Encrypts and then decrypts data using an AWS KMS customer master key. + * + *

+ * Arguments: + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master + * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class BasicEncryptionExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecrypt(keyArn); + } + + static void encryptAndDecrypt(final String keyArn) { + // 1. Instantiate the SDK + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider + final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder().withKeysForEncryption(keyArn).build(); + + // 3. Create an encryption context + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data + final CryptoResult encryptResult = crypto.encryptData(masterKeyProvider, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data + final CryptoResult decryptResult = crypto.decryptData(masterKeyProvider, ciphertext); + + // 6. Before verifying the plaintext, verify that the customer master key that + // was used in the encryption operation was the one supplied to the master key provider. + if (!decryptResult.getMasterKeyIds().get(0).equals(keyArn)) { + throw new IllegalStateException("Wrong key ID!"); + } + + // 7. Also, verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. Because the + // SDK can add values to the encryption context, don't require that + // the entire context matches. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java new file mode 100644 index 000000000..7a4511f01 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.internal; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.util.Map; + +/** + * A JceKeyCipher based on the Advanced Encryption Standard in Galois/Counter Mode. + */ +class AesGcmJceKeyCipher extends JceKeyCipher { + private static final int NONCE_LENGTH = 12; + private static final int TAG_LENGTH = 128; + private static final String TRANSFORMATION = "AES/GCM/NoPadding"; + private static final int SPEC_LENGTH = Integer.BYTES + Integer.BYTES + NONCE_LENGTH; + + AesGcmJceKeyCipher(SecretKey key) { + super(key, key); + } + + private static byte[] specToBytes(final GCMParameterSpec spec) { + final byte[] nonce = spec.getIV(); + final byte[] result = new byte[SPEC_LENGTH]; + final ByteBuffer buffer = ByteBuffer.wrap(result); + buffer.putInt(spec.getTLen()); + buffer.putInt(nonce.length); + buffer.put(nonce); + return result; + } + + private static GCMParameterSpec bytesToSpec(final byte[] data, final int offset) throws InvalidKeyException { + if (data.length - offset != SPEC_LENGTH) { + throw new InvalidKeyException("Algorithm specification was an invalid data size"); + } + + final ByteBuffer buffer = ByteBuffer.wrap(data, offset, SPEC_LENGTH); + final int tagLen = buffer.getInt(); + final int nonceLen = buffer.getInt(); + + if (tagLen != TAG_LENGTH) { + throw new InvalidKeyException(String.format("Authentication tag length must be %s", TAG_LENGTH)); + } + + if (nonceLen != NONCE_LENGTH) { + throw new InvalidKeyException(String.format("Initialization vector (IV) length must be %s", NONCE_LENGTH)); + } + + final byte[] nonce = new byte[nonceLen]; + buffer.get(nonce); + + return new GCMParameterSpec(tagLen, nonce); + } + + @Override + WrappingData buildWrappingCipher(final Key key, final Map encryptionContext) + throws GeneralSecurityException { + final byte[] nonce = new byte[NONCE_LENGTH]; + Utils.getSecureRandom().nextBytes(nonce); + final GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, nonce); + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, key, spec); + final byte[] aad = EncryptionContextSerializer.serialize(encryptionContext); + cipher.updateAAD(aad); + return new WrappingData(cipher, specToBytes(spec)); + } + + @Override + Cipher buildUnwrappingCipher(final Key key, final byte[] extraInfo, final int offset, + final Map encryptionContext) throws GeneralSecurityException { + final GCMParameterSpec spec = bytesToSpec(extraInfo, offset); + final Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + final byte[] aad = EncryptionContextSerializer.serialize(encryptionContext); + cipher.updateAAD(aad); + return cipher; + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java new file mode 100644 index 000000000..643278a71 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java @@ -0,0 +1,136 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.internal; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.apache.commons.lang3.ArrayUtils; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Map; + +/** + * Abstract class for encrypting and decrypting JCE data keys. + */ +public abstract class JceKeyCipher { + + private final Key wrappingKey; + private final Key unwrappingKey; + private static final Charset KEY_NAME_ENCODING = StandardCharsets.UTF_8; + + /** + * Returns a new instance of a JceKeyCipher based on the + * Advanced Encryption Standard in Galois/Counter Mode. + * + * @param secretKey The secret key to use for encrypt/decrypt operations. + * @return The JceKeyCipher. + */ + public static JceKeyCipher aesGcm(SecretKey secretKey) { + return new AesGcmJceKeyCipher(secretKey); + } + + /** + * Returns a new instance of a JceKeyCipher based on RSA. + * + * @param wrappingKey The public key to use for encrypting the key. + * @param unwrappingKey The private key to use for decrypting the key. + * @param transformation The transformation. + * @return The JceKeyCipher. + */ + public static JceKeyCipher rsa(PublicKey wrappingKey, PrivateKey unwrappingKey, String transformation) { + return new RsaJceKeyCipher(wrappingKey, unwrappingKey, transformation); + } + + JceKeyCipher(Key wrappingKey, Key unwrappingKey) { + this.wrappingKey = wrappingKey; + this.unwrappingKey = unwrappingKey; + } + + abstract WrappingData buildWrappingCipher(Key key, Map encryptionContext) throws GeneralSecurityException; + + abstract Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset, + Map encryptionContext) throws GeneralSecurityException; + + + /** + * Encrypts the given key, incorporating the given keyName and encryptionContext. + * @param key The key to encrypt. + * @param keyName A UTF-8 encoded representing a name for the key. + * @param keyNamespace A UTF-8 encoded value that namespaces the key. + * @param encryptionContext A key-value mapping of arbitrary, non-secret, UTF-8 encoded strings used + * during encryption and decryption to provide additional authenticated data (AAD). + * @return The encrypted data key. + */ + public EncryptedDataKey encryptKey(final byte[] key, final String keyName, final String keyNamespace, + final Map encryptionContext) { + + final byte[] keyNameBytes = keyName.getBytes(KEY_NAME_ENCODING); + + try { + final JceKeyCipher.WrappingData wData = buildWrappingCipher(wrappingKey, encryptionContext); + final Cipher cipher = wData.cipher; + final byte[] encryptedKey = cipher.doFinal(key); + + final byte[] provInfo; + if (wData.extraInfo.length == 0) { + provInfo = keyNameBytes; + } else { + provInfo = new byte[keyNameBytes.length + wData.extraInfo.length]; + System.arraycopy(keyNameBytes, 0, provInfo, 0, keyNameBytes.length); + System.arraycopy(wData.extraInfo, 0, provInfo, keyNameBytes.length, wData.extraInfo.length); + } + + return new KeyBlob(keyNamespace, provInfo, encryptedKey); + } catch (final GeneralSecurityException gsex) { + throw new AwsCryptoException(gsex); + } + } + + /** + * Decrypts the given encrypted data key. + * + * @param edk The encrypted data key. + * @param keyName A UTF-8 encoded String representing a name for the key. + * @param encryptionContext A key-value mapping of arbitrary, non-secret, UTF-8 encoded strings used + * during encryption and decryption to provide additional authenticated data (AAD). + * @return The decrypted key. + * @throws GeneralSecurityException If a problem occurred decrypting the key. + */ + public byte[] decryptKey(final EncryptedDataKey edk, final String keyName, + final Map encryptionContext) throws GeneralSecurityException { + final byte[] keyNameBytes = keyName.getBytes(KEY_NAME_ENCODING); + + final Cipher cipher = buildUnwrappingCipher(unwrappingKey, edk.getProviderInformation(), + keyNameBytes.length, encryptionContext); + return cipher.doFinal(edk.getEncryptedDataKey()); + } + + static class WrappingData { + public final Cipher cipher; + public final byte[] extraInfo; + + WrappingData(final Cipher cipher, final byte[] extraInfo) { + this.cipher = cipher; + this.extraInfo = extraInfo != null ? extraInfo : ArrayUtils.EMPTY_BYTE_ARRAY; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java new file mode 100644 index 000000000..c830f5487 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java @@ -0,0 +1,109 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.internal; + +import org.apache.commons.lang3.ArrayUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import java.security.GeneralSecurityException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.util.Map; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A JceKeyCipher based on RSA. + */ +class RsaJceKeyCipher extends JceKeyCipher { + + private static final Logger LOGGER = Logger.getLogger(RsaJceKeyCipher.class.getName()); + // MGF1 with SHA-224 isn't really supported, but we include it in the regex because we need it + // for proper handling of the algorithm. + private static final Pattern SUPPORTED_TRANSFORMATIONS = + Pattern.compile("RSA/ECB/(?:PKCS1Padding|OAEPWith(SHA-(?:1|224|256|384|512))AndMGF1Padding)", + Pattern.CASE_INSENSITIVE); + private final AlgorithmParameterSpec parameterSpec_; + private final String transformation_; + + RsaJceKeyCipher(PublicKey wrappingKey, PrivateKey unwrappingKey, String transformation) { + super(wrappingKey, unwrappingKey); + + final Matcher matcher = SUPPORTED_TRANSFORMATIONS.matcher(transformation); + if (matcher.matches()) { + final String hashUnknownCase = matcher.group(1); + if (hashUnknownCase != null) { + // OAEP mode a.k.a PKCS #1v2 + final String hash = hashUnknownCase.toUpperCase(); + transformation_ = "RSA/ECB/OAEPPadding"; + + final MGF1ParameterSpec mgf1Spec; + switch (hash) { + case "SHA-1": + mgf1Spec = MGF1ParameterSpec.SHA1; + break; + case "SHA-224": + LOGGER.warning(transformation + " is not officially supported by the JceMasterKey"); + mgf1Spec = MGF1ParameterSpec.SHA224; + break; + case "SHA-256": + mgf1Spec = MGF1ParameterSpec.SHA256; + break; + case "SHA-384": + mgf1Spec = MGF1ParameterSpec.SHA384; + break; + case "SHA-512": + mgf1Spec = MGF1ParameterSpec.SHA512; + break; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + transformation); + } + parameterSpec_ = new OAEPParameterSpec(hash, "MGF1", mgf1Spec, PSource.PSpecified.DEFAULT); + } else { + // PKCS #1 v1.x + transformation_ = transformation; + parameterSpec_ = null; + } + } else { + LOGGER.warning(transformation + " is not officially supported by the JceMasterKey"); + // Unsupported transformation, just use exactly what we are given + transformation_ = transformation; + parameterSpec_ = null; + } + } + + @Override + WrappingData buildWrappingCipher(Key key, Map encryptionContext) throws GeneralSecurityException { + final Cipher cipher = Cipher.getInstance(transformation_); + cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec_); + return new WrappingData(cipher, ArrayUtils.EMPTY_BYTE_ARRAY); + } + + @Override + Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset, Map encryptionContext) throws GeneralSecurityException { + if (extraInfo.length != offset) { + throw new IllegalArgumentException("Extra info must be empty for RSA keys"); + } + + final Cipher cipher = Cipher.getInstance(transformation_); + cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec_); + return cipher; + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java b/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java index adedea54a..5761d03f9 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/Utils.java @@ -311,4 +311,25 @@ public static byte[] bigIntegerToByteArray(final BigInteger bigInteger, final in System.arraycopy(rawBytes, 0, paddedResult, length - rawBytes.length, rawBytes.length); return paddedResult; } + + /** + * Returns true if the prefix of the given length for the input arrays are equal. + * This method will return as soon as the first difference is found, and is thus not constant-time. + * + * @param a The first array. + * @param b The second array. + * @param length The length of the prefix to compare. + * @return True if the prefixes are equal, false otherwise. + */ + public static boolean arrayPrefixEquals(final byte[] a, final byte[] b, final int length) { + if (a == null || b == null || a.length < length || b.length < length) { + return false; + } + for (int x = 0; x < length; x++) { + if (a[x] != b[x]) { + return false; + } + } + return true; + } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java index 70d289ddd..4995066cb 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java @@ -13,58 +13,36 @@ package com.amazonaws.encryptionsdk.jce; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.internal.JceKeyCipher; +import com.amazonaws.encryptionsdk.internal.Utils; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.spec.AlgorithmParameterSpec; -import java.security.spec.MGF1ParameterSpec; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.OAEPParameterSpec; -import javax.crypto.spec.PSource; -import javax.crypto.spec.SecretKeySpec; - -import com.amazonaws.encryptionsdk.CryptoAlgorithm; -import com.amazonaws.encryptionsdk.DataKey; -import com.amazonaws.encryptionsdk.EncryptedDataKey; -import com.amazonaws.encryptionsdk.MasterKey; -import com.amazonaws.encryptionsdk.exception.AwsCryptoException; -import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; -import com.amazonaws.encryptionsdk.internal.EncryptionContextSerializer; /** * Represents a {@link MasterKey} backed by one (or more) JCE {@link Key}s. Instances of this should * only be acquired using {@link #getInstance(SecretKey, String, String, String)} or * {@link #getInstance(PublicKey, PrivateKey, String, String, String)}. */ -public abstract class JceMasterKey extends MasterKey { - private static final Logger LOGGER = Logger.getLogger(JceMasterKey.class.getName()); - private static final byte[] EMPTY_ARRAY = new byte[0]; - - private final SecureRandom rnd = new SecureRandom(); - private final Key wrappingKey_; - private final Key unwrappingKey_; +public class JceMasterKey extends MasterKey { private final String providerName_; private final String keyId_; private final byte[] keyIdBytes_; + private final JceKeyCipher jceKeyCipher_; /** * Returns a {@code JceMasterKey} backed by {@code key} using {@code wrappingAlgorithm}. @@ -82,7 +60,7 @@ public static JceMasterKey getInstance(final SecretKey key, final String provide final String wrappingAlgorithm) { switch (wrappingAlgorithm.toUpperCase()) { case "AES/GCM/NOPADDING": - return new AesGcm(key, provider, keyId); + return new JceMasterKey(provider, keyId, JceKeyCipher.aesGcm(key)); default: throw new IllegalArgumentException("Right now only AES/GCM/NoPadding is supported"); @@ -104,18 +82,16 @@ public static JceMasterKey getInstance(final PublicKey wrappingKey, final Privat final String provider, final String keyId, final String wrappingAlgorithm) { if (wrappingAlgorithm.toUpperCase().startsWith("RSA/ECB/")) { - return new Rsa(wrappingKey, unwrappingKey, provider, keyId, wrappingAlgorithm); + return new JceMasterKey(provider, keyId, JceKeyCipher.rsa(wrappingKey, unwrappingKey, wrappingAlgorithm)); } throw new UnsupportedOperationException("Currently only RSA asymmetric algorithms are supported"); } - protected JceMasterKey(final Key wrappingKey, final Key unwrappingKey, final String providerName, - final String keyId) { - wrappingKey_ = wrappingKey; - unwrappingKey_ = unwrappingKey; + protected JceMasterKey(final String providerName, final String keyId, final JceKeyCipher jceKeyCipher) { providerName_ = providerName; keyId_ = keyId; keyIdBytes_ = keyId_.getBytes(StandardCharsets.UTF_8); + jceKeyCipher_ = jceKeyCipher; } @Override @@ -132,9 +108,10 @@ public String getKeyId() { public DataKey generateDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext) { final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; - rnd.nextBytes(rawKey); - final SecretKeySpec key = new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()); - return encryptRawKey(key, rawKey, encryptionContext); + Utils.getSecureRandom().nextBytes(rawKey); + EncryptedDataKey encryptedDataKey = jceKeyCipher_.encryptKey(rawKey, keyId_, providerName_, encryptionContext); + return new DataKey<>(new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()), + encryptedDataKey.getEncryptedDataKey(), encryptedDataKey.getProviderInformation(), this); } @Override @@ -150,26 +127,8 @@ public DataKey encryptDataKey(final CryptoAlgorithm algorithm, throw new IllegalArgumentException("Incorrect key algorithm. Expected " + key.getAlgorithm() + " but got " + algorithm.getKeyAlgo()); } - final byte[] rawKey = key.getEncoded(); - final DataKey result = encryptRawKey(key, rawKey, encryptionContext); - Arrays.fill(rawKey, (byte) 0); - return result; - } - - protected DataKey encryptRawKey(final SecretKey key, final byte[] rawKey, - final Map encryptionContext) { - try { - final WrappingData wData = buildWrappingCipher(wrappingKey_, encryptionContext); - final Cipher cipher = wData.cipher; - final byte[] encryptedKey = cipher.doFinal(rawKey); - - final byte[] provInfo = new byte[keyIdBytes_.length + wData.extraInfo.length]; - System.arraycopy(keyIdBytes_, 0, provInfo, 0, keyIdBytes_.length); - System.arraycopy(wData.extraInfo, 0, provInfo, keyIdBytes_.length, wData.extraInfo.length); - return new DataKey<>(key, encryptedKey, provInfo, this); - } catch (final GeneralSecurityException gsex) { - throw new AwsCryptoException(gsex); - } + EncryptedDataKey encryptedDataKey = jceKeyCipher_.encryptKey(key.getEncoded(), keyId_, providerName_, encryptionContext); + return new DataKey<>(key, encryptedDataKey.getEncryptedDataKey(), encryptedDataKey.getProviderInformation(), this); } @Override @@ -182,10 +141,13 @@ public DataKey decryptDataKey(final CryptoAlgorithm algorithm, for (final EncryptedDataKey edk : encryptedDataKeys) { try { if (edk.getProviderId().equals(getProviderId()) - && arrayPrefixEquals(edk.getProviderInformation(), keyIdBytes_, keyIdBytes_.length)) { - final DataKey result = actualDecrypt(algorithm, edk, encryptionContext); - if (result != null) { - return result; + && Utils.arrayPrefixEquals(edk.getProviderInformation(), keyIdBytes_, keyIdBytes_.length)) { + final byte[] decryptedKey = jceKeyCipher_.decryptKey(edk, keyId_, encryptionContext); + + // Validate that the decrypted key length is as expected + if (decryptedKey.length == algorithm.getDataKeyLength()) { + return new DataKey<>(new SecretKeySpec(decryptedKey, algorithm.getDataKeyAlgo()), + edk.getEncryptedDataKey(), edk.getProviderInformation(), this); } } } catch (final Exception ex) { @@ -194,194 +156,4 @@ && arrayPrefixEquals(edk.getProviderInformation(), keyIdBytes_, keyIdBytes_.leng } throw buildCannotDecryptDksException(exceptions); } - - protected DataKey actualDecrypt(final CryptoAlgorithm algorithm, final EncryptedDataKey edk, - final Map encryptionContext) throws GeneralSecurityException { - final Cipher cipher = buildUnwrappingCipher(unwrappingKey_, edk.getProviderInformation(), - keyIdBytes_.length, - encryptionContext); - final byte[] rawKey = cipher.doFinal(edk.getEncryptedDataKey()); - if (rawKey.length != algorithm.getDataKeyLength()) { - // Something's wrong here. Assume that the decryption is invalid. - return null; - } - return new DataKey<>( - new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()), - edk.getEncryptedDataKey(), - edk.getProviderInformation(), this); - - } - - protected static boolean arrayPrefixEquals(final byte[] a, final byte[] b, final int len) { - if (a == null || b == null || a.length < len || b.length < len) { - return false; - } - for (int x = 0; x < len; x++) { - if (a[x] != b[x]) { - return false; - } - } - return true; - } - - protected abstract WrappingData buildWrappingCipher(Key key, Map encryptionContext) - throws GeneralSecurityException; - - protected abstract Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset, - Map encryptionContext) throws GeneralSecurityException; - - private static class WrappingData { - public final Cipher cipher; - public final byte[] extraInfo; - - public WrappingData(final Cipher cipher, final byte[] extraInfo) { - super(); - this.cipher = cipher; - this.extraInfo = extraInfo != null ? extraInfo : EMPTY_ARRAY; - } - } - - private static class Rsa extends JceMasterKey { - // MGF1 with SHA-224 isn't really supported, but we include it in the regex because we need it - // for proper handling of the algorithm. - private static final Pattern SUPPORTED_TRANSFORMATIONS = - Pattern.compile("RSA/ECB/(?:PKCS1Padding|OAEPWith(SHA-(?:1|224|256|384|512))AndMGF1Padding)", - Pattern.CASE_INSENSITIVE); - private final AlgorithmParameterSpec parameterSpec_; - private final String transformation_; - - private Rsa(PublicKey wrappingKey, PrivateKey unwrappingKey, String providerName, String keyId, - String transformation) { - super(wrappingKey, unwrappingKey, providerName, keyId); - - final Matcher matcher = SUPPORTED_TRANSFORMATIONS.matcher(transformation); - if (matcher.matches()) { - final String hashUnknownCase = matcher.group(1); - if (hashUnknownCase != null) { - // OAEP mode a.k.a PKCS #1v2 - final String hash = hashUnknownCase.toUpperCase(); - transformation_ = "RSA/ECB/OAEPPadding"; - - final MGF1ParameterSpec mgf1Spec; - switch (hash) { - case "SHA-1": - mgf1Spec = MGF1ParameterSpec.SHA1; - break; - case "SHA-224": - LOGGER.warning(transformation + " is not officially supported by the JceMasterKey"); - mgf1Spec = MGF1ParameterSpec.SHA224; - break; - case "SHA-256": - mgf1Spec = MGF1ParameterSpec.SHA256; - break; - case "SHA-384": - mgf1Spec = MGF1ParameterSpec.SHA384; - break; - case "SHA-512": - mgf1Spec = MGF1ParameterSpec.SHA512; - break; - default: - throw new IllegalArgumentException("Unsupported algorithm: " + transformation); - } - parameterSpec_ = new OAEPParameterSpec(hash, "MGF1", mgf1Spec, PSource.PSpecified.DEFAULT); - } else { - // PKCS #1 v1.x - transformation_ = transformation; - parameterSpec_ = null; - } - } else { - LOGGER.warning(transformation + " is not officially supported by the JceMasterKey"); - // Unsupported transformation, just use exactly what we are given - transformation_ = transformation; - parameterSpec_ = null; - } - } - - @Override - protected WrappingData buildWrappingCipher(Key key, Map encryptionContext) - throws GeneralSecurityException { - // We require BouncyCastle to avoid some bugs in the default Java implementation - // of OAEP. - final Cipher cipher = Cipher.getInstance(transformation_); - cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec_); - return new WrappingData(cipher, EMPTY_ARRAY); - } - - @Override - protected Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset, - Map encryptionContext) throws GeneralSecurityException { - if (extraInfo.length != offset) { - throw new IllegalArgumentException("Extra info must be empty for RSA keys"); - } - // We require BouncyCastle to avoid some bugs in the default Java implementation - // of OAEP. - final Cipher cipher = Cipher.getInstance(transformation_); - cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec_); - return cipher; - } - } - - private static class AesGcm extends JceMasterKey { - private static final int NONCE_LENGTH = 12; - private static final int TAG_LENGTH = 128; - private static final String TRANSFORMATION = "AES/GCM/NoPadding"; - - private final SecureRandom rnd = new SecureRandom(); - - public AesGcm(final SecretKey key, final String providerName, final String keyId) { - super(key, key, providerName, keyId); - } - - private static byte[] specToBytes(final GCMParameterSpec spec) { - final byte[] nonce = spec.getIV(); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (final DataOutputStream dos = new DataOutputStream(baos)) { - dos.writeInt(spec.getTLen()); - dos.writeInt(nonce.length); - dos.write(nonce); - dos.close(); - baos.close(); - } catch (final IOException ex) { - throw new AssertionError("Impossible exception", ex); - } - return baos.toByteArray(); - } - - private static GCMParameterSpec bytesToSpec(final byte[] data, final int offset) { - final ByteArrayInputStream bais = new ByteArrayInputStream(data, offset, data.length - offset); - try (final DataInputStream dis = new DataInputStream(bais)) { - final int tagLen = dis.readInt(); - final int nonceLen = dis.readInt(); - final byte[] nonce = new byte[nonceLen]; - dis.readFully(nonce); - return new GCMParameterSpec(tagLen, nonce); - } catch (final IOException ex) { - throw new AssertionError("Impossible exception", ex); - } - } - - @Override - protected WrappingData buildWrappingCipher(final Key key, final Map encryptionContext) - throws GeneralSecurityException { - final byte[] nonce = new byte[NONCE_LENGTH]; - rnd.nextBytes(nonce); - final GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH, nonce); - final Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, key, spec); - final byte[] aad = EncryptionContextSerializer.serialize(encryptionContext); - cipher.updateAAD(aad); - return new WrappingData(cipher, specToBytes(spec)); - } - - @Override - protected Cipher buildUnwrappingCipher(final Key key, final byte[] extraInfo, final int offset, - final Map encryptionContext) throws GeneralSecurityException { - final GCMParameterSpec spec = bytesToSpec(extraInfo, offset); - final Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.DECRYPT_MODE, key, spec); - final byte[] aad = EncryptionContextSerializer.serialize(encryptionContext); - cipher.updateAAD(aad); - return cipher; - } - } } diff --git a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java new file mode 100644 index 000000000..5d162679d --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.Test; + +public class BasicEncryptionExampleTest { + + @Test + public void testEncryptAndDecrypt() { + BasicEncryptionExample.encryptAndDecrypt(KMSTestFixtures.TEST_KEY_IDS[0]); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/UtilsTest.java b/src/test/java/com/amazonaws/encryptionsdk/UtilsTest.java index 50987611f..7a2013023 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/UtilsTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/UtilsTest.java @@ -2,6 +2,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; @@ -121,5 +122,17 @@ public void testBigIntegerToByteArray_InvalidLength() { Utils.bigIntegerToByteArray(new BigInteger(bytes), 3)); } + @Test + public void testArrayPrefixEquals() { + byte[] a = new byte[] {10, 11, 12, 13, 14, 15}; + byte[] b = new byte[] {10, 11, 12, 13, 20, 21, 22}; + + assertFalse(Utils.arrayPrefixEquals(null, b, 4)); + assertFalse(Utils.arrayPrefixEquals(a, null, 4)); + assertFalse(Utils.arrayPrefixEquals(a, b, a.length + 1)); + assertTrue(Utils.arrayPrefixEquals(a, b, 4)); + assertFalse(Utils.arrayPrefixEquals(a, b, 5)); + } + } diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java index 486c52579..1cd53370b 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KMSTestFixtures.java @@ -1,6 +1,6 @@ package com.amazonaws.encryptionsdk.kms; -final class KMSTestFixtures { +public final class KMSTestFixtures { private KMSTestFixtures() { throw new UnsupportedOperationException( "This class exists to hold static constants and cannot be instantiated." @@ -14,7 +14,7 @@ private KMSTestFixtures() { * This should go without saying, but never use these keys for production purposes (as anyone in the world can * decrypt data encrypted using them). */ - static final String[] TEST_KEY_IDS = new String[] { + public static final String[] TEST_KEY_IDS = new String[] { "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f", "arn:aws:kms:eu-central-1:658956600833:key/75414c93-5285-4b57-99c9-30c1cf0a22c2" }; From 36958b836454cb10eb5f20be73e71d322560cd28 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Thu, 21 Nov 2019 13:01:51 -0800 Subject: [PATCH 03/18] Defining Keyring interface, RawAesKeyring and RawRsaKeyring. (#142) * Defining Keyring interface, RawAesKeyring and RawRsaKeyring. *Issue #, if available:* #102 *Description of changes:* This change defines the Keyring interface, RawAesKeyring and RawRsaKeyring. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. * Moving factory methods to StandardKeyrings and correcting RawAes trace. * Adding additional tests for RawAesKeyring and RawRsaKeyring * Creating separate Encryption/Decryption materials for Keyring usage To maintain backward compatibility with MasterKey/MasterKeyProviders, new EncryptionMaterials and DecryptionMaterials classes are defined for use in Keyrings, so they can include names inline with the spec and additional validation. This change also adds test dependencies for JUnit5. * Minor formatting fixes * Fixing comments and migrating KeyringTraceTest to JUnit5 * Renaming algorithm to algorithmSuite * Making optional materials properties throw exceptions if not populated. * Using Objects.requireNonNull and renaming builder methods --- pom.xml | 19 +- .../encryptionsdk/EncryptedDataKey.java | 4 + .../keyrings/DecryptionMaterials.java | 227 +++++++++++++++ .../keyrings/EncryptionMaterials.java | 270 ++++++++++++++++++ .../encryptionsdk/keyrings/Keyring.java | 40 +++ .../encryptionsdk/keyrings/KeyringTrace.java | 14 +- .../keyrings/KeyringTraceEntry.java | 8 +- .../encryptionsdk/keyrings/RawAesKeyring.java | 65 +++++ .../encryptionsdk/keyrings/RawKeyring.java | 122 ++++++++ .../encryptionsdk/keyrings/RawRsaKeyring.java | 62 ++++ .../keyrings/StandardKeyrings.java | 56 ++++ .../model/DecryptionMaterials.java | 18 -- .../model/EncryptionMaterials.java | 19 +- .../encryptionsdk/model/KeyBlob.java | 4 +- .../keyrings/DecryptionMaterialsTest.java | 148 ++++++++++ .../keyrings/EncryptionMaterialsTest.java | 175 ++++++++++++ .../keyrings/KeyringTraceTest.java | 40 ++- .../keyrings/RawAesKeyringTest.java | 133 +++++++++ .../keyrings/RawKeyringTest.java | 214 ++++++++++++++ .../keyrings/RawRsaKeyringTest.java | 135 +++++++++ 20 files changed, 1700 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java diff --git a/pom.xml b/pom.xml index 6c485750c..c426db5e6 100644 --- a/pom.xml +++ b/pom.xml @@ -53,16 +53,23 @@ - org.mockito - mockito-core - 2.28.1 + org.junit.jupiter + junit-jupiter-engine + 5.5.2 + test + + + + org.junit.vintage + junit-vintage-engine + 5.5.2 test - junit - junit - 4.12 + org.mockito + mockito-junit-jupiter + 3.1.0 test diff --git a/src/main/java/com/amazonaws/encryptionsdk/EncryptedDataKey.java b/src/main/java/com/amazonaws/encryptionsdk/EncryptedDataKey.java index 4629a9e07..aa5cfeb89 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/EncryptedDataKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/EncryptedDataKey.java @@ -16,9 +16,13 @@ //@ model import java.util.Arrays; //@ model import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; //@ nullable_by_default public interface EncryptedDataKey { + + Charset PROVIDER_ENCODING = StandardCharsets.UTF_8; //@// An EncryptedDataKey object abstractly contains 3 pieces of data. //@// These are represented by 3 byte arrays: diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java new file mode 100644 index 000000000..1cf84058e --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java @@ -0,0 +1,227 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; + +import javax.crypto.SecretKey; +import java.security.PublicKey; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; + +/** + * Contains the cryptographic materials needed for a decryption operation with Keyrings. + */ +public final class DecryptionMaterials { + private final CryptoAlgorithm algorithmSuite; + private SecretKey plaintextDataKey; + private final PublicKey verificationKey; + private final Map encryptionContext; + private final KeyringTrace keyringTrace; + + private DecryptionMaterials(Builder b) { + requireNonNull(b.algorithmSuite, "algorithmSuite is required"); + requireNonNull(b.keyringTrace, "keyringTrace is required"); + requireNonNull(b.encryptionContext, "encryptionContext is required"); + validatePlaintextDataKey(b.algorithmSuite, b.plaintextDataKey); + validateVerificationKey(b.algorithmSuite, b.verificationKey); + + algorithmSuite = b.algorithmSuite; + plaintextDataKey = b.plaintextDataKey; + verificationKey = b.verificationKey; + encryptionContext = b.encryptionContext; + keyringTrace = b.keyringTrace; + } + + /** + * The algorithm suite to use for this decryption operation. + */ + public CryptoAlgorithm getAlgorithmSuite() { + return algorithmSuite; + } + + /** + * Returns true if a plaintext data key has been populated. + * + * @return True if plaintext data key is populated, false otherwise. + */ + public boolean hasPlaintextDataKey() { + return this.plaintextDataKey != null; + } + + /** + * A data key to be used as input for encryption. + * + * @return The plaintext data key. + * @throws IllegalStateException if plaintext data key has not been populated. + */ + public SecretKey getPlaintextDataKey() throws IllegalStateException { + if (!hasPlaintextDataKey()) { + throw new IllegalStateException("plaintextDataKey has not been populated"); + } + return plaintextDataKey; + } + + /** + * Sets the plaintext data key. The plaintext data key must not already be populated. + * + * @param plaintextDataKey The plaintext data key. + * @param keyringTraceEntry The keyring trace entry recording this action. + */ + public void setPlaintextDataKey(SecretKey plaintextDataKey, KeyringTraceEntry keyringTraceEntry) { + if (hasPlaintextDataKey()) { + throw new IllegalStateException("plaintextDataKey was already populated"); + } + requireNonNull(plaintextDataKey, "plaintextDataKey is required"); + requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + validatePlaintextDataKey(algorithmSuite, plaintextDataKey); + this.plaintextDataKey = plaintextDataKey; + keyringTrace.add(keyringTraceEntry); + } + + /** + * Returns true if verification key has been populated. + * + * @return True if verification key is populated, false otherwise. + */ + public boolean hasVerificationKey() { + return verificationKey != null; + } + + /** + * The verification key used for signature verification. + * + * @return The verification key. + * @throws IllegalStateException if a verification key has not been populated. + */ + public PublicKey getVerificationKey() throws IllegalStateException { + if (!hasVerificationKey()) { + throw new IllegalStateException(String.format( + "Signature verification is not supported by AlgorithmSuite %s", algorithmSuite.name())); + } + return verificationKey; + } + + public Map getEncryptionContext() { + return encryptionContext; + } + + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DecryptionMaterials that = (DecryptionMaterials) o; + return algorithmSuite == that.algorithmSuite && + Objects.equals(plaintextDataKey, that.plaintextDataKey) && + Objects.equals(verificationKey, that.verificationKey) && + Objects.equals(encryptionContext, that.encryptionContext) && + Objects.equals(keyringTrace, that.keyringTrace); + } + + @Override + public int hashCode() { + return Objects.hash(algorithmSuite, plaintextDataKey, verificationKey, encryptionContext, keyringTrace); + } + + public static Builder newBuilder(CryptoAlgorithm algorithm) { + return new Builder(algorithm); + } + + public Builder toBuilder() { + return new Builder(this); + } + + private void validatePlaintextDataKey(CryptoAlgorithm algorithmSuite, SecretKey plaintextDataKey) throws IllegalArgumentException { + if (plaintextDataKey != null) { + isTrue(algorithmSuite.getDataKeyLength() == plaintextDataKey.getEncoded().length, + String.format("Incorrect key length. Expected %s but got %s", + algorithmSuite.getDataKeyLength(), plaintextDataKey.getEncoded().length)); + isTrue(algorithmSuite.getDataKeyAlgo().equalsIgnoreCase(plaintextDataKey.getAlgorithm()), + String.format("Incorrect key algorithm. Expected %s but got %s", + algorithmSuite.getDataKeyAlgo(), plaintextDataKey.getAlgorithm())); + } + } + + /** + * Validates that a verification key is specified if and only if + * the given algorithm suite supports signature verification. + */ + private void validateVerificationKey(CryptoAlgorithm algorithmSuite, PublicKey verificationKey) throws IllegalArgumentException { + if (algorithmSuite.getTrailingSignatureAlgo() == null && verificationKey != null) { + throw new IllegalArgumentException( + String.format("Algorithm Suite %s does not support signature verification", algorithmSuite.name())); + } else if (algorithmSuite.getTrailingSignatureAlgo() != null && verificationKey == null) { + throw new IllegalArgumentException( + String.format("Algorithm %s requires a verification key for signature verification", algorithmSuite.name())); + } + } + + public static final class Builder { + private CryptoAlgorithm algorithmSuite; + private SecretKey plaintextDataKey; + private PublicKey verificationKey; + private Map encryptionContext = Collections.emptyMap(); + private KeyringTrace keyringTrace = new KeyringTrace(); + + private Builder(CryptoAlgorithm algorithmSuite) { + this.algorithmSuite = algorithmSuite; + } + + private Builder(DecryptionMaterials result) { + this.algorithmSuite = result.algorithmSuite; + this.plaintextDataKey = result.plaintextDataKey; + this.verificationKey = result.verificationKey; + this.encryptionContext = result.encryptionContext; + this.keyringTrace = result.keyringTrace; + } + + public Builder algorithmSuite(CryptoAlgorithm algorithmSuite) { + this.algorithmSuite = algorithmSuite; + return this; + } + + public Builder plaintextDataKey(SecretKey plaintextDataKey) { + this.plaintextDataKey = plaintextDataKey; + return this; + } + + public Builder verificationKey(PublicKey verificationKey) { + this.verificationKey = verificationKey; + return this; + } + + public Builder encryptionContext(Map encryptionContext) { + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + public Builder keyringTrace(KeyringTrace keyringTrace) { + this.keyringTrace = keyringTrace; + return this; + } + + public DecryptionMaterials build() { + return new DecryptionMaterials(this); + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java new file mode 100644 index 000000000..d52f5d355 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java @@ -0,0 +1,270 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; + +import javax.crypto.SecretKey; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; + +/** + * Contains the cryptographic materials needed for an encryption operation with Keyrings. + */ +public final class EncryptionMaterials { + private final CryptoAlgorithm algorithmSuite; + private final Map encryptionContext; + private final List encryptedDataKeys; + private SecretKey plaintextDataKey; + private final PrivateKey signingKey; + private final KeyringTrace keyringTrace; + + private EncryptionMaterials(Builder b) { + requireNonNull(b.algorithmSuite, "algorithmSuite is required"); + requireNonNull(b.keyringTrace, "keyringTrace is required"); + requireNonNull(b.encryptionContext, "encryptionContext is required"); + validatePlaintextDataKey(b.algorithmSuite, b.plaintextDataKey); + validateSigningKey(b.algorithmSuite, b.signingKey); + this.algorithmSuite = b.algorithmSuite; + this.encryptionContext = b.encryptionContext; + this.encryptedDataKeys = b.encryptedDataKeys; + this.plaintextDataKey = b.plaintextDataKey; + this.signingKey = b.signingKey; + this.keyringTrace = b.keyringTrace; + } + + public Builder toBuilder() { + return new Builder(this); + } + + public static Builder newBuilder(CryptoAlgorithm algorithmSuite) { + return new Builder(algorithmSuite); + } + + /** + * The algorithm suite to be used for encryption. + */ + public CryptoAlgorithm getAlgorithmSuite() { + return algorithmSuite; + } + + /** + * The encryption context associated with this encryption. + */ + public Map getEncryptionContext() { + return encryptionContext; + } + + /** + * An unmodifiable list of the encrypted data keys that correspond to the plaintext data key. + */ + public List getEncryptedDataKeys() { + return Collections.unmodifiableList(encryptedDataKeys); + } + + /** + * Add an encrypted data key to the list of encrypted data keys. + * + * @param encryptedDataKey The encrypted data key to add. + * @param keyringTraceEntry The keyring trace entry recording this action. + */ + public void addEncryptedDataKey(EncryptedDataKey encryptedDataKey, KeyringTraceEntry keyringTraceEntry) { + requireNonNull(encryptedDataKey, "encryptedDataKey is required"); + requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + encryptedDataKeys.add(encryptedDataKey); + keyringTrace.add(keyringTraceEntry); + } + + /** + * Returns true if a plaintext data key has been populated. + * + * @return True if plaintext data key is populated, false otherwise. + */ + public boolean hasPlaintextDataKey() { + return this.plaintextDataKey != null; + } + + /** + * A data key to be used as input for encryption. + * + * @return The plaintext data key. + * @throws IllegalStateException if plain text data key has not been populated. + */ + public SecretKey getPlaintextDataKey() throws IllegalStateException { + if (!hasPlaintextDataKey()) { + throw new IllegalStateException("plaintextDataKey has not been populated"); + } + return plaintextDataKey; + } + + /** + * Sets the plaintext data key. The plaintext data key must not already be populated. + * + * @param plaintextDataKey The plaintext data key. + * @param keyringTraceEntry The keyring trace entry recording this action. + */ + public void setPlaintextDataKey(SecretKey plaintextDataKey, KeyringTraceEntry keyringTraceEntry) { + if (hasPlaintextDataKey()) { + throw new IllegalStateException("plaintextDataKey was already populated"); + } + requireNonNull(plaintextDataKey, "plaintextDataKey is required"); + requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + validatePlaintextDataKey(algorithmSuite, plaintextDataKey); + this.plaintextDataKey = plaintextDataKey; + keyringTrace.add(keyringTraceEntry); + } + + /** + * Returns true if a signing key has been populated. + * + * @return True if signing key is populated, false otherwise. + */ + public boolean hasSigningKey() { + return this.signingKey != null; + } + + + /** + * The key to be used as the signing key for signature verification during encryption. + * + * @return The signing key. + * @throws IllegalStateException if signing key has not been populated. + */ + public PrivateKey getSigningKey() throws IllegalStateException { + if (!hasSigningKey()) { + throw new IllegalStateException(String.format( + "Signing is not supported by AlgorithmSuite %s", algorithmSuite.name())); + } + return signingKey; + } + + /** + * A keyring trace containing all of the actions that keyrings have taken on this set of encryption materials. + */ + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + /** + * Validates that the given plaintext data key fits the specification + * for the data key algorithm specified in the given algorithm suite. + */ + private void validatePlaintextDataKey(CryptoAlgorithm algorithmSuite, SecretKey plaintextDataKey) throws IllegalArgumentException { + if (plaintextDataKey != null) { + isTrue(algorithmSuite.getDataKeyLength() == plaintextDataKey.getEncoded().length, + String.format("Incorrect data key length. Expected %s but got %s", + algorithmSuite.getDataKeyLength(), plaintextDataKey.getEncoded().length)); + isTrue(algorithmSuite.getDataKeyAlgo().equalsIgnoreCase(plaintextDataKey.getAlgorithm()), + String.format("Incorrect data key algorithm. Expected %s but got %s", + algorithmSuite.getDataKeyAlgo(), plaintextDataKey.getAlgorithm())); + } + } + + /** + * Validates that a signing key is specified only if and only if + * the given algorithm suite supports signature verification. + */ + private void validateSigningKey(CryptoAlgorithm algorithmSuite, PrivateKey signingKey) throws IllegalArgumentException { + if (algorithmSuite.getTrailingSignatureAlgo() == null && signingKey != null) { + throw new IllegalArgumentException( + String.format("Algorithm Suite %s does not support signing", algorithmSuite.name())); + } else if (algorithmSuite.getTrailingSignatureAlgo() != null && signingKey == null) { + throw new IllegalArgumentException( + String.format("Algorithm Suite %s requires a signing key for signing", algorithmSuite.name())); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EncryptionMaterials that = (EncryptionMaterials) o; + return algorithmSuite == that.algorithmSuite && + Objects.equals(encryptionContext, that.encryptionContext) && + Objects.equals(encryptedDataKeys, that.encryptedDataKeys) && + Objects.equals(plaintextDataKey, that.plaintextDataKey) && + Objects.equals(signingKey, that.signingKey) && + Objects.equals(keyringTrace, that.keyringTrace); + } + + @Override + public int hashCode() { + return Objects.hash(algorithmSuite, encryptionContext, encryptedDataKeys, plaintextDataKey, signingKey, keyringTrace); + } + + public static class Builder { + private CryptoAlgorithm algorithmSuite; + private Map encryptionContext = Collections.emptyMap(); + private List encryptedDataKeys = new ArrayList<>(); + private SecretKey plaintextDataKey; + private PrivateKey signingKey; + private KeyringTrace keyringTrace = new KeyringTrace(); + + private Builder(CryptoAlgorithm algorithmSuite) { + this.algorithmSuite = algorithmSuite; + } + + private Builder(EncryptionMaterials r) { + algorithmSuite = r.algorithmSuite; + encryptionContext = r.encryptionContext; + encryptedDataKeys = r.encryptedDataKeys; + plaintextDataKey = r.plaintextDataKey; + signingKey = r.signingKey; + keyringTrace = r.keyringTrace; + } + + public EncryptionMaterials build() { + return new EncryptionMaterials(this); + } + + public Builder algorithmSuite(CryptoAlgorithm algorithmSuite) { + this.algorithmSuite = algorithmSuite; + return this; + } + + public Builder encryptionContext(Map encryptionContext) { + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + public Builder encryptedDataKeys(List encryptedDataKeys) { + this.encryptedDataKeys = new ArrayList<>(encryptedDataKeys); + return this; + } + + public Builder plaintextDataKey(SecretKey plaintextDataKey) { + this.plaintextDataKey = plaintextDataKey; + return this; + } + + public Builder signingKey(PrivateKey signingKey) { + this.signingKey = signingKey; + return this; + } + + public Builder keyringTrace(KeyringTrace keyringTrace) { + this.keyringTrace = keyringTrace; + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java new file mode 100644 index 000000000..20f4c69f5 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; + +import java.util.List; + +/** + * Keyrings are responsible for the generation, encryption, and decryption of data keys. + */ +public interface Keyring { + + /** + * Attempt to encrypt either the given data key (if present) or one that may be generated + * + * @param encryptionMaterials Materials needed for encryption that the keyring may modify. + */ + void onEncrypt(EncryptionMaterials encryptionMaterials); + + /** + * Attempt to decrypt the encrypted data keys + * + * @param decryptionMaterials Materials needed for decryption that the keyring may modify. + * @param encryptedDataKeys List of encrypted data keys. + */ + void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys); + +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java index 7759d70d7..c9528c08f 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java @@ -17,9 +17,7 @@ import org.apache.commons.lang3.builder.ToStringStyle; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; /** @@ -38,8 +36,16 @@ public class KeyringTrace { * indicating what actions were taken by a keyring. */ public void add(String keyNamespace, String keyName, KeyringTraceFlag... flags) { - entries.add(new KeyringTraceEntry(keyNamespace, keyName, - new HashSet<>(Arrays.asList(flags)))); + add(new KeyringTraceEntry(keyNamespace, keyName, flags)); + } + + /** + * Add a new entry to the keyring trace. + * + * @param entry The entry to add. + */ + public void add(KeyringTraceEntry entry) { + entries.add(entry); } /** diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java index 64a14a268..4f656c7be 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceEntry.java @@ -16,7 +16,9 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -37,17 +39,17 @@ public class KeyringTraceEntry { * * @param keyNamespace The namespace for the key. * @param keyName The name of the key. - * @param flags A set of one or more KeyringTraceFlag enums + * @param flags One or more KeyringTraceFlags * indicating what actions were taken by a keyring. */ - KeyringTraceEntry(final String keyNamespace, final String keyName, final Set flags) { + public KeyringTraceEntry(final String keyNamespace, final String keyName, final KeyringTraceFlag... flags) { notBlank(keyNamespace, "keyNamespace is required"); notBlank(keyName, "keyName is required"); notEmpty(flags, "At least one flag is required"); this.keyNamespace = keyNamespace; this.keyName = keyName; - this.flags = Collections.unmodifiableSet(flags); + this.flags = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(flags))); } /** diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java new file mode 100644 index 000000000..5832658a9 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.internal.JceKeyCipher; +import com.amazonaws.encryptionsdk.internal.Utils; + +import javax.crypto.SecretKey; + +/** + * A {@code Keyring} which does local AES-GCM encryption + * decryption of data keys using the provided wrapping key. + *

+ * Instantiate by using the {@code StandardKeyrings.rawAes(...)} factory method. + */ +class RawAesKeyring extends RawKeyring { + + RawAesKeyring(String keyNamespace, String keyName, SecretKey wrappingKey) { + super(keyNamespace, keyName, JceKeyCipher.aesGcm(wrappingKey)); + } + + @Override + boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { + + // the key provider ID of the encrypted data key must + // have a value equal to this keyring's key namespace. + if (!keyNamespace.equals(encryptedDataKey.getProviderId())) { + return false; + } + + // the key name obtained from the encrypted data key's key provider + // information must have a value equal to this keyring's key name. + if (!Utils.arrayPrefixEquals(encryptedDataKey.getProviderInformation(), keyNameBytes, keyNameBytes.length)) { + return false; + } + + return true; + } + + @Override + KeyringTraceEntry traceOnEncrypt() { + return new KeyringTraceEntry(keyNamespace, keyName, + KeyringTraceFlag.ENCRYPTED_DATA_KEY, + KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT); + } + + @Override + KeyringTraceEntry traceOnDecrypt() { + return new KeyringTraceEntry(keyNamespace, keyName, + KeyringTraceFlag.DECRYPTED_DATA_KEY, + KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java new file mode 100644 index 000000000..8daa567f8 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java @@ -0,0 +1,122 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.internal.JceKeyCipher; +import com.amazonaws.encryptionsdk.internal.Utils; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import java.util.List; +import java.util.logging.Logger; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.notBlank; + +/** + * A keyring supporting local encryption and decryption using either RSA or AES-GCM. + */ +abstract class RawKeyring implements Keyring { + + final String keyNamespace; + final String keyName; + final byte[] keyNameBytes; + private final JceKeyCipher jceKeyCipher; + private static final Logger LOGGER = Logger.getLogger(RawKeyring.class.getName()); + + RawKeyring(final String keyNamespace, final String keyName, JceKeyCipher jceKeyCipher) { + notBlank(keyNamespace, "keyNamespace is required"); + notBlank(keyName, "keyName is required"); + requireNonNull(jceKeyCipher, "jceKeyCipher is required"); + + this.keyNamespace = keyNamespace; + this.keyName = keyName; + this.keyNameBytes = keyName.getBytes(PROVIDER_ENCODING); + this.jceKeyCipher = jceKeyCipher; + } + + /** + * Returns true if the given encrypted data key may be decrypted with this keyring. + * + * @param encryptedDataKey The encrypted data key. + * @return True if the key may be decrypted, false otherwise. + */ + abstract boolean validToDecrypt(EncryptedDataKey encryptedDataKey); + + /** + * Gets the trace entry to add the the keyring trace upon successful encryption. + * + * @return The keyring trace entry. + */ + abstract KeyringTraceEntry traceOnEncrypt(); + + /** + * Gets the trace entry to add to the keyring trace upon successful decryption. + * + * @return The keyring trace entry. + */ + abstract KeyringTraceEntry traceOnDecrypt(); + + @Override + public void onEncrypt(EncryptionMaterials encryptionMaterials) { + requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + + if (!encryptionMaterials.hasPlaintextDataKey()) { + generateDataKey(encryptionMaterials); + } + + final EncryptedDataKey encryptedDataKey = jceKeyCipher.encryptKey( + encryptionMaterials.getPlaintextDataKey().getEncoded(), + keyName, keyNamespace, encryptionMaterials.getEncryptionContext()); + encryptionMaterials.addEncryptedDataKey(encryptedDataKey, traceOnEncrypt()); + } + + @Override + public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasPlaintextDataKey() || encryptedDataKeys.isEmpty()) { + return; + } + + for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { + if (validToDecrypt(encryptedDataKey)) { + try { + final byte[] decryptedKey = jceKeyCipher.decryptKey( + encryptedDataKey, keyName, decryptionMaterials.getEncryptionContext()); + decryptionMaterials.setPlaintextDataKey( + new SecretKeySpec(decryptedKey, decryptionMaterials.getAlgorithmSuite().getDataKeyAlgo()), + traceOnDecrypt()); + return; + } catch (Exception e) { + LOGGER.info("Could not decrypt key due to: " + e.getMessage()); + } + } + } + + LOGGER.warning("Could not decrypt any data keys"); + } + + private void generateDataKey(EncryptionMaterials encryptionMaterials) { + final byte[] rawKey = new byte[encryptionMaterials.getAlgorithmSuite().getDataKeyLength()]; + Utils.getSecureRandom().nextBytes(rawKey); + final SecretKey key = new SecretKeySpec(rawKey, encryptionMaterials.getAlgorithmSuite().getDataKeyAlgo()); + + encryptionMaterials.setPlaintextDataKey(key, new KeyringTraceEntry(keyNamespace, keyName, KeyringTraceFlag.GENERATED_DATA_KEY)); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java new file mode 100644 index 000000000..486cd26e6 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.internal.JceKeyCipher; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Arrays; + +/** + * A {@link Keyring} which does local RSA encryption and decryption of data keys using the + * provided public and private keys. + *

+ * Instantiate by using the {@code StandardKeyrings.rawRsa(...)} factory method. + */ +class RawRsaKeyring extends RawKeyring { + + RawRsaKeyring(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String transformation) { + super(keyNamespace, keyName, JceKeyCipher.rsa(publicKey, privateKey, transformation)); + } + + @Override + boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { + + // the key provider ID of the encrypted data key must + // have a value equal to this keyring's key namespace. + if (!keyNamespace.equals(encryptedDataKey.getProviderId())) { + return false; + } + + // the encrypted data key's key provider information + // must have a value equal to this keyring's key name. + if (!Arrays.equals(encryptedDataKey.getProviderInformation(), keyNameBytes)) { + return false; + } + + return true; + } + + @Override + KeyringTraceEntry traceOnEncrypt() { + return new KeyringTraceEntry(keyNamespace, keyName, KeyringTraceFlag.ENCRYPTED_DATA_KEY); + } + + @Override + KeyringTraceEntry traceOnDecrypt() { + return new KeyringTraceEntry(keyNamespace, keyName, KeyringTraceFlag.DECRYPTED_DATA_KEY); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java new file mode 100644 index 000000000..d36dae07e --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import javax.crypto.SecretKey; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * Factory methods for instantiating the standard {@code Keyring}s provided by the AWS Encryption SDK. + */ +public class StandardKeyrings { + + private StandardKeyrings() { + } + + /** + * Constructs a {@code Keyring} which does local AES-GCM encryption + * decryption of data keys using the provided wrapping key. + * + * @param keyNamespace A value that, together with the key name, identifies the wrapping key. + * @param keyName A value that, together with the key namespace, identifies the wrapping key. + * @param wrappingKey The AES key input to AES-GCM to encrypt plaintext data keys. + * @return The {@link Keyring} + */ + public static Keyring rawAes(String keyNamespace, String keyName, SecretKey wrappingKey) { + return new RawAesKeyring(keyNamespace, keyName, wrappingKey); + } + + /** + * Constructs a {@code Keyring} which does local RSA encryption and decryption of data keys using the + * provided public and private keys. If {@code privateKey} is {@code null} then the returned {@code Keyring} + * can only be used for encryption. + * + * @param keyNamespace A value that, together with the key name, identifies the wrapping key. + * @param keyName A value that, together with the key namespace, identifies the wrapping key. + * @param publicKey The RSA public key used by this keyring to encrypt data keys. + * @param privateKey The RSA private key used by this keyring to decrypt data keys. + * @param wrappingAlgorithm The RSA algorithm to use with this keyring. + * @return The {@link Keyring} + */ + public static Keyring rawRsa(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String wrappingAlgorithm) { + return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java index 0c0ba52c7..94423b884 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java @@ -3,17 +3,14 @@ import java.security.PublicKey; import com.amazonaws.encryptionsdk.DataKey; -import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; public final class DecryptionMaterials { private final DataKey dataKey; private final PublicKey trailingSignatureKey; - private final KeyringTrace keyringTrace; private DecryptionMaterials(Builder b) { dataKey = b.getDataKey(); trailingSignatureKey = b.getTrailingSignatureKey(); - keyringTrace = b.getKeyringTrace(); } public DataKey getDataKey() { @@ -24,10 +21,6 @@ public PublicKey getTrailingSignatureKey() { return trailingSignatureKey; } - public KeyringTrace getKeyringTrace() { - return keyringTrace; - } - public static Builder newBuilder() { return new Builder(); } @@ -39,12 +32,10 @@ public Builder toBuilder() { public static final class Builder { private DataKey dataKey; private PublicKey trailingSignatureKey; - private KeyringTrace keyringTrace; private Builder(DecryptionMaterials result) { this.dataKey = result.getDataKey(); this.trailingSignatureKey = result.getTrailingSignatureKey(); - this.keyringTrace = result.getKeyringTrace(); } private Builder() {} @@ -67,15 +58,6 @@ public Builder setTrailingSignatureKey(PublicKey trailingSignatureKey) { return this; } - public KeyringTrace getKeyringTrace() { - return keyringTrace; - } - - public Builder setKeyringTrace(KeyringTrace keyringTrace) { - this.keyringTrace = keyringTrace; - return this; - } - public DecryptionMaterials build() { return new DecryptionMaterials(this); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java index f9b05a153..1a40d7c36 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java @@ -11,7 +11,6 @@ import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.MasterKey; -import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; /** * Contains the cryptographic materials needed for an encryption operation. @@ -25,7 +24,6 @@ public final class EncryptionMaterials { private final SecretKey cleartextDataKey; private final PrivateKey trailingSignatureKey; private final List masterKeys; - private final KeyringTrace keyringTrace; private EncryptionMaterials(Builder b) { this.algorithm = b.algorithm; @@ -34,7 +32,6 @@ private EncryptionMaterials(Builder b) { this.cleartextDataKey = b.cleartextDataKey; this.trailingSignatureKey = b.trailingSignatureKey; this.masterKeys = b.getMasterKeys(); - this.keyringTrace = b.keyringTrace; } public Builder toBuilder() { @@ -103,13 +100,12 @@ public List getMasterKeys() { Objects.equals(encryptedDataKeys, that.encryptedDataKeys) && Objects.equals(cleartextDataKey, that.cleartextDataKey) && Objects.equals(trailingSignatureKey, that.trailingSignatureKey) && - Objects.equals(masterKeys, that.masterKeys) && - Objects.equals(keyringTrace, that.keyringTrace); + Objects.equals(masterKeys, that.masterKeys); } @Override public int hashCode() { return Objects.hash(algorithm, encryptionContext, encryptedDataKeys, cleartextDataKey, trailingSignatureKey, - masterKeys, keyringTrace); + masterKeys); } public static class Builder { @@ -119,7 +115,6 @@ public static class Builder { private SecretKey cleartextDataKey; private PrivateKey trailingSignatureKey; private List masterKeys = Collections.emptyList(); - private KeyringTrace keyringTrace; private Builder() {} @@ -130,7 +125,6 @@ private Builder(EncryptionMaterials r) { cleartextDataKey = r.cleartextDataKey; trailingSignatureKey = r.trailingSignatureKey; setMasterKeys(r.masterKeys); - keyringTrace = r.keyringTrace; } public EncryptionMaterials build() { @@ -190,14 +184,5 @@ public Builder setMasterKeys(List masterKeys) { this.masterKeys = Collections.unmodifiableList(new ArrayList<>(masterKeys)); return this; } - - public KeyringTrace getKeyringTrace() { - return keyringTrace; - } - - public Builder setKeyringTrace(KeyringTrace keyringTrace) { - this.keyringTrace = keyringTrace; - return this; - } } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/KeyBlob.java b/src/main/java/com/amazonaws/encryptionsdk/model/KeyBlob.java index c44fd2f8f..dbea9f6b4 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/KeyBlob.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/KeyBlob.java @@ -542,7 +542,7 @@ public int getKeyProviderIdLen() { */ @Override public String getProviderId() { - String s = new String(keyProviderId_, StandardCharsets.UTF_8); + String s = new String(keyProviderId_, PROVIDER_ENCODING); // The following assume statement essentially says that different // calls to the String constructor above, with the same parameters, // result in strings with the same contents. The assumption is @@ -627,7 +627,7 @@ public byte[] getEncryptedDataKey() { //@ assignable \nothing; //@ signals_only AwsCryptoException; public void setKeyProviderId(final String keyProviderId) { - final byte[] keyProviderIdBytes = keyProviderId.getBytes(StandardCharsets.UTF_8); + final byte[] keyProviderIdBytes = keyProviderId.getBytes(PROVIDER_ENCODING); //@ assume Arrays.equalArrays(keyProviderIdBytes, EncryptedDataKey.s2ba(keyProviderId)); if (keyProviderIdBytes.length > Constants.UNSIGNED_SHORT_MAX_VAL) { throw new AwsCryptoException( diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java new file mode 100644 index 000000000..68687d4ad --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PublicKey; +import java.util.Collections; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class DecryptionMaterialsTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("testKey", "testValue"); + private static final KeyringTrace KEYRING_TRACE = new KeyringTrace(); + private static final KeyringTraceEntry KEYRING_TRACE_ENTRY = new KeyringTraceEntry("Namespace", "Name", KeyringTraceFlag.ENCRYPTED_DATA_KEY); + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static PublicKey VERIFICATION_KEY; + + @BeforeAll + static void setup() throws Exception { + + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + final KeyPair keyPair = TrailingSignatureAlgorithm.forCryptoAlgorithm(ALGORITHM_SUITE).generateKey(); + VERIFICATION_KEY = keyPair.getPublic(); + } + + @Test + void testBuilderNullCryptoAlgorithm() { + assertThrows(NullPointerException.class, () -> DecryptionMaterials.newBuilder(null).build()); + } + + @Test + void testBuilder() { + DecryptionMaterials result = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(KEYRING_TRACE) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .verificationKey(VERIFICATION_KEY) + .build(); + + assertEquals(ALGORITHM_SUITE, result.getAlgorithmSuite()); + assertEquals(ENCRYPTION_CONTEXT, result.getEncryptionContext()); + assertEquals(KEYRING_TRACE, result.getKeyringTrace()); + assertEquals(PLAINTEXT_DATA_KEY, result.getPlaintextDataKey()); + assertEquals(VERIFICATION_KEY, result.getVerificationKey()); + } + + @Test + void testInvalidPlaintextDataKey() { + SecretKey wrongLength = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength() + 1), ALGORITHM_SUITE.getDataKeyAlgo()); + assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .plaintextDataKey(wrongLength) + .verificationKey(VERIFICATION_KEY) + .build()); + + SecretKey wrongAlgorithm = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), "InvalidAlgorithm"); + assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .plaintextDataKey(wrongAlgorithm) + .verificationKey(VERIFICATION_KEY) + .build()); + + DecryptionMaterials materials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .verificationKey(VERIFICATION_KEY) + .build(); + assertThrows(IllegalArgumentException.class, () -> materials + .setPlaintextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); + assertThrows(IllegalArgumentException.class, () -> materials + .setPlaintextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); + } + + @Test + void testInvalidVerificationKey() { + assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .verificationKey(null) + .build()); + assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) + .verificationKey(VERIFICATION_KEY) + .build()); + + } + + @Test + void testToBuilder() { + DecryptionMaterials expected = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(KEYRING_TRACE) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .verificationKey(VERIFICATION_KEY) + .build(); + + DecryptionMaterials actual = expected.toBuilder().build(); + + assertEquals(expected, actual); + assertNotSame(expected, actual); + } + + @Test + void testSetPlaintextDataKey() { + DecryptionMaterials materials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .verificationKey(VERIFICATION_KEY) + .build(); + + assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, null)); + + materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(PLAINTEXT_DATA_KEY, materials.getPlaintextDataKey()); + assertEquals(1, materials.getKeyringTrace().getEntries().size()); + assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); + + assertThrows(IllegalStateException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); + } + + @Test + void testGetOptionalProperties() { + DecryptionMaterials materials = DecryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) + .build(); + + assertThrows(IllegalStateException.class, materials::getPlaintextDataKey); + assertThrows(IllegalStateException.class, materials::getVerificationKey); + } + +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java new file mode 100644 index 000000000..d1c207dbd --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java @@ -0,0 +1,175 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.util.Collections; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class EncryptionMaterialsTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("testKey", "testValue"); + private static final KeyringTrace KEYRING_TRACE = new KeyringTrace(); + private static final KeyringTraceEntry KEYRING_TRACE_ENTRY = new KeyringTraceEntry("Namespace", "Name", KeyringTraceFlag.ENCRYPTED_DATA_KEY); + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + @Mock + private static EncryptedDataKey ENCRYPTED_DATA_KEY; + private static PrivateKey SIGNING_KEY; + + @BeforeAll + static void setup() throws Exception { + + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + final KeyPair keyPair = TrailingSignatureAlgorithm.forCryptoAlgorithm(ALGORITHM_SUITE).generateKey(); + SIGNING_KEY = keyPair.getPrivate(); + } + + @Test + void testBuilderNullCryptoAlgorithm() { + assertThrows(NullPointerException.class, () -> EncryptionMaterials.newBuilder(null).build()); + } + + @Test + void testBuilder() { + EncryptionMaterials result = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(KEYRING_TRACE) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .encryptedDataKeys(Collections.singletonList(ENCRYPTED_DATA_KEY)) + .signingKey(SIGNING_KEY) + .build(); + + assertEquals(ALGORITHM_SUITE, result.getAlgorithmSuite()); + assertEquals(ENCRYPTION_CONTEXT, result.getEncryptionContext()); + assertEquals(KEYRING_TRACE, result.getKeyringTrace()); + assertEquals(PLAINTEXT_DATA_KEY, result.getPlaintextDataKey()); + assertEquals(1, result.getEncryptedDataKeys().size()); + assertEquals(ENCRYPTED_DATA_KEY, result.getEncryptedDataKeys().get(0)); + assertEquals(SIGNING_KEY, result.getSigningKey()); + } + + @Test + void testInvalidPlaintextDataKey() { + SecretKey wrongLength = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength() + 1), ALGORITHM_SUITE.getDataKeyAlgo()); + assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .plaintextDataKey(wrongLength) + .signingKey(SIGNING_KEY) + .build()); + + SecretKey wrongAlgorithm = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), "InvalidAlgorithm"); + assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .plaintextDataKey(wrongAlgorithm) + .signingKey(SIGNING_KEY) + .build()); + + EncryptionMaterials materials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .signingKey(SIGNING_KEY) + .build(); + assertThrows(IllegalArgumentException.class, () -> materials + .setPlaintextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); + assertThrows(IllegalArgumentException.class, () -> materials + .setPlaintextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); + } + + @Test + void testInvalidSigningKey() { + assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .signingKey(null) + .build()); + assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) + .signingKey(SIGNING_KEY) + .build()); + + } + + @Test + void testToBuilder() { + EncryptionMaterials expected = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(KEYRING_TRACE) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .encryptedDataKeys(Collections.singletonList(ENCRYPTED_DATA_KEY)) + .signingKey(SIGNING_KEY) + .build(); + + EncryptionMaterials actual = expected.toBuilder().build(); + + assertEquals(expected, actual); + assertNotSame(expected, actual); + } + + @Test + void testAddEncryptedDataKey() { + EncryptionMaterials materials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .signingKey(SIGNING_KEY) + .build(); + + assertThrows(NullPointerException.class, () -> materials.addEncryptedDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.addEncryptedDataKey(ENCRYPTED_DATA_KEY, null)); + + materials.addEncryptedDataKey(ENCRYPTED_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(1, materials.getEncryptedDataKeys().size()); + assertEquals(ENCRYPTED_DATA_KEY, materials.getEncryptedDataKeys().get(0)); + assertEquals(1, materials.getKeyringTrace().getEntries().size()); + assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testSetPlaintextDataKey() { + EncryptionMaterials materials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .signingKey(SIGNING_KEY) + .build(); + + assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, null)); + + materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(PLAINTEXT_DATA_KEY, materials.getPlaintextDataKey()); + assertEquals(1, materials.getKeyringTrace().getEntries().size()); + assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); + + assertThrows(IllegalStateException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); + } + + @Test + void testGetOptionalProperties() { + EncryptionMaterials materials = EncryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) + .build(); + + assertThrows(IllegalStateException.class, materials::getPlaintextDataKey); + assertThrows(IllegalStateException.class, materials::getSigningKey); + } + +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java index c67fcbe77..5b6105070 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java @@ -13,22 +13,19 @@ package com.amazonaws.encryptionsdk.keyrings; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static java.util.Collections.singleton; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; -public class KeyringTraceTest { +class KeyringTraceTest { @Test - public void testOrderMaintained() { - KeyringTraceEntry entry1 = new KeyringTraceEntry("ns1", "name1", - singleton(KeyringTraceFlag.GENERATED_DATA_KEY)); - KeyringTraceEntry entry2 = new KeyringTraceEntry("ns2", "name2", - singleton(KeyringTraceFlag.DECRYPTED_DATA_KEY)); - KeyringTraceEntry entry3 = new KeyringTraceEntry("ns3", "name3", - singleton(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + void testOrderMaintained() { + KeyringTraceEntry entry1 = new KeyringTraceEntry("ns1", "name1", KeyringTraceFlag.GENERATED_DATA_KEY); + KeyringTraceEntry entry2 = new KeyringTraceEntry("ns2", "name2", KeyringTraceFlag.DECRYPTED_DATA_KEY); + KeyringTraceEntry entry3 = new KeyringTraceEntry("ns3", "name3", KeyringTraceFlag.ENCRYPTED_DATA_KEY); KeyringTrace trace = new KeyringTrace(); trace.add(entry1.getKeyNamespace(), entry1.getKeyName(), entry1.getFlags().iterator().next()); @@ -40,23 +37,20 @@ public void testOrderMaintained() { assertEquals(entry3, trace.getEntries().get(2)); } - @Test(expected = UnsupportedOperationException.class) - public void testImmutable() { + @Test + void testImmutable() { KeyringTrace trace = new KeyringTrace(); trace.add("namespace", "name", KeyringTraceFlag.GENERATED_DATA_KEY); - trace.getEntries().add(new KeyringTraceEntry("ns1", "name1", - singleton(KeyringTraceFlag.GENERATED_DATA_KEY))); + assertThrows(UnsupportedOperationException.class, () -> + trace.getEntries().add(new KeyringTraceEntry("ns1", "name1", KeyringTraceFlag.GENERATED_DATA_KEY))); } @Test - public void testKeyringTraceEntryEquals() { - KeyringTraceEntry entry1 = new KeyringTraceEntry("namespace", "name", - singleton(KeyringTraceFlag.GENERATED_DATA_KEY)); - KeyringTraceEntry entry2 = new KeyringTraceEntry(entry1.getKeyNamespace(), entry1.getKeyName(), - entry1.getFlags()); - KeyringTraceEntry entry3 = new KeyringTraceEntry("othernamespace", "name", - singleton(KeyringTraceFlag.GENERATED_DATA_KEY)); + void testKeyringTraceEntryEquals() { + KeyringTraceEntry entry1 = new KeyringTraceEntry("namespace", "name", KeyringTraceFlag.GENERATED_DATA_KEY); + KeyringTraceEntry entry2 = new KeyringTraceEntry(entry1.getKeyNamespace(), entry1.getKeyName(), entry1.getFlags().toArray(new KeyringTraceFlag[]{})); + KeyringTraceEntry entry3 = new KeyringTraceEntry("othernamespace", "name", KeyringTraceFlag.GENERATED_DATA_KEY); assertEquals(entry1, entry1); assertEquals(entry1, entry2); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java new file mode 100644 index 000000000..4183e9a06 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.Test; + +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.ALGORITHM; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.KEYNAME; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.KEYNAMESPACE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RawAesKeyringTest { + + private final RawAesKeyring keyring = new RawAesKeyring(KEYNAMESPACE, KEYNAME, new SecretKeySpec(generate(32), "AES")); + + @Test + void testValidToDecrypt() { + assertTrue(keyring.validToDecrypt(new KeyBlob( + KEYNAMESPACE, KEYNAME.getBytes(StandardCharsets.UTF_8), new byte[]{}))); + assertTrue(keyring.validToDecrypt(new KeyBlob( + KEYNAMESPACE, ArrayUtils.add(KEYNAME.getBytes(StandardCharsets.UTF_8), (byte) 5), new byte[]{}))); + //Bad namespace + assertFalse(keyring.validToDecrypt(new KeyBlob( + "WrongNamespace", KEYNAME.getBytes(StandardCharsets.UTF_8), new byte[]{}))); + //Bad provider info + assertFalse(keyring.validToDecrypt(new KeyBlob( + KEYNAMESPACE, new byte[]{1,2,3}, new byte[]{}))); + } + + @Test + void testEncryptDecryptExistingDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) + .plaintextDataKey(DATA_KEY) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + + final EncryptedDataKey actualEncryptedDataKey = encryptionMaterials.getEncryptedDataKeys().get(0); + assertEquals(KEYNAMESPACE, actualEncryptedDataKey.getProviderId()); + assertTrue(Utils.arrayPrefixEquals(keyring.keyNameBytes, actualEncryptedDataKey.getProviderInformation(), keyring.keyNameBytes.length)); + assertTrue(actualEncryptedDataKey.getProviderInformation().length > keyring.keyNameBytes.length); + + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(KEYNAME, encryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); + assertEquals(KEYNAMESPACE, encryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); + assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + + assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); + assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); + assertEquals(2, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.DECRYPTED_DATA_KEY)); + assertTrue(decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + } + + @Test + void testEncryptDecryptGenerateDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onEncrypt(encryptionMaterials); + + assertNotNull(encryptionMaterials.getPlaintextDataKey()); + assertEquals(encryptionMaterials.getPlaintextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + + final EncryptedDataKey actualEncryptedDataKey = encryptionMaterials.getEncryptedDataKeys().get(0); + assertEquals(KEYNAMESPACE, actualEncryptedDataKey.getProviderId()); + assertTrue(Utils.arrayPrefixEquals(keyring.keyNameBytes, actualEncryptedDataKey.getProviderInformation(), keyring.keyNameBytes.length)); + assertTrue(actualEncryptedDataKey.getProviderInformation().length > keyring.keyNameBytes.length); + + assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.GENERATED_DATA_KEY)); + assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().contains(KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + + assertEquals(encryptionMaterials.getPlaintextDataKey(), decryptionMaterials.getPlaintextDataKey()); + assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); + assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); + assertEquals(2, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.DECRYPTED_DATA_KEY)); + assertTrue(decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + } + +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java new file mode 100644 index 000000000..945aa17bc --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java @@ -0,0 +1,214 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.internal.JceKeyCipher; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RawKeyringTest { + + static final String KEYNAME = "testKeyname"; + static final String KEYNAMESPACE = "testKeynamespace"; + static final CryptoAlgorithm ALGORITHM = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + static final SecretKey DATA_KEY = new SecretKeySpec(generate(ALGORITHM.getDataKeyLength()), ALGORITHM.getDataKeyAlgo()); + static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + private static final EncryptedDataKey ENCRYPTED_DATA_KEY = new KeyBlob("keyProviderId", new byte[]{1, 2, 3}, generate(ALGORITHM.getDataKeyLength())); + private static final EncryptedDataKey INVALID_DATA_KEY = new KeyBlob("invalidProviderId", new byte[]{1, 2, 3}, generate(ALGORITHM.getDataKeyLength())); + private static final KeyringTraceEntry ENCRYPTED_DATA_KEY_TRACE = new KeyringTraceEntry(KEYNAMESPACE, KEYNAME, KeyringTraceFlag.ENCRYPTED_DATA_KEY); + private static final KeyringTraceEntry DECRYPTED_DATA_KEY_TRACE = new KeyringTraceEntry(KEYNAMESPACE, KEYNAME, KeyringTraceFlag.DECRYPTED_DATA_KEY); + private static final KeyringTraceEntry GENERATED_DATA_KEY_TRACE = new KeyringTraceEntry(KEYNAMESPACE, KEYNAME, KeyringTraceFlag.GENERATED_DATA_KEY); + @Mock(lenient = true) private JceKeyCipher jceKeyCipher; + private Keyring keyring; + + @BeforeEach + void setup() throws Exception { + when(jceKeyCipher.encryptKey(DATA_KEY.getEncoded(), KEYNAME, KEYNAMESPACE, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_DATA_KEY); + when(jceKeyCipher.decryptKey(ENCRYPTED_DATA_KEY, KEYNAME, ENCRYPTION_CONTEXT)).thenReturn(DATA_KEY.getEncoded()); + + keyring = new RawKeyring(KEYNAMESPACE, KEYNAME, jceKeyCipher) { + @Override + boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { + return !encryptedDataKey.getProviderId().equals(INVALID_DATA_KEY.getProviderId()); + } + + @Override + KeyringTraceEntry traceOnEncrypt() { + return ENCRYPTED_DATA_KEY_TRACE; + } + + @Override + KeyringTraceEntry traceOnDecrypt() { + return DECRYPTED_DATA_KEY_TRACE; + } + }; + } + + @Test + void testEncryptDecryptExistingDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) + .plaintextDataKey(DATA_KEY) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + assertEncryptedDataKeyEquals(ENCRYPTED_DATA_KEY, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(ENCRYPTED_DATA_KEY_TRACE, encryptionMaterials.getKeyringTrace().getEntries().get(0)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); + + assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testEncryptNullDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(byte[].class); + when(jceKeyCipher.encryptKey(dataKeyCaptor.capture(), eq(KEYNAME), eq(KEYNAMESPACE), eq(ENCRYPTION_CONTEXT))).thenReturn(ENCRYPTED_DATA_KEY); + keyring.onEncrypt(encryptionMaterials); + + assertEquals(encryptionMaterials.getPlaintextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); + assertArrayEquals(encryptionMaterials.getPlaintextDataKey().getEncoded(), dataKeyCaptor.getValue()); + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + assertNotNull(encryptionMaterials.getPlaintextDataKey()); + assertEncryptedDataKeyEquals(ENCRYPTED_DATA_KEY, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(GENERATED_DATA_KEY_TRACE, encryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(ENCRYPTED_DATA_KEY_TRACE, encryptionMaterials.getKeyringTrace().getEntries().get(1)); + } + + @Test + void testDecryptAlreadyDecryptedDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .plaintextDataKey(DATA_KEY) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); + + assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testDecryptNoValidDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, Collections.singletonList(INVALID_DATA_KEY)); + + assertFalse(decryptionMaterials.hasPlaintextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testDecryptNoDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); + + assertFalse(decryptionMaterials.hasPlaintextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + + @Test + void testDecryptMultipleKeysOneInvalid() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + final List edks = new ArrayList<>(); + edks.add(INVALID_DATA_KEY); + edks.add(ENCRYPTED_DATA_KEY); + + keyring.onDecrypt(decryptionMaterials, edks); + + assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptMultipleKeysOneException() throws GeneralSecurityException { + final EncryptedDataKey BAD_DATA_KEY = new KeyBlob("exceptionProvider", new byte[]{1, 2, 3}, new byte[]{4, 5, 6}); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + when(jceKeyCipher.decryptKey(BAD_DATA_KEY, KEYNAME, ENCRYPTION_CONTEXT)) + .thenThrow(new GeneralSecurityException("could not decrypt key")); + + final List edks = new ArrayList<>(); + edks.add(BAD_DATA_KEY); + edks.add(ENCRYPTED_DATA_KEY); + + keyring.onDecrypt(decryptionMaterials, edks); + + assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + private void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { + assertEquals(expected.getProviderId(), actual.getProviderId()); + assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); + assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java new file mode 100644 index 000000000..6e43d5d37 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java @@ -0,0 +1,135 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.ALGORITHM; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.KEYNAME; +import static com.amazonaws.encryptionsdk.keyrings.RawKeyringTest.KEYNAMESPACE; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RawRsaKeyringTest { + + private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + private static RawRsaKeyring keyring; + + @BeforeAll + static void setup() throws Exception { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + keyring = new RawRsaKeyring(KEYNAMESPACE, KEYNAME, keyPair.getPublic(), keyPair.getPrivate(), TRANSFORMATION); + } + + @Test + void testValidToDecrypt() { + assertTrue(keyring.validToDecrypt(new KeyBlob( + KEYNAMESPACE, KEYNAME.getBytes(StandardCharsets.UTF_8), new byte[]{}))); + //Provider info has extra data + assertFalse(keyring.validToDecrypt(new KeyBlob( + KEYNAMESPACE, ArrayUtils.add(KEYNAME.getBytes(StandardCharsets.UTF_8), (byte)5), new byte[]{}))); + //Bad namespace + assertFalse(keyring.validToDecrypt(new KeyBlob( + "WrongNamespace", KEYNAME.getBytes(StandardCharsets.UTF_8), new byte[]{}))); + } + + @Test + void testEncryptDecryptExistingDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) + .plaintextDataKey(DATA_KEY) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + + final EncryptedDataKey actualEncryptedDataKey = encryptionMaterials.getEncryptedDataKeys().get(0); + assertEquals(KEYNAMESPACE, actualEncryptedDataKey.getProviderId()); + assertArrayEquals(keyring.keyNameBytes, actualEncryptedDataKey.getProviderInformation()); + + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(KEYNAME, encryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); + assertEquals(KEYNAMESPACE, encryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + + assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); + assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); + assertEquals(1, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.DECRYPTED_DATA_KEY)); + } + + @Test + void testEncryptDecryptGenerateDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onEncrypt(encryptionMaterials); + + assertNotNull(encryptionMaterials.getPlaintextDataKey()); + assertEquals(encryptionMaterials.getPlaintextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + + final EncryptedDataKey actualEncryptedDataKey = encryptionMaterials.getEncryptedDataKeys().get(0); + assertEquals(KEYNAMESPACE, actualEncryptedDataKey.getProviderId()); + assertArrayEquals(keyring.keyNameBytes, actualEncryptedDataKey.getProviderInformation()); + + assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.GENERATED_DATA_KEY)); + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + + assertEquals(encryptionMaterials.getPlaintextDataKey(), decryptionMaterials.getPlaintextDataKey()); + assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); + assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); + assertEquals(1, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); + assertTrue(decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.DECRYPTED_DATA_KEY)); + } + +} From b75027dc394992b4819bd9735a70e931e735ff4a Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Wed, 18 Dec 2019 13:30:37 -0800 Subject: [PATCH 04/18] Define the MultiKeyring (#148) * Define the MultiKeyring * Making defensive copy of child keyrings and adding convienance factory method --- .../encryptionsdk/keyrings/MultiKeyring.java | 102 ++++++++++ .../keyrings/StandardKeyrings.java | 28 +++ .../keyrings/MultiKeyringTest.java | 184 ++++++++++++++++++ .../org.mockito.plugins.MockMaker | 2 + 4 files changed, 316 insertions(+) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java create mode 100644 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java new file mode 100644 index 000000000..7d957b281 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java @@ -0,0 +1,102 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; + +/** + * A keyring which combines other keyrings, allowing one OnEncrypt or OnDecrypt call to + * modify the encryption or decryption materials using more than one keyring. + */ +class MultiKeyring implements Keyring { + + final Keyring generatorKeyring; + final List childrenKeyrings; + + MultiKeyring(Keyring generatorKeyring, List childrenKeyrings) { + this.generatorKeyring = generatorKeyring; + this.childrenKeyrings = childrenKeyrings == null ? emptyList() : unmodifiableList(new ArrayList<>(childrenKeyrings)); + + isTrue(this.generatorKeyring != null || !this.childrenKeyrings.isEmpty(), + "At least a generator keyring or children keyrings must be defined"); + } + + @Override + public void onEncrypt(EncryptionMaterials encryptionMaterials) { + requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + + if (generatorKeyring != null) { + generatorKeyring.onEncrypt(encryptionMaterials); + } + + if (!encryptionMaterials.hasPlaintextDataKey()) { + throw new AwsCryptoException("Either a generator keyring must be supplied that produces a plaintext " + + "data key or a plaintext data key must already be present in the encryption materials."); + } + + for (Keyring keyring : childrenKeyrings) { + keyring.onEncrypt(encryptionMaterials); + } + } + + @Override + public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasPlaintextDataKey()) { + return; + } + + final List keyringsToDecryptWith = new ArrayList<>(); + + if (generatorKeyring != null) { + keyringsToDecryptWith.add(generatorKeyring); + } + + keyringsToDecryptWith.addAll(childrenKeyrings); + + final List exceptions = new ArrayList<>(); + + for (Keyring keyring : keyringsToDecryptWith) { + try { + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + if (decryptionMaterials.hasPlaintextDataKey()) { + // Decryption succeeded, return immediately + return; + } + } catch (Exception e) { + exceptions.add(e); + } + } + + if (!exceptions.isEmpty()) { + final AwsCryptoException exception = new CannotUnwrapDataKeyException( + "Unable to decrypt data key and one or more child keyrings had an error.", exceptions.get(0)); + exceptions.forEach(exception::addSuppressed); + throw exception; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index d36dae07e..34b7e22f4 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -16,6 +16,8 @@ import javax.crypto.SecretKey; import java.security.PrivateKey; import java.security.PublicKey; +import java.util.Arrays; +import java.util.List; /** * Factory methods for instantiating the standard {@code Keyring}s provided by the AWS Encryption SDK. @@ -53,4 +55,30 @@ public static Keyring rawAes(String keyNamespace, String keyName, SecretKey wrap public static Keyring rawRsa(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String wrappingAlgorithm) { return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); } + + /** + * Constructs a {@code Keyring} which combines other keyrings, allowing one OnEncrypt or OnDecrypt call + * to modify the encryption or decryption materials using more than one keyring. + * + * @param generatorKeyring A keyring that can generate data keys. Required if childrenKeyrings is empty. + * @param childrenKeyrings A list of keyrings to be used to modify the encryption or decryption materials. + * At least one is required if generatorKeyring is null. + * @return The {@link Keyring} + */ + public static Keyring multi(Keyring generatorKeyring, List childrenKeyrings) { + return new MultiKeyring(generatorKeyring, childrenKeyrings); + } + + /** + * Constructs a {@code Keyring} which combines other keyrings, allowing one OnEncrypt or OnDecrypt call + * to modify the encryption or decryption materials using more than one keyring. + * + * @param generatorKeyring A keyring that can generate data keys. Required if childrenKeyrings is empty. + * @param childrenKeyrings Keyrings to be used to modify the encryption or decryption materials. + * At least one is required if generatorKeyring is null. + * @return The {@link Keyring} + */ + public static Keyring multi(Keyring generatorKeyring, Keyring... childrenKeyrings) { + return new MultiKeyring(generatorKeyring, Arrays.asList(childrenKeyrings)); + } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java new file mode 100644 index 000000000..75e43a676 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class MultiKeyringTest { + + @Mock Keyring generatorKeyring; + @Mock Keyring keyring1; + @Mock Keyring keyring2; + @Mock EncryptionMaterials encryptionMaterials; + @Mock DecryptionMaterials decryptionMaterials; + @Mock List encryptedDataKeys; + final List childrenKeyrings = new ArrayList<>(); + + @BeforeEach + void setup() { + childrenKeyrings.add(keyring1); + childrenKeyrings.add(keyring2); + } + + @Test + void testConstructor() { + assertThrows(IllegalArgumentException.class, () -> new MultiKeyring(null, null)); + assertThrows(IllegalArgumentException.class, () -> new MultiKeyring(null, Collections.emptyList())); + new MultiKeyring(generatorKeyring, null); + new MultiKeyring(null, Collections.singletonList(keyring1)); + } + + @Test + void testOnEncryptWithGenerator() { + MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); + when(encryptionMaterials.hasPlaintextDataKey()).thenReturn(true); + + keyring.onEncrypt(encryptionMaterials); + + verify(generatorKeyring).onEncrypt(encryptionMaterials); + verify(keyring1).onEncrypt(encryptionMaterials); + verify(keyring2).onEncrypt(encryptionMaterials); + } + + @Test + void testOnEncryptWithoutGenerator() { + MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); + when(encryptionMaterials.hasPlaintextDataKey()).thenReturn(true); + + keyring.onEncrypt(encryptionMaterials); + + verifyNoInteractions(generatorKeyring); + verify(keyring1).onEncrypt(encryptionMaterials); + verify(keyring2).onEncrypt(encryptionMaterials); + } + + @Test + void testOnEncryptNoPlaintextDataKey() { + MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); + when(encryptionMaterials.hasPlaintextDataKey()).thenReturn(false); + + assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); + } + + @Test + void testOnDecryptWithPlaintextDataKey() { + MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); + + when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(true); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + verifyNoInteractions(generatorKeyring, keyring1, keyring2); + } + + @Test + void testOnDecryptWithGenerator() { + MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); + + when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + InOrder inOrder = inOrder(generatorKeyring, keyring1); + inOrder.verify(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); + verifyNoInteractions(keyring2); + } + + @Test + void testOnDecryptWithoutGenerator() { + MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); + + when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + InOrder inOrder = inOrder(keyring1, keyring2); + inOrder.verify(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring2).onDecrypt(decryptionMaterials, encryptedDataKeys); + verifyNoInteractions(generatorKeyring); + } + + @Test + void testOnDecryptFailureThenSuccess() { + MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); + + when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false).thenReturn(true); + doThrow(new IllegalStateException()).when(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + InOrder inOrder = inOrder(generatorKeyring, keyring1); + inOrder.verify(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); + verifyNoInteractions(keyring2); + } + + @Test + void testOnDecryptFailure() { + MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); + + when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false); + doThrow(new AwsCryptoException()).when(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + doThrow(new IllegalStateException()).when(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); + doThrow(new IllegalArgumentException()).when(keyring2).onDecrypt(decryptionMaterials, encryptedDataKeys); + + AwsCryptoException exception = null; + try { + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + fail(); + } catch (AwsCryptoException e) { + exception = e; + } + + assertEquals(3, exception.getSuppressed().length); + + InOrder inOrder = inOrder(generatorKeyring, keyring1, keyring2); + inOrder.verify(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring2).onDecrypt(decryptionMaterials, encryptedDataKeys); + } + + @Test + void testOnDecryptNoFailuresNoPlaintextDataKeys() { + MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); + + when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false, false, false, false); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + InOrder inOrder = inOrder(generatorKeyring, keyring1, keyring2); + inOrder.verify(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); + inOrder.verify(keyring2).onDecrypt(decryptionMaterials, encryptedDataKeys); + } + +} diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..d41cd3cca --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1,2 @@ +# Enables mocking final types +mock-maker-inline From 77f46c2739315d5f3e5c87e09eb63a4218fd1962 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Wed, 18 Dec 2019 14:05:47 -0800 Subject: [PATCH 05/18] Defining the KMS Keyring (#147) * Defining the KMS Keyring. * Adding test for MismatchedDataKey and updating StandardKeyrings * Adding builder for KmsClientSupplier to support easy client setup * Support raw key IDs in KmsMasterKey * Reducing visibility of KmsKeyring * Don't attempt encryption with a null generator * Make MismatchedDataKeyException an AwsCryptoException * Optimizing O(n^2) operation in onDecrypt * Making defensive copy of keyIds * Skip malformed arns in OnDecrypt instead of failing --- pom.xml | 2 +- .../exception/MalformedArnException.java | 39 ++ .../exception/MismatchedDataKeyException.java | 39 ++ .../exception/UnsupportedRegionException.java | 39 ++ .../encryptionsdk/keyrings/KmsKeyring.java | 176 +++++++++ .../keyrings/StandardKeyrings.java | 19 + .../kms/DataKeyEncryptionDao.java | 104 ++++++ .../encryptionsdk/kms/KmsClientSupplier.java | 212 +++++++++++ .../kms/KmsDataKeyEncryptionDao.java | 172 +++++++++ .../encryptionsdk/kms/KmsMasterKey.java | 106 ++---- .../amazonaws/encryptionsdk/kms/KmsUtils.java | 82 +++++ .../keyrings/KmsKeyringTest.java | 334 ++++++++++++++++++ .../kms/KmsClientSupplierTest.java | 173 +++++++++ .../kms/KmsDataKeyEncryptionDaoTest.java | 243 +++++++++++++ .../encryptionsdk/kms/KmsMasterKeyTest.java | 74 ++++ .../encryptionsdk/kms/KmsUtilsTest.java | 55 +++ .../encryptionsdk/kms/MockKMSClient.java | 4 +- 17 files changed, 1792 insertions(+), 81 deletions(-) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/exception/MalformedArnException.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/exception/MismatchedDataKeyException.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java diff --git a/pom.xml b/pom.xml index c426db5e6..a8774878b 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ com.amazonaws aws-java-sdk - 1.11.561 + 1.11.677 true diff --git a/src/main/java/com/amazonaws/encryptionsdk/exception/MalformedArnException.java b/src/main/java/com/amazonaws/encryptionsdk/exception/MalformedArnException.java new file mode 100644 index 000000000..58f78833c --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/exception/MalformedArnException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.exception; + +/** + * This exception is thrown when an Amazon Resource Name is provided that does not + * match the CMK Alias or ARN format. + */ +public class MalformedArnException extends AwsCryptoException { + + private static final long serialVersionUID = -1L; + + public MalformedArnException() { + super(); + } + + public MalformedArnException(final String message) { + super(message); + } + + public MalformedArnException(final Throwable cause) { + super(cause); + } + + public MalformedArnException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/exception/MismatchedDataKeyException.java b/src/main/java/com/amazonaws/encryptionsdk/exception/MismatchedDataKeyException.java new file mode 100644 index 000000000..46a062187 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/exception/MismatchedDataKeyException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.exception; + +/** + * This exception is thrown when the key used by KMS to decrypt a data key does not + * match the provider information contained within the encrypted data key. + */ +public class MismatchedDataKeyException extends AwsCryptoException { + + private static final long serialVersionUID = -1L; + + public MismatchedDataKeyException() { + super(); + } + + public MismatchedDataKeyException(final String message) { + super(message); + } + + public MismatchedDataKeyException(final Throwable cause) { + super(cause); + } + + public MismatchedDataKeyException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java b/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java new file mode 100644 index 000000000..2c06a602c --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.exception; + +/** + * This exception is thrown when a region that is not allowed to be used by + * a given KmsClientSupplier is specified. + */ +public class UnsupportedRegionException extends AwsCryptoException { + + private static final long serialVersionUID = -1L; + + public UnsupportedRegionException() { + super(); + } + + public UnsupportedRegionException(final String message) { + super(message); + } + + public UnsupportedRegionException(final Throwable cause) { + super(cause); + } + + public UnsupportedRegionException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java new file mode 100644 index 000000000..4b3974501 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java @@ -0,0 +1,176 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.GenerateDataKeyResult; +import com.amazonaws.encryptionsdk.kms.KmsUtils; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.isArnWellFormed; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; + +/** + * A keyring which interacts with AWS Key Management Service (KMS) to create, + * encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). + */ +class KmsKeyring implements Keyring { + + private final DataKeyEncryptionDao dataKeyEncryptionDao; + private final List keyIds; + private final String generatorKeyId; + private final boolean isDiscovery; + + KmsKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, List keyIds, String generatorKeyId) { + requireNonNull(dataKeyEncryptionDao, "dataKeyEncryptionDao is required"); + this.dataKeyEncryptionDao = dataKeyEncryptionDao; + this.keyIds = keyIds == null ? emptyList() : unmodifiableList(new ArrayList<>(keyIds)); + this.generatorKeyId = generatorKeyId; + this.isDiscovery = this.generatorKeyId == null && this.keyIds.isEmpty(); + + if (!this.keyIds.stream().allMatch(KmsUtils::isArnWellFormed)) { + throw new MalformedArnException("keyIds must contain only CMK aliases and well formed ARNs"); + } + + if (generatorKeyId != null) { + if (!isArnWellFormed(generatorKeyId)) { + throw new MalformedArnException("generatorKeyId must be either a CMK alias or a well formed ARN"); + } + if (this.keyIds.contains(generatorKeyId)) { + throw new IllegalArgumentException("KeyIds should not contain the generatorKeyId"); + } + } + } + + @Override + public void onEncrypt(EncryptionMaterials encryptionMaterials) { + requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + + // If this keyring is a discovery keyring, OnEncrypt MUST return the input encryption materials unmodified. + if (isDiscovery) { + return; + } + + // If the input encryption materials do not contain a plaintext data key and this keyring does not + // have a generator defined, OnEncrypt MUST not modify the encryption materials and MUST fail. + if (!encryptionMaterials.hasPlaintextDataKey() && generatorKeyId == null) { + throw new AwsCryptoException("Encryption materials must contain either a plaintext data key or a generator"); + } + + final List keyIdsToEncrypt = new ArrayList<>(keyIds); + + // If the input encryption materials do not contain a plaintext data key and a generator is defined onEncrypt + // MUST attempt to generate a new plaintext data key and encrypt that data key by calling KMS GenerateDataKey. + if (!encryptionMaterials.hasPlaintextDataKey()) { + generateDataKey(encryptionMaterials); + } else if (generatorKeyId != null) { + // If this keyring's generator is defined and was not used to generate a data key, OnEncrypt + // MUST also attempt to encrypt the plaintext data key using the CMK specified by the generator. + keyIdsToEncrypt.add(generatorKeyId); + } + + // Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt + // to encrypt the plaintext data key using each CMK specified in it's key IDs list. + for (String keyId : keyIdsToEncrypt) { + encryptDataKey(keyId, encryptionMaterials); + } + } + + private void generateDataKey(final EncryptionMaterials encryptionMaterials) { + final GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey(generatorKeyId, + encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext()); + + encryptionMaterials.setPlaintextDataKey(result.getPlaintextDataKey(), + new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.GENERATED_DATA_KEY)); + encryptionMaterials.addEncryptedDataKey(result.getEncryptedDataKey(), + new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + } + + private void encryptDataKey(final String keyId, final EncryptionMaterials encryptionMaterials) { + final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey(keyId, + encryptionMaterials.getPlaintextDataKey(), encryptionMaterials.getEncryptionContext()); + + encryptionMaterials.addEncryptedDataKey(encryptedDataKey, + new KeyringTraceEntry(KMS_PROVIDER_ID, keyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + } + + @Override + public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasPlaintextDataKey() || encryptedDataKeys.isEmpty()) { + return; + } + + final Set configuredKeyIds = new HashSet<>(keyIds); + + if (generatorKeyId != null) { + configuredKeyIds.add(generatorKeyId); + } + + for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { + if (okToDecrypt(encryptedDataKey, configuredKeyIds)) { + try { + final DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey(encryptedDataKey, + decryptionMaterials.getAlgorithmSuite(), decryptionMaterials.getEncryptionContext()); + + decryptionMaterials.setPlaintextDataKey(result.getPlaintextDataKey(), + new KeyringTraceEntry(KMS_PROVIDER_ID, result.getKeyArn(), + KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); + return; + } catch (CannotUnwrapDataKeyException e) { + continue; + } + } + } + } + + private boolean okToDecrypt(EncryptedDataKey encryptedDataKey, Set configuredKeyIds) { + // Only attempt to decrypt keys provided by KMS + if (!encryptedDataKey.getProviderId().equals(KMS_PROVIDER_ID)) { + return false; + } + + // If the key ARN cannot be parsed, skip it + if(!isArnWellFormed(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING))) + { + return false; + } + + // If this keyring is a discovery keyring, OnDecrypt MUST attempt to + // decrypt every encrypted data key in the input encrypted data key list + if (isDiscovery) { + return true; + } + + // OnDecrypt MUST attempt to decrypt each input encrypted data key in the input + // encrypted data key list where the key provider info has a value equal to one + // of the ARNs in this keyring's key IDs or the generator + return configuredKeyIds.contains(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING)); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index 34b7e22f4..9dc96cbf8 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -13,6 +13,9 @@ package com.amazonaws.encryptionsdk.keyrings; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.KmsClientSupplier; + import javax.crypto.SecretKey; import java.security.PrivateKey; import java.security.PublicKey; @@ -56,6 +59,22 @@ public static Keyring rawRsa(String keyNamespace, String keyName, PublicKey publ return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); } + /** + * Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create, + * encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). + * + * @param clientSupplier A function that returns a KMS client that can make GenerateDataKey, + * Encrypt, and Decrypt calls in a particular AWS region. + * @param grantTokens A list of string grant tokens to be included in all KMS calls. + * @param keyIds A list of strings identifying KMS CMKs, in ARN, CMK Alias, or ARN Alias format. + * @param generator A string that identifies a KMS CMK responsible for generating a data key, + * as well as encrypting and decrypting data keys in ARN, CMK Alias, or ARN Alias format. + * @return The {@code Keyring} + */ + public static Keyring kms(KmsClientSupplier clientSupplier, List grantTokens, List keyIds, String generator) { + return new KmsKeyring(DataKeyEncryptionDao.kms(clientSupplier, grantTokens), keyIds, generator); + } + /** * Constructs a {@code Keyring} which combines other keyrings, allowing one OnEncrypt or OnDecrypt call * to modify the encryption or decryption materials using more than one keyring. diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java new file mode 100644 index 000000000..4267ba6f8 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; + +import javax.crypto.SecretKey; +import java.util.List; +import java.util.Map; + +public interface DataKeyEncryptionDao { + + /** + * Generates a unique data key, returning both the plaintext copy of the key and an encrypted copy encrypted using + * the customer master key specified by the given keyId. + * + * @param keyId The customer master key to encrypt the generated key with. + * @param algorithmSuite The algorithm suite associated with the key. + * @param encryptionContext The encryption context. + * @return GenerateDataKeyResult containing the plaintext data key and the encrypted data key. + */ + GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext); + + /** + * Encrypts the given plaintext data key using the customer aster key specified by the given keyId. + * + * @param keyId The customer master key to encrypt the plaintext data key with. + * @param plaintextDataKey The plaintext data key to encrypt. + * @param encryptionContext The encryption context. + * @return The encrypted data key. + */ + EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDataKey, Map encryptionContext); + + /** + * Decrypted the given encrypted data key. + * + * @param encryptedDataKey The encrypted data key to decrypt. + * @param algorithmSuite The algorithm suite associated with the key. + * @param encryptionContext The encryption context. + * @return DecryptDataKeyResult containing the plaintext data key and the ARN of the key that decrypted it. + */ + DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, CryptoAlgorithm algorithmSuite, Map encryptionContext); + + /** + * Constructs an instance of DataKeyEncryptionDao that uses AWS Key Management Service (KMS) for + * generation, encryption, and decryption of data keys. + * + * @param clientSupplier A supplier of AWSKMS clients + * @param grantTokens A list of grant tokens to supply to KMS + * @return The DataKeyEncryptionDao + */ + static DataKeyEncryptionDao kms(KmsClientSupplier clientSupplier, List grantTokens) { + return new KmsDataKeyEncryptionDao(clientSupplier, grantTokens); + } + + class GenerateDataKeyResult { + private final SecretKey plaintextDataKey; + private final EncryptedDataKey encryptedDataKey; + + public GenerateDataKeyResult(SecretKey plaintextDataKey, EncryptedDataKey encryptedDataKey) { + this.plaintextDataKey = plaintextDataKey; + this.encryptedDataKey = encryptedDataKey; + } + + public SecretKey getPlaintextDataKey() { + return plaintextDataKey; + } + + public EncryptedDataKey getEncryptedDataKey() { + return encryptedDataKey; + } + } + + class DecryptDataKeyResult { + private final String keyArn; + private final SecretKey plaintextDataKey; + + public DecryptDataKeyResult(String keyArn, SecretKey plaintextDataKey) { + this.keyArn = keyArn; + this.plaintextDataKey = plaintextDataKey; + } + + public String getKeyArn() { + return keyArn; + } + + public SecretKey getPlaintextDataKey() { + return plaintextDataKey; + } + + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java new file mode 100644 index 000000000..6182e161c --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java @@ -0,0 +1,212 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.AWSKMSClientBuilder; +import com.amazonaws.services.kms.model.AWSKMSException; + +import javax.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; +import static org.apache.commons.lang3.Validate.notEmpty; + +/** + * Represents a function that accepts an AWS region and returns an {@code AWSKMS} client for that region. The + * function should be able to handle when the region is null. + */ +@FunctionalInterface +public interface KmsClientSupplier { + + /** + * Gets an {@code AWSKMS} client for the given regionId. + * + * @param regionId The AWS region (or null) + * @return The AWSKMS client + * @throws UnsupportedRegionException if a regionId is specified that this + * client supplier is configured to not allow. + */ + AWSKMS getClient(@Nullable String regionId) throws UnsupportedRegionException; + + /** + * Gets a Builder for constructing a KmsClientSupplier + * + * @return The builder + */ + static Builder builder() { + return new Builder(AWSKMSClientBuilder.standard()); + } + + /** + * Builder to construct a KmsClientSupplier given various + * optional settings. + */ + class Builder { + + private AWSCredentialsProvider credentialsProvider; + private ClientConfiguration clientConfiguration; + private Set allowedRegions = Collections.emptySet(); + private Set excludedRegions = Collections.emptySet(); + private boolean clientCachingEnabled = false; + private final Map clientsCache = new HashMap<>(); + private static final Set KMS_METHODS = new HashSet<>(); + private AWSKMSClientBuilder kmsClientBuilder; + + static { + KMS_METHODS.add("generateDataKey"); + KMS_METHODS.add("encrypt"); + KMS_METHODS.add("decrypt"); + } + + Builder(AWSKMSClientBuilder kmsClientBuilder) { + this.kmsClientBuilder = kmsClientBuilder; + } + + public KmsClientSupplier build() { + isTrue(allowedRegions.isEmpty() || excludedRegions.isEmpty(), + "Either allowed regions or excluded regions may be set, not both."); + + return regionId -> { + if (!allowedRegions.isEmpty() && !allowedRegions.contains(regionId)) { + throw new UnsupportedRegionException(String.format("Region %s is not in the list of allowed regions %s", + regionId, allowedRegions)); + } + + if (excludedRegions.contains(regionId)) { + throw new UnsupportedRegionException(String.format("Region %s is in the list of excluded regions %s", + regionId, excludedRegions)); + } + + if (clientsCache.containsKey(regionId)) { + return clientsCache.get(regionId); + } + + if (credentialsProvider != null) { + kmsClientBuilder = kmsClientBuilder.withCredentials(credentialsProvider); + } + + if (clientConfiguration != null) { + kmsClientBuilder = kmsClientBuilder.withClientConfiguration(clientConfiguration); + } + + if (regionId != null) { + kmsClientBuilder = kmsClientBuilder.withRegion(regionId); + } + + AWSKMS client = kmsClientBuilder.build(); + + if (clientCachingEnabled) { + client = newCachingProxy(client, regionId); + } + + return client; + }; + } + + /** + * Sets the AWSCredentialsProvider used by the client. + * + * @param credentialsProvider New AWSCredentialsProvider to use. + */ + public Builder credentialsProvider(AWSCredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Sets the ClientConfiguration to be used by the client. + * + * @param clientConfiguration Custom configuration to use. + */ + public Builder clientConfiguration(ClientConfiguration clientConfiguration) { + this.clientConfiguration = clientConfiguration; + return this; + } + + /** + * Sets the AWS regions that the client supplier is permitted to use. + * + * @param regions The set of regions. + */ + public Builder allowedRegions(Set regions) { + notEmpty(regions, "At least one region is required"); + this.allowedRegions = Collections.unmodifiableSet(new HashSet<>(regions)); + return this; + } + + /** + * Sets the AWS regions that the client supplier is not permitted to use. + * + * @param regions The set of regions. + */ + public Builder excludedRegions(Set regions) { + requireNonNull(regions, "regions is required"); + this.excludedRegions = Collections.unmodifiableSet(new HashSet<>(regions)); + return this; + } + + /** + * When set to true, allows for the AWSKMS client for each region to be cached and reused. + * + * @param enabled Whether or not caching is enabled. + */ + public Builder clientCaching(boolean enabled) { + this.clientCachingEnabled = enabled; + return this; + } + + /** + * Creates a proxy for the AWSKMS client that will populate the client into the client cache + * after a KMS method successfully completes or a KMS exception occurs. This is to prevent a + * a malicious user from causing a local resource DOS by sending ciphertext with a large number + * of spurious regions, thereby filling the cache with regions and exhausting resources. + * + * @param client The client to proxy + * @param regionId The region the client is associated with + * @return The proxy + */ + private AWSKMS newCachingProxy(AWSKMS client, String regionId) { + return (AWSKMS) Proxy.newProxyInstance( + AWSKMS.class.getClassLoader(), + new Class[]{AWSKMS.class}, + (proxy, method, methodArgs) -> { + try { + final Object result = method.invoke(client, methodArgs); + if (KMS_METHODS.contains(method.getName())) { + clientsCache.put(regionId, client); + } + return result; + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof AWSKMSException && + KMS_METHODS.contains(method.getName())) { + clientsCache.put(regionId, client); + } + + throw e.getTargetException(); + } + }); + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java new file mode 100644 index 000000000..a00a1f1c7 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java @@ -0,0 +1,172 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; +import com.amazonaws.encryptionsdk.internal.VersionInfo; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.EncryptRequest; +import com.amazonaws.services.kms.model.GenerateDataKeyRequest; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.getClientByArn; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; + +/** + * An implementation of DataKeyEncryptionDao that uses AWS Key Management Service (KMS) for + * generation, encryption, and decryption of data keys. The KmsMethods interface is implemented + * to allow usage in KmsMasterKey. + */ +class KmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { + + private final KmsClientSupplier clientSupplier; + private List grantTokens; + + KmsDataKeyEncryptionDao(KmsClientSupplier clientSupplier, List grantTokens) { + requireNonNull(clientSupplier, "clientSupplier is required"); + + this.clientSupplier = clientSupplier; + this.grantTokens = grantTokens == null ? new ArrayList<>() : new ArrayList<>(grantTokens); + } + + @Override + public GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext) { + requireNonNull(keyId, "keyId is required"); + requireNonNull(algorithmSuite, "algorithmSuite is required"); + requireNonNull(encryptionContext, "encryptionContext is required"); + + final com.amazonaws.services.kms.model.GenerateDataKeyResult kmsResult; + + try { + kmsResult = getClientByArn(keyId, clientSupplier) + .generateDataKey(updateUserAgent( + new GenerateDataKeyRequest() + .withKeyId(keyId) + .withNumberOfBytes(algorithmSuite.getDataKeyLength()) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens))); + } catch (final AmazonServiceException ex) { + throw new AwsCryptoException(ex); + } + + final byte[] rawKey = new byte[algorithmSuite.getDataKeyLength()]; + kmsResult.getPlaintext().get(rawKey); + if (kmsResult.getPlaintext().remaining() > 0) { + throw new IllegalStateException("Received an unexpected number of bytes from KMS"); + } + final byte[] encryptedKey = new byte[kmsResult.getCiphertextBlob().remaining()]; + kmsResult.getCiphertextBlob().get(encryptedKey); + + return new GenerateDataKeyResult(new SecretKeySpec(rawKey, algorithmSuite.getDataKeyAlgo()), + new KeyBlob(KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedKey)); + } + + @Override + public EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDataKey, Map encryptionContext) { + requireNonNull(keyId, "keyId is required"); + requireNonNull(plaintextDataKey, "plaintextDataKey is required"); + requireNonNull(encryptionContext, "encryptionContext is required"); + isTrue(plaintextDataKey.getFormat().equals("RAW"), "Only RAW encoded keys are supported"); + + final com.amazonaws.services.kms.model.EncryptResult kmsResult; + + try { + kmsResult = getClientByArn(keyId, clientSupplier) + .encrypt(updateUserAgent(new EncryptRequest() + .withKeyId(keyId) + .withPlaintext(ByteBuffer.wrap(plaintextDataKey.getEncoded())) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens))); + } catch (final AmazonServiceException ex) { + throw new AwsCryptoException(ex); + } + final byte[] encryptedDataKey = new byte[kmsResult.getCiphertextBlob().remaining()]; + kmsResult.getCiphertextBlob().get(encryptedDataKey); + + return new KeyBlob(KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedDataKey); + + } + + @Override + public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, CryptoAlgorithm algorithmSuite, Map encryptionContext) { + requireNonNull(encryptedDataKey, "encryptedDataKey is required"); + requireNonNull(algorithmSuite, "algorithmSuite is required"); + requireNonNull(encryptionContext, "encryptionContext is required"); + + final String providerInformation = new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING); + final com.amazonaws.services.kms.model.DecryptResult kmsResult; + + try { + kmsResult = getClientByArn(providerInformation, clientSupplier) + .decrypt(updateUserAgent(new DecryptRequest() + .withCiphertextBlob(ByteBuffer.wrap(encryptedDataKey.getEncryptedDataKey())) + .withEncryptionContext(encryptionContext) + .withGrantTokens(grantTokens))); + } catch (final AmazonServiceException | UnsupportedRegionException ex) { + throw new CannotUnwrapDataKeyException(ex); + } + + if (!kmsResult.getKeyId().equals(providerInformation)) { + throw new MismatchedDataKeyException("Received an unexpected key Id from KMS"); + } + + final byte[] rawKey = new byte[algorithmSuite.getDataKeyLength()]; + kmsResult.getPlaintext().get(rawKey); + if (kmsResult.getPlaintext().remaining() > 0) { + throw new IllegalStateException("Received an unexpected number of bytes from KMS"); + } + + return new DecryptDataKeyResult(kmsResult.getKeyId(), new SecretKeySpec(rawKey, algorithmSuite.getDataKeyAlgo())); + + } + + private T updateUserAgent(T request) { + request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); + + return request; + } + + @Override + public void setGrantTokens(List grantTokens) { + this.grantTokens = new ArrayList<>(grantTokens); + } + + @Override + public List getGrantTokens() { + return Collections.unmodifiableList(grantTokens); + } + + @Override + public void addGrantToken(String grantToken) { + grantTokens.add(grantToken); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java index b78840221..60c69445c 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java @@ -14,17 +14,12 @@ package com.amazonaws.encryptionsdk.kms; import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Supplier; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.AmazonWebServiceRequest; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.AwsCrypto; @@ -35,30 +30,18 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; -import com.amazonaws.encryptionsdk.internal.VersionInfo; import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.model.DecryptRequest; -import com.amazonaws.services.kms.model.DecryptResult; -import com.amazonaws.services.kms.model.EncryptRequest; -import com.amazonaws.services.kms.model.EncryptResult; -import com.amazonaws.services.kms.model.GenerateDataKeyRequest; -import com.amazonaws.services.kms.model.GenerateDataKeyResult; + +import static java.util.Collections.emptyList; /** * Represents a single Customer Master Key (CMK) and is used to encrypt/decrypt data with * {@link AwsCrypto}. */ public final class KmsMasterKey extends MasterKey implements KmsMethods { - private final Supplier kms_; + private final KmsDataKeyEncryptionDao dataKeyEncryptionDao_; private final MasterKeyProvider sourceProvider_; private final String id_; - private final List grantTokens_ = new ArrayList<>(); - - private T updateUserAgent(T request) { - request.getRequestClientOptions().appendUserAgent(VersionInfo.USER_AGENT); - - return request; - } /** * @@ -80,11 +63,11 @@ public static KmsMasterKey getInstance(final AWSCredentialsProvider creds, final static KmsMasterKey getInstance(final Supplier kms, final String id, final MasterKeyProvider provider) { - return new KmsMasterKey(kms, id, provider); + return new KmsMasterKey(new KmsDataKeyEncryptionDao(s -> kms.get(), emptyList()), id, provider); } - private KmsMasterKey(final Supplier kms, final String id, final MasterKeyProvider provider) { - kms_ = kms; + KmsMasterKey(final KmsDataKeyEncryptionDao dataKeyEncryptionDao, final String id, final MasterKeyProvider provider) { + dataKeyEncryptionDao_ = dataKeyEncryptionDao; id_ = id; sourceProvider_ = provider; } @@ -102,39 +85,27 @@ public String getKeyId() { @Override public DataKey generateDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext) { - final GenerateDataKeyResult gdkResult = kms_.get().generateDataKey(updateUserAgent( - new GenerateDataKeyRequest() - .withKeyId(getKeyId()) - .withNumberOfBytes(algorithm.getDataKeyLength()) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens_) - )); - final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; - gdkResult.getPlaintext().get(rawKey); - if (gdkResult.getPlaintext().remaining() > 0) { - throw new IllegalStateException("Recieved an unexpected number of bytes from KMS"); - } - final byte[] encryptedKey = new byte[gdkResult.getCiphertextBlob().remaining()]; - gdkResult.getCiphertextBlob().get(encryptedKey); - - final SecretKeySpec key = new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()); - return new DataKey<>(key, encryptedKey, gdkResult.getKeyId().getBytes(StandardCharsets.UTF_8), this); + final DataKeyEncryptionDao.GenerateDataKeyResult gdkResult = dataKeyEncryptionDao_.generateDataKey( + getKeyId(), algorithm, encryptionContext); + return new DataKey<>(gdkResult.getPlaintextDataKey(), + gdkResult.getEncryptedDataKey().getEncryptedDataKey(), + gdkResult.getEncryptedDataKey().getProviderInformation(), + this); } @Override public void setGrantTokens(final List grantTokens) { - grantTokens_.clear(); - grantTokens_.addAll(grantTokens); + dataKeyEncryptionDao_.setGrantTokens(grantTokens); } @Override public List getGrantTokens() { - return grantTokens_; + return dataKeyEncryptionDao_.getGrantTokens(); } @Override public void addGrantToken(final String grantToken) { - grantTokens_.add(grantToken); + dataKeyEncryptionDao_.addGrantToken(grantToken); } @Override @@ -142,22 +113,12 @@ public DataKey encryptDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext, final DataKey dataKey) { final SecretKey key = dataKey.getKey(); - if (!key.getFormat().equals("RAW")) { - throw new IllegalArgumentException("Only RAW encoded keys are supported"); - } - try { - final EncryptResult encryptResult = kms_.get().encrypt(updateUserAgent( - new EncryptRequest() - .withKeyId(id_) - .withPlaintext(ByteBuffer.wrap(key.getEncoded())) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens_))); - final byte[] edk = new byte[encryptResult.getCiphertextBlob().remaining()]; - encryptResult.getCiphertextBlob().get(edk); - return new DataKey<>(dataKey.getKey(), edk, encryptResult.getKeyId().getBytes(StandardCharsets.UTF_8), this); - } catch (final AmazonServiceException asex) { - throw new AwsCryptoException(asex); - } + final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao_.encryptDataKey(id_, key, encryptionContext); + + return new DataKey<>(dataKey.getKey(), + encryptedDataKey.getEncryptedDataKey(), + encryptedDataKey.getProviderInformation(), + this); } @Override @@ -168,24 +129,13 @@ public DataKey decryptDataKey(final CryptoAlgorithm algorithm, final List exceptions = new ArrayList<>(); for (final EncryptedDataKey edk : encryptedDataKeys) { try { - final DecryptResult decryptResult = kms_.get().decrypt(updateUserAgent( - new DecryptRequest() - .withCiphertextBlob(ByteBuffer.wrap(edk.getEncryptedDataKey())) - .withEncryptionContext(encryptionContext) - .withGrantTokens(grantTokens_))); - if (decryptResult.getKeyId().equals(id_)) { - final byte[] rawKey = new byte[algorithm.getDataKeyLength()]; - decryptResult.getPlaintext().get(rawKey); - if (decryptResult.getPlaintext().remaining() > 0) { - throw new IllegalStateException("Received an unexpected number of bytes from KMS"); - } - return new DataKey<>( - new SecretKeySpec(rawKey, algorithm.getDataKeyAlgo()), - edk.getEncryptedDataKey(), - edk.getProviderInformation(), this); - } - } catch (final AmazonServiceException awsex) { - exceptions.add(awsex); + final DataKeyEncryptionDao.DecryptDataKeyResult result = dataKeyEncryptionDao_.decryptDataKey(edk, algorithm, encryptionContext); + return new DataKey<>( + result.getPlaintextDataKey(), + edk.getEncryptedDataKey(), + edk.getProviderInformation(), this); + } catch (final AwsCryptoException ex) { + exceptions.add(ex); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java new file mode 100644 index 000000000..f6aab16eb --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java @@ -0,0 +1,82 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.arn.Arn; +import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import com.amazonaws.services.kms.AWSKMS; + +public class KmsUtils { + + private static final String ALIAS_PREFIX = "alias/"; + private static final String ARN_PREFIX = "arn:"; + /** + * The provider ID used for the KmsKeyring + */ + public static final String KMS_PROVIDER_ID = "aws-kms"; + + /** + * Parses region from the given arn (if possible) and passes that region to the + * given clientSupplier to produce an {@code AWSKMS} client. + * + * @param arn The Amazon Resource Name or Key Alias + * @param clientSupplier The client supplier + * @return AWSKMS The client + * @throws MalformedArnException if the arn is malformed + */ + public static AWSKMS getClientByArn(String arn, KmsClientSupplier clientSupplier) throws MalformedArnException { + if (isKeyAlias(arn)) { + return clientSupplier.getClient(null); + } + + if(isArn(arn)) { + try { + return clientSupplier.getClient(Arn.fromString(arn).getRegion()); + } catch (IllegalArgumentException e) { + throw new MalformedArnException(e); + } + } + + // Not an alias or an ARN, must be a raw Key ID + return clientSupplier.getClient(null); + } + + /** + * Returns true if the given arn is a well formed Amazon Resource Name or Key Alias. Does + * not return true for raw key IDs. + * + * @param arn The Amazon Resource Name or Key Alias + * @return True if well formed, false otherwise + */ + public static boolean isArnWellFormed(String arn) { + if (isKeyAlias(arn)) { + return true; + } + + try { + Arn.fromString(arn); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + private static boolean isKeyAlias(String arn) { + return arn.startsWith(ALIAS_PREFIX); + } + + private static boolean isArn(String arn) { + return arn.startsWith(ARN_PREFIX); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java new file mode 100644 index 000000000..6ed3629ff --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java @@ -0,0 +1,334 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KmsKeyringTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + private static final String GENERATOR_KEY_ID = "arn:aws:kms:us-east-1:999999999999:key/generator-89ab-cdef-fedc-ba9876543210"; + private static final String KEY_ID_1 = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; + private static final String KEY_ID_2 = "arn:aws:kms:us-east-1:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; + private static final EncryptedDataKey ENCRYPTED_GENERATOR_KEY = new KeyBlob(KMS_PROVIDER_ID, + GENERATOR_KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final EncryptedDataKey ENCRYPTED_KEY_1 = new KeyBlob(KMS_PROVIDER_ID, + KEY_ID_1.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final EncryptedDataKey ENCRYPTED_KEY_2 = new KeyBlob(KMS_PROVIDER_ID, + KEY_ID_2.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + private static final KeyringTraceEntry ENCRYPTED_GENERATOR_KEY_TRACE = + new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + private static final KeyringTraceEntry ENCRYPTED_KEY_1_TRACE = + new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + private static final KeyringTraceEntry ENCRYPTED_KEY_2_TRACE = + new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + private static final KeyringTraceEntry GENERATED_DATA_KEY_TRACE = + new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, GENERATED_DATA_KEY); + @Mock(lenient = true) private DataKeyEncryptionDao dataKeyEncryptionDao; + private Keyring keyring; + + @BeforeEach + void setup() { + when(dataKeyEncryptionDao.encryptDataKey(GENERATOR_KEY_ID, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_GENERATOR_KEY); + when(dataKeyEncryptionDao.encryptDataKey(KEY_ID_1, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_1); + when(dataKeyEncryptionDao.encryptDataKey(KEY_ID_2, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_2); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_GENERATOR_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(GENERATOR_KEY_ID, PLAINTEXT_DATA_KEY)); + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(KEY_ID_1, PLAINTEXT_DATA_KEY)); + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DecryptDataKeyResult(KEY_ID_2, PLAINTEXT_DATA_KEY)); + + List keyIds = new ArrayList<>(); + keyIds.add(KEY_ID_1); + keyIds.add(KEY_ID_2); + keyring = new KmsKeyring(dataKeyEncryptionDao, keyIds, GENERATOR_KEY_ID); + } + + @Test + void testMalformedArns() { + assertThrows(MalformedArnException.class, () -> new KmsKeyring(dataKeyEncryptionDao, null, "badArn")); + assertThrows(MalformedArnException.class, () -> new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList("badArn"), GENERATOR_KEY_ID)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(new KeyBlob(KMS_PROVIDER_ID, "badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + + // Malformed Arn for a non KMS provider shouldn't fail + encryptedDataKeys.clear(); + encryptedDataKeys.add(new KeyBlob("OtherProviderId", "badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + } + + @Test + void testGeneratorKeyInKeyIds() { + assertThrows(IllegalArgumentException.class, () -> new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList(GENERATOR_KEY_ID), GENERATOR_KEY_ID)); + } + + @Test + void testEncryptDecryptExistingDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(3, encryptionMaterials.getEncryptedDataKeys().size()); + assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_GENERATOR_KEY)); + assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_KEY_1)); + assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_KEY_2)); + + assertEquals(3, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_GENERATOR_KEY_TRACE)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_2_TRACE)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_GENERATOR_KEY); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testEncryptNullDataKey() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .keyringTrace(new KeyringTrace()) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.generateDataKey(GENERATOR_KEY_ID, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenReturn(new DataKeyEncryptionDao.GenerateDataKeyResult(PLAINTEXT_DATA_KEY, ENCRYPTED_GENERATOR_KEY)); + keyring.onEncrypt(encryptionMaterials); + + assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getPlaintextDataKey()); + + assertEquals(4, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(GENERATED_DATA_KEY_TRACE)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_GENERATOR_KEY_TRACE)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_2_TRACE)); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_GENERATOR_KEY); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testEncryptNullGenerator() { + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .keyringTrace(new KeyringTrace()) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + Keyring keyring = new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList(KEY_ID_1), null); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); + assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_KEY_1)); + + assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getPlaintextDataKey()); + + assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); + assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); + } + + @Test + void testDiscoveryEncrypt() { + keyring = new KmsKeyring(dataKeyEncryptionDao, null, null); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + keyring.onEncrypt(encryptionMaterials); + + assertFalse(encryptionMaterials.hasPlaintextDataKey()); + assertEquals(0, encryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testEncryptNoGeneratorOrPlaintextDataKey() { + List keyIds = new ArrayList<>(); + keyIds.add(KEY_ID_1); + keyring = new KmsKeyring(dataKeyEncryptionDao, keyIds, null); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE).build(); + assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); + } + + @Test + void testDecryptFirstKeyFails() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptMismatchedDataKeyException() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new MismatchedDataKeyException()); + + assertThrows(MismatchedDataKeyException.class, () -> keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_KEY_1))); + } + + @Test + void testDecryptFirstKeyWrongProvider() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + EncryptedDataKey wrongProviderKey = new KeyBlob("OtherProvider", KEY_ID_1.getBytes(PROVIDER_ENCODING), new byte[]{}); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(wrongProviderKey); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDiscoveryDecrypt() { + keyring = new KmsKeyring(dataKeyEncryptionDao, null, null); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(ENCRYPTED_KEY_1); + encryptedDataKeys.add(ENCRYPTED_KEY_2); + keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testDecryptAlreadyDecryptedDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_GENERATOR_KEY)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } + + @Test + void testDecryptNoDataKey() { + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .keyringTrace(new KeyringTrace()) + .build(); + + keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); + + assertFalse(decryptionMaterials.hasPlaintextDataKey()); + assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java new file mode 100644 index 000000000..cdca99fd6 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java @@ -0,0 +1,173 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.AWSKMSClientBuilder; +import com.amazonaws.services.kms.model.AWSKMSException; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.EncryptRequest; +import com.amazonaws.services.kms.model.EncryptResult; +import com.amazonaws.services.kms.model.GenerateDataKeyRequest; +import com.amazonaws.services.kms.model.KMSInvalidStateException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KmsClientSupplierTest { + + @Mock AWSKMSClientBuilder kmsClientBuilder; + @Mock AWSKMS awskms; + @Mock EncryptRequest encryptRequest; + @Mock DecryptRequest decryptRequest; + @Mock GenerateDataKeyRequest generateDataKeyRequest; + @Mock AWSCredentialsProvider credentialsProvider; + @Mock ClientConfiguration clientConfiguration; + private static final String REGION_1 = "us-east-1"; + private static final String REGION_2 = "us-west-2"; + private static final String REGION_3 = "eu-west-1"; + + @Test + void testCredentialsAndClientConfiguration() { + when(kmsClientBuilder.withClientConfiguration(clientConfiguration)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.withCredentials(credentialsProvider)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + KmsClientSupplier supplier = new KmsClientSupplier.Builder(kmsClientBuilder) + .credentialsProvider(credentialsProvider) + .clientConfiguration(clientConfiguration) + .build(); + + supplier.getClient(null); + + verify(kmsClientBuilder).withClientConfiguration(clientConfiguration); + verify(kmsClientBuilder).withCredentials(credentialsProvider); + verify(kmsClientBuilder).build(); + } + + @Test + void testAllowedAndExcludedRegions() { + KmsClientSupplier supplierWithDefaultValues = new KmsClientSupplier.Builder(kmsClientBuilder) + .build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); + + KmsClientSupplier supplierWithAllowed = new KmsClientSupplier.Builder(kmsClientBuilder) + .allowedRegions(Collections.singleton(REGION_1)) + .build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertNotNull(supplierWithAllowed.getClient(REGION_1)); + assertThrows(UnsupportedRegionException.class, () -> supplierWithAllowed.getClient(REGION_2)); + + KmsClientSupplier supplierWithExcluded = new KmsClientSupplier.Builder(kmsClientBuilder) + .excludedRegions(Collections.singleton(REGION_1)) + .build(); + + when(kmsClientBuilder.withRegion(REGION_2)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertThrows(UnsupportedRegionException.class, () -> supplierWithExcluded.getClient(REGION_1)); + assertNotNull(supplierWithExcluded.getClient(REGION_2)); + + assertThrows(IllegalArgumentException.class, () -> new KmsClientSupplier.Builder(kmsClientBuilder) + .allowedRegions(Collections.singleton(REGION_1)) + .excludedRegions(Collections.singleton(REGION_2)) + .build()); + } + + @Test + void testClientCachingDisabled() { + KmsClientSupplier supplierCachingDisabled = new KmsClientSupplier.Builder(kmsClientBuilder) + .clientCaching(false) + .build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + AWSKMS uncachedClient = supplierCachingDisabled.getClient(REGION_1); + verify(kmsClientBuilder, times(1)).build(); + + when(awskms.encrypt(encryptRequest)).thenReturn(new EncryptResult()); + + uncachedClient.encrypt(encryptRequest); + supplierCachingDisabled.getClient(REGION_1); + verify(kmsClientBuilder, times(2)).build(); + } + + @Test + void testClientCaching() { + KmsClientSupplier supplier = new KmsClientSupplier.Builder(kmsClientBuilder) + .clientCaching(true) + .build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.withRegion(REGION_2)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.withRegion(REGION_3)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + AWSKMS client = supplier.getClient(REGION_1); + AWSKMS client2 = supplier.getClient(REGION_2); + AWSKMS client3 = supplier.getClient(REGION_3); + verify(kmsClientBuilder, times(3)).build(); + + // No KMS methods have been called yet, so clients remain uncached + supplier.getClient(REGION_1); + supplier.getClient(REGION_2); + supplier.getClient(REGION_3); + verify(kmsClientBuilder, times(6)).build(); + + when(awskms.encrypt(encryptRequest)).thenReturn(new EncryptResult()); + when(awskms.decrypt(decryptRequest)).thenThrow(new KMSInvalidStateException("test")); + when(awskms.generateDataKey(generateDataKeyRequest)).thenThrow(new IllegalArgumentException("test")); + + // Successful KMS call, client is cached + client.encrypt(encryptRequest); + supplier.getClient(REGION_1); + verify(kmsClientBuilder, times(6)).build(); + + // KMS call resulted in KMS exception, client is cached + assertThrows(AWSKMSException.class, () -> client2.decrypt(decryptRequest)); + supplier.getClient(REGION_2); + verify(kmsClientBuilder, times(6)).build(); + + // KMS call resulted in non-KMS exception, client is not cached + assertThrows(IllegalArgumentException.class, () -> client3.generateDataKey(generateDataKeyRequest)); + supplier.getClient(REGION_3); + verify(kmsClientBuilder, times(7)).build(); + + // Non-KMS method, client is not cached + client3.toString(); + supplier.getClient(REGION_3); + verify(kmsClientBuilder, times(8)).build(); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java new file mode 100644 index 000000000..e9dc140d7 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java @@ -0,0 +1,243 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.RequestClientOptions; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; +import com.amazonaws.encryptionsdk.internal.VersionInfo; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.model.DecryptRequest; +import com.amazonaws.services.kms.model.DecryptResult; +import com.amazonaws.services.kms.model.EncryptRequest; +import com.amazonaws.services.kms.model.GenerateDataKeyRequest; +import com.amazonaws.services.kms.model.KMSInvalidStateException; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class KmsDataKeyEncryptionDaoTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + private static final SecretKey DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final List GRANT_TOKENS = Collections.singletonList("testGrantToken"); + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + private static final EncryptedDataKey ENCRYPTED_DATA_KEY = new KeyBlob(KMS_PROVIDER_ID, + "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210".getBytes(EncryptedDataKey.PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + + @Test + void testEncryptAndDecrypt() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + String keyId = client.createKey().getKeyMetadata().getArn(); + EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey(keyId, DATA_KEY, ENCRYPTION_CONTEXT); + + ArgumentCaptor er = ArgumentCaptor.forClass(EncryptRequest.class); + verify(client, times(1)).encrypt(er.capture()); + + EncryptRequest actualRequest = er.getValue(); + + assertEquals(keyId, actualRequest.getKeyId()); + assertEquals(GRANT_TOKENS, actualRequest.getGrantTokens()); + assertEquals(ENCRYPTION_CONTEXT, actualRequest.getEncryptionContext()); + assertArrayEquals(DATA_KEY.getEncoded(), actualRequest.getPlaintext().array()); + assertUserAgent(actualRequest); + + assertEquals(KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); + assertArrayEquals(keyId.getBytes(EncryptedDataKey.PROVIDER_ENCODING), encryptedDataKeyResult.getProviderInformation()); + assertNotNull(encryptedDataKeyResult.getEncryptedDataKey()); + + DataKeyEncryptionDao.DecryptDataKeyResult decryptDataKeyResult = dao.decryptDataKey(encryptedDataKeyResult, ALGORITHM_SUITE, ENCRYPTION_CONTEXT); + + ArgumentCaptor decrypt = ArgumentCaptor.forClass(DecryptRequest.class); + verify(client, times(1)).decrypt(decrypt.capture()); + + DecryptRequest actualDecryptRequest = decrypt.getValue(); + assertEquals(GRANT_TOKENS, actualDecryptRequest.getGrantTokens()); + assertEquals(ENCRYPTION_CONTEXT, actualDecryptRequest.getEncryptionContext()); + assertArrayEquals(encryptedDataKeyResult.getEncryptedDataKey(), actualDecryptRequest.getCiphertextBlob().array()); + assertUserAgent(actualDecryptRequest); + + assertEquals(DATA_KEY, decryptDataKeyResult.getPlaintextDataKey()); + assertEquals(keyId, decryptDataKeyResult.getKeyArn()); + } + + @Test + void testGenerateAndDecrypt() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + String keyId = client.createKey().getKeyMetadata().getArn(); + DataKeyEncryptionDao.GenerateDataKeyResult generateDataKeyResult = dao.generateDataKey(keyId, ALGORITHM_SUITE, ENCRYPTION_CONTEXT); + + ArgumentCaptor gr = ArgumentCaptor.forClass(GenerateDataKeyRequest.class); + verify(client, times(1)).generateDataKey(gr.capture()); + + GenerateDataKeyRequest actualRequest = gr.getValue(); + + assertEquals(keyId, actualRequest.getKeyId()); + assertEquals(GRANT_TOKENS, actualRequest.getGrantTokens()); + assertEquals(ENCRYPTION_CONTEXT, actualRequest.getEncryptionContext()); + assertEquals(ALGORITHM_SUITE.getDataKeyLength(), actualRequest.getNumberOfBytes()); + assertUserAgent(actualRequest); + + assertNotNull(generateDataKeyResult.getPlaintextDataKey()); + assertEquals(ALGORITHM_SUITE.getDataKeyLength(), generateDataKeyResult.getPlaintextDataKey().getEncoded().length); + assertEquals(ALGORITHM_SUITE.getDataKeyAlgo(), generateDataKeyResult.getPlaintextDataKey().getAlgorithm()); + assertNotNull(generateDataKeyResult.getEncryptedDataKey()); + + DataKeyEncryptionDao.DecryptDataKeyResult decryptDataKeyResult = dao.decryptDataKey(generateDataKeyResult.getEncryptedDataKey(), ALGORITHM_SUITE, ENCRYPTION_CONTEXT); + + ArgumentCaptor decrypt = ArgumentCaptor.forClass(DecryptRequest.class); + verify(client, times(1)).decrypt(decrypt.capture()); + + DecryptRequest actualDecryptRequest = decrypt.getValue(); + assertEquals(GRANT_TOKENS, actualDecryptRequest.getGrantTokens()); + assertEquals(ENCRYPTION_CONTEXT, actualDecryptRequest.getEncryptionContext()); + assertArrayEquals(generateDataKeyResult.getEncryptedDataKey().getEncryptedDataKey(), actualDecryptRequest.getCiphertextBlob().array()); + assertUserAgent(actualDecryptRequest); + + assertEquals(generateDataKeyResult.getPlaintextDataKey(), decryptDataKeyResult.getPlaintextDataKey()); + assertEquals(keyId, decryptDataKeyResult.getKeyArn()); + } + + @Test + void testEncryptWithRawKeyId() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + String keyId = client.createKey().getKeyMetadata().getArn(); + String rawKeyId = keyId.split("/")[1]; + EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey(rawKeyId, DATA_KEY, ENCRYPTION_CONTEXT); + + ArgumentCaptor er = ArgumentCaptor.forClass(EncryptRequest.class); + verify(client, times(1)).encrypt(er.capture()); + + EncryptRequest actualRequest = er.getValue(); + + assertEquals(rawKeyId, actualRequest.getKeyId()); + assertEquals(GRANT_TOKENS, actualRequest.getGrantTokens()); + assertEquals(ENCRYPTION_CONTEXT, actualRequest.getEncryptionContext()); + assertArrayEquals(DATA_KEY.getEncoded(), actualRequest.getPlaintext().array()); + assertUserAgent(actualRequest); + + assertEquals(KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); + assertArrayEquals(keyId.getBytes(EncryptedDataKey.PROVIDER_ENCODING), encryptedDataKeyResult.getProviderInformation()); + assertNotNull(encryptedDataKeyResult.getEncryptedDataKey()); + } + + @Test + void testEncryptWrongKeyFormat() { + SecretKey key = mock(SecretKey.class); + when(key.getFormat()).thenReturn("BadFormat"); + + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + String keyId = client.createKey().getKeyMetadata().getArn(); + + assertThrows(IllegalArgumentException.class, () -> dao.encryptDataKey(keyId, key, ENCRYPTION_CONTEXT)); + } + + @Test + void testKmsFailure() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + String keyId = client.createKey().getKeyMetadata().getArn(); + doThrow(new KMSInvalidStateException("fail")).when(client).generateDataKey(isA(GenerateDataKeyRequest.class)); + doThrow(new KMSInvalidStateException("fail")).when(client).encrypt(isA(EncryptRequest.class)); + doThrow(new KMSInvalidStateException("fail")).when(client).decrypt(isA(DecryptRequest.class)); + + assertThrows(AwsCryptoException.class, () -> dao.generateDataKey(keyId, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey(keyId, DATA_KEY, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + } + + @Test + void testUnsupportedRegionException() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + String keyId = client.createKey().getKeyMetadata().getArn(); + doThrow(new UnsupportedRegionException("fail")).when(client).generateDataKey(isA(GenerateDataKeyRequest.class)); + doThrow(new UnsupportedRegionException("fail")).when(client).encrypt(isA(EncryptRequest.class)); + doThrow(new UnsupportedRegionException("fail")).when(client).decrypt(isA(DecryptRequest.class)); + + assertThrows(AwsCryptoException.class, () -> dao.generateDataKey(keyId, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey(keyId, DATA_KEY, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + } + + @Test + void testDecryptBadKmsKeyId() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + DecryptResult badResult = new DecryptResult(); + badResult.setKeyId("badKeyId"); + + doReturn(badResult).when(client).decrypt(isA(DecryptRequest.class)); + + assertThrows(MismatchedDataKeyException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + } + + @Test + void testDecryptBadKmsKeyLength() { + AWSKMS client = spy(new MockKMSClient()); + DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + + DecryptResult badResult = new DecryptResult(); + badResult.setKeyId(new String(ENCRYPTED_DATA_KEY.getProviderInformation(), EncryptedDataKey.PROVIDER_ENCODING)); + badResult.setPlaintext(ByteBuffer.allocate(ALGORITHM_SUITE.getDataKeyLength() + 1)); + + doReturn(badResult).when(client).decrypt(isA(DecryptRequest.class)); + + assertThrows(IllegalStateException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + } + + private void assertUserAgent(AmazonWebServiceRequest request) { + assertTrue(request.getRequestClientOptions().getClientMarker(RequestClientOptions.Marker.USER_AGENT) + .contains(VersionInfo.USER_AGENT)); + } + +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java new file mode 100644 index 000000000..e4c731ee7 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.model.KeyBlob; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KmsMasterKeyTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("test", "value"); + private static final String CMK_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; + @Mock KmsDataKeyEncryptionDao dataKeyEncryptionDao; + + /** + * Test that when decryption of an encrypted data key throws a MismatchedDataKeyException, this + * key is skipped and another key in the list of keys is decrypted. + */ + @Test + void testMismatchedDataKeyException() { + EncryptedDataKey encryptedDataKey1 = new KeyBlob(KMS_PROVIDER_ID, "KeyId1".getBytes(PROVIDER_ENCODING), generate(64)); + EncryptedDataKey encryptedDataKey2 = new KeyBlob(KMS_PROVIDER_ID, "KeyId2".getBytes(PROVIDER_ENCODING), generate(64)); + SecretKey secretKey = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + + when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenThrow(new MismatchedDataKeyException()); + when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DataKeyEncryptionDao.DecryptDataKeyResult("KeyId2", secretKey)); + + KmsMasterKey kmsMasterKey = new KmsMasterKey(dataKeyEncryptionDao, CMK_ARN, null); + + List encryptedDataKeys = new ArrayList<>(); + encryptedDataKeys.add(encryptedDataKey1); + encryptedDataKeys.add(encryptedDataKey2); + + DataKey result = kmsMasterKey.decryptDataKey(ALGORITHM_SUITE, encryptedDataKeys, ENCRYPTION_CONTEXT); + + assertEquals(secretKey, result.getKey()); + } + +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java new file mode 100644 index 000000000..e8bd05477 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import com.amazonaws.services.kms.AWSKMS; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class KmsUtilsTest { + + private static final String VALID_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; + private static final String VALID_ALIAS_ARN = "arn:aws:kms:us-east-1:999999999999:alias/MyCryptoKey"; + private static final String VALID_ALIAS = "alias/MyCryptoKey"; + private static final String VALID_RAW_KEY_ID = "01234567-89ab-cdef-fedc-ba9876543210"; + + @Mock + private AWSKMS client; + + + @Test + void testGetClientByArn() { + assertEquals(client, KmsUtils.getClientByArn(VALID_ARN, s -> client)); + assertEquals(client, KmsUtils.getClientByArn(VALID_ALIAS_ARN, s -> client)); + assertEquals(client, KmsUtils.getClientByArn(VALID_ALIAS, s -> client)); + assertThrows(MalformedArnException.class, () -> KmsUtils.getClientByArn("arn:invalid", s -> client)); + assertEquals(client, KmsUtils.getClientByArn(VALID_RAW_KEY_ID, s -> client)); + } + + @Test + void testIsArnWellFormed() { + assertTrue(KmsUtils.isArnWellFormed(VALID_ARN)); + assertTrue(KmsUtils.isArnWellFormed(VALID_ALIAS_ARN)); + assertTrue(KmsUtils.isArnWellFormed(VALID_ALIAS)); + assertFalse(KmsUtils.isArnWellFormed(VALID_RAW_KEY_ID)); + assertFalse(KmsUtils.isArnWellFormed("arn:invalid")); + + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/MockKMSClient.java b/src/test/java/com/amazonaws/encryptionsdk/kms/MockKMSClient.java index 37fe9cbff..00ce5c074 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/MockKMSClient.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/MockKMSClient.java @@ -29,7 +29,7 @@ import com.amazonaws.ResponseMetadata; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; -import com.amazonaws.services.kms.AWSKMSClient; +import com.amazonaws.services.kms.AbstractAWSKMS; import com.amazonaws.services.kms.model.CreateAliasRequest; import com.amazonaws.services.kms.model.CreateAliasResult; import com.amazonaws.services.kms.model.CreateGrantRequest; @@ -85,7 +85,7 @@ import com.amazonaws.services.kms.model.UpdateKeyDescriptionRequest; import com.amazonaws.services.kms.model.UpdateKeyDescriptionResult; -public class MockKMSClient extends AWSKMSClient { +public class MockKMSClient extends AbstractAWSKMS { private static final SecureRandom rnd = new SecureRandom(); private static final String ACCOUNT_ID = "01234567890"; private final Map results_ = new HashMap<>(); From a6893b6856d2dfcc57c9dc187a42af4a86619a5b Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Wed, 18 Dec 2019 14:23:26 -0800 Subject: [PATCH 06/18] Define the MasterKeyProvider Keyring. (#149) --- .../internal/AesGcmJceKeyCipher.java | 2 +- .../encryptionsdk/internal/JceKeyCipher.java | 13 +- .../internal/RsaJceKeyCipher.java | 2 +- .../encryptionsdk/jce/JceMasterKey.java | 10 + .../keyrings/MasterKeyProviderKeyring.java | 137 ++++++++ .../encryptionsdk/keyrings/RawAesKeyring.java | 14 - .../encryptionsdk/keyrings/RawKeyring.java | 42 ++- .../encryptionsdk/keyrings/RawRsaKeyring.java | 10 - .../keyrings/StandardKeyrings.java | 12 + .../MasterKeyProviderKeyringTest.java | 332 ++++++++++++++++++ .../keyrings/RawKeyringTest.java | 10 - 11 files changed, 530 insertions(+), 54 deletions(-) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java index 7a4511f01..00f2cbbd1 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/AesGcmJceKeyCipher.java @@ -32,7 +32,7 @@ class AesGcmJceKeyCipher extends JceKeyCipher { private static final int SPEC_LENGTH = Integer.BYTES + Integer.BYTES + NONCE_LENGTH; AesGcmJceKeyCipher(SecretKey key) { - super(key, key); + super(key, key, true); } private static byte[] specToBytes(final GCMParameterSpec spec) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java index 643278a71..16aefda35 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/JceKeyCipher.java @@ -36,6 +36,7 @@ public abstract class JceKeyCipher { private final Key wrappingKey; private final Key unwrappingKey; private static final Charset KEY_NAME_ENCODING = StandardCharsets.UTF_8; + private final boolean encryptionContextSigned; /** * Returns a new instance of a JceKeyCipher based on the @@ -60,9 +61,10 @@ public static JceKeyCipher rsa(PublicKey wrappingKey, PrivateKey unwrappingKey, return new RsaJceKeyCipher(wrappingKey, unwrappingKey, transformation); } - JceKeyCipher(Key wrappingKey, Key unwrappingKey) { + JceKeyCipher(Key wrappingKey, Key unwrappingKey, boolean encryptionContextSigned) { this.wrappingKey = wrappingKey; this.unwrappingKey = unwrappingKey; + this.encryptionContextSigned = encryptionContextSigned; } abstract WrappingData buildWrappingCipher(Key key, Map encryptionContext) throws GeneralSecurityException; @@ -70,6 +72,15 @@ public static JceKeyCipher rsa(PublicKey wrappingKey, PrivateKey unwrappingKey, abstract Cipher buildUnwrappingCipher(Key key, byte[] extraInfo, int offset, Map encryptionContext) throws GeneralSecurityException; + /** + * Returns true if this key cipher supports signing and verification + * of the encryption context. + * + * @return True if encryption context signing/verification is supported. + */ + public boolean isEncryptionContextSigned() { + return encryptionContextSigned; + } /** * Encrypts the given key, incorporating the given keyName and encryptionContext. diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java b/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java index c830f5487..47b65514a 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/RsaJceKeyCipher.java @@ -44,7 +44,7 @@ class RsaJceKeyCipher extends JceKeyCipher { private final String transformation_; RsaJceKeyCipher(PublicKey wrappingKey, PrivateKey unwrappingKey, String transformation) { - super(wrappingKey, unwrappingKey); + super(wrappingKey, unwrappingKey, false); final Matcher matcher = SUPPORTED_TRANSFORMATIONS.matcher(transformation); if (matcher.matches()) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java index 4995066cb..6c6e03e34 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java @@ -104,6 +104,16 @@ public String getKeyId() { return keyId_; } + /** + * Returns true if the underlying key cipher supports signing and + * verification of the encryption context. + * + * @return True if encryption context signing/verification is supported. + */ + public boolean isEncryptionContextSigned() { + return jceKeyCipher_.isEncryptionContextSigned(); + } + @Override public DataKey generateDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java new file mode 100644 index 000000000..7e0b14675 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java @@ -0,0 +1,137 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.MasterKeyRequest; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; + +import java.util.ArrayList; +import java.util.List; + +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY; + +/** + * A keyring which wraps a legacy MasterKeyProvider to + * facilitate transition to keyrings. + */ +class MasterKeyProviderKeyring> implements Keyring { + + private final MasterKeyProvider masterKeyProvider; + + MasterKeyProviderKeyring(MasterKeyProvider masterKeyProvider) { + requireNonNull(masterKeyProvider, "masterKeyProvider is required"); + + this.masterKeyProvider = masterKeyProvider; + } + + @Override + public void onEncrypt(EncryptionMaterials encryptionMaterials) { + requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + + final List masterKeys = masterKeyProvider.getMasterKeysForEncryption(MasterKeyRequest.newBuilder() + .setEncryptionContext(encryptionMaterials.getEncryptionContext()).build()); + + if (masterKeys == null || masterKeys.isEmpty()) { + throw new AwsCryptoException("No master keys available from the master key provider."); + } + + final K primaryMasterKey = masterKeys.get(0); + final List masterKeysToEncryptWith = new ArrayList<>(masterKeys); + + if (!encryptionMaterials.hasPlaintextDataKey()) { + final DataKey dataKey = primaryMasterKey.generateDataKey( + encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext()); + encryptionMaterials.setPlaintextDataKey(dataKey.getKey(), new KeyringTraceEntry( + primaryMasterKey.getProviderId(), primaryMasterKey.getKeyId(), KeyringTraceFlag.GENERATED_DATA_KEY)); + encryptionMaterials.addEncryptedDataKey(dataKey, encryptTraceEntry(primaryMasterKey)); + // The primary master key has already been used for encryption, so remove it from the list to encrypt with + masterKeysToEncryptWith.remove(primaryMasterKey); + } + + final DataKey dataKey = new DataKey<>(encryptionMaterials.getPlaintextDataKey(), EMPTY_BYTE_ARRAY, + EMPTY_BYTE_ARRAY, primaryMasterKey); + + for (K masterKey : masterKeysToEncryptWith) { + final EncryptedDataKey encryptedDataKey = masterKey.encryptDataKey(encryptionMaterials.getAlgorithmSuite(), + encryptionMaterials.getEncryptionContext(), dataKey); + encryptionMaterials.addEncryptedDataKey(encryptedDataKey, encryptTraceEntry(masterKey)); + } + } + + @Override + public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + requireNonNull(decryptionMaterials, "decryptionMaterials are required"); + requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); + + if (decryptionMaterials.hasPlaintextDataKey()) { + return; + } + + final DataKey dataKey; + try { + dataKey = masterKeyProvider.decryptDataKey(decryptionMaterials.getAlgorithmSuite(), encryptedDataKeys, + decryptionMaterials.getEncryptionContext()); + } catch (CannotUnwrapDataKeyException e) { + return; + } + + decryptionMaterials.setPlaintextDataKey(dataKey.getKey(), decryptTraceEntry(dataKey.getMasterKey())); + } + + private boolean signedEncryptionContext(MasterKey masterKey) { + if (masterKey instanceof KmsMasterKey) { + return true; + } + + if (masterKey instanceof JceMasterKey) { + return ((JceMasterKey) masterKey).isEncryptionContextSigned(); + } + + return false; + } + + private KeyringTraceEntry encryptTraceEntry(MasterKey masterKey) { + final List flags = new ArrayList<>(); + flags.add(ENCRYPTED_DATA_KEY); + + if (signedEncryptionContext(masterKey)) { + flags.add(SIGNED_ENCRYPTION_CONTEXT); + } + + return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{})); + } + + private KeyringTraceEntry decryptTraceEntry(MasterKey masterKey) { + final List flags = new ArrayList<>(); + flags.add(DECRYPTED_DATA_KEY); + + if (signedEncryptionContext(masterKey)) { + flags.add(VERIFIED_ENCRYPTION_CONTEXT); + } + + return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{})); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java index 5832658a9..bbd2a7495 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyring.java @@ -48,18 +48,4 @@ boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { return true; } - - @Override - KeyringTraceEntry traceOnEncrypt() { - return new KeyringTraceEntry(keyNamespace, keyName, - KeyringTraceFlag.ENCRYPTED_DATA_KEY, - KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT); - } - - @Override - KeyringTraceEntry traceOnDecrypt() { - return new KeyringTraceEntry(keyNamespace, keyName, - KeyringTraceFlag.DECRYPTED_DATA_KEY, - KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); - } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java index 8daa567f8..3b4a8606e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java @@ -24,6 +24,11 @@ import java.util.logging.Logger; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.Validate.notBlank; @@ -57,20 +62,6 @@ abstract class RawKeyring implements Keyring { */ abstract boolean validToDecrypt(EncryptedDataKey encryptedDataKey); - /** - * Gets the trace entry to add the the keyring trace upon successful encryption. - * - * @return The keyring trace entry. - */ - abstract KeyringTraceEntry traceOnEncrypt(); - - /** - * Gets the trace entry to add to the keyring trace upon successful decryption. - * - * @return The keyring trace entry. - */ - abstract KeyringTraceEntry traceOnDecrypt(); - @Override public void onEncrypt(EncryptionMaterials encryptionMaterials) { requireNonNull(encryptionMaterials, "encryptionMaterials are required"); @@ -82,7 +73,8 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { final EncryptedDataKey encryptedDataKey = jceKeyCipher.encryptKey( encryptionMaterials.getPlaintextDataKey().getEncoded(), keyName, keyNamespace, encryptionMaterials.getEncryptionContext()); - encryptionMaterials.addEncryptedDataKey(encryptedDataKey, traceOnEncrypt()); + encryptionMaterials.addEncryptedDataKey(encryptedDataKey, + new KeyringTraceEntry(keyNamespace, keyName, encryptTraceFlags())); } @Override @@ -101,7 +93,7 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List masterKeyProvider) { + return new MasterKeyProviderKeyring<>(masterKeyProvider); + } + + /** * Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create, * encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). * diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java new file mode 100644 index 000000000..64b8b26dd --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java @@ -0,0 +1,332 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.MasterKeyRequest; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; +import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MasterKeyProviderKeyringTest { + + private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; + private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + + @Test + void testOnEncryptWithoutPlaintextDataKey() { + + MasterKeyProvider masterKeyProvider = mock(JceMasterKey.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + + JceMasterKey masterKey1 = mock(JceMasterKey.class); + JceMasterKey masterKey2 = mock(JceMasterKey.class); + + List masterKeys = new ArrayList<>(); + masterKeys.add(masterKey1); + masterKeys.add(masterKey2); + + ArgumentCaptor masterKeyRequestCaptor = ArgumentCaptor.forClass(MasterKeyRequest.class); + ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(DataKey.class); + + final String KEY_ID_1 = "KeyId1"; + final String KEY_ID_2 = "KeyId2"; + final String PROVIDER_1 = "Provider1"; + final String PROVIDER_2 = "Provider2"; + + DataKey dataKey1 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_1.getBytes(PROVIDER_ENCODING), masterKey1); + DataKey dataKey2 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_2.getBytes(PROVIDER_ENCODING), masterKey2); + + when(masterKeyProvider.getMasterKeysForEncryption(masterKeyRequestCaptor.capture())).thenReturn(masterKeys); + when(masterKey1.generateDataKey(ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenReturn(dataKey1); + when(masterKey2.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) + .thenReturn(dataKey2); + when(masterKey1.getProviderId()).thenReturn(PROVIDER_1); + when(masterKey1.getKeyId()).thenReturn(KEY_ID_1); + when(masterKey2.getProviderId()).thenReturn(PROVIDER_2); + when(masterKey2.getKeyId()).thenReturn(KEY_ID_2); + when(masterKey1.isEncryptionContextSigned()).thenReturn(true); + when(masterKey2.isEncryptionContextSigned()).thenReturn(false); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(ENCRYPTION_CONTEXT, masterKeyRequestCaptor.getValue().getEncryptionContext()); + assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getValue().getKey()); + assertEncryptedDataKeyEquals(dataKey1, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEncryptedDataKeyEquals(dataKey2, encryptionMaterials.getEncryptedDataKeys().get(1)); + assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, GENERATED_DATA_KEY), + encryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), + encryptionMaterials.getKeyringTrace().getEntries().get(1)); + assertEquals(new KeyringTraceEntry(PROVIDER_2, KEY_ID_2, ENCRYPTED_DATA_KEY), + encryptionMaterials.getKeyringTrace().getEntries().get(2)); + } + + @Test + void testOnEncryptWithPlaintextDataKey() { + + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + + KmsMasterKey masterKey1 = mock(KmsMasterKey.class); + KmsMasterKey masterKey2 = mock(KmsMasterKey.class); + + List masterKeys = new ArrayList<>(); + masterKeys.add(masterKey1); + masterKeys.add(masterKey2); + + ArgumentCaptor masterKeyRequestCaptor = ArgumentCaptor.forClass(MasterKeyRequest.class); + ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(DataKey.class); + + final String KEY_ID_1 = "KeyId1"; + final String KEY_ID_2 = "KeyId2"; + final String PROVIDER_1 = "Provider1"; + final String PROVIDER_2 = "Provider2"; + + DataKey dataKey1 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_1.getBytes(PROVIDER_ENCODING), masterKey1); + DataKey dataKey2 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_2.getBytes(PROVIDER_ENCODING), masterKey2); + + when(masterKeyProvider.getMasterKeysForEncryption(masterKeyRequestCaptor.capture())).thenReturn(masterKeys); + when(masterKey1.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) + .thenReturn(dataKey1); + when(masterKey2.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) + .thenReturn(dataKey2); + when(masterKey1.getProviderId()).thenReturn(PROVIDER_1); + when(masterKey1.getKeyId()).thenReturn(KEY_ID_1); + when(masterKey2.getProviderId()).thenReturn(PROVIDER_2); + when(masterKey2.getKeyId()).thenReturn(KEY_ID_2); + + keyring.onEncrypt(encryptionMaterials); + + assertEquals(ENCRYPTION_CONTEXT, masterKeyRequestCaptor.getValue().getEncryptionContext()); + assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getAllValues().get(0).getKey()); + assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getAllValues().get(1).getKey()); + assertEncryptedDataKeyEquals(dataKey1, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEncryptedDataKeyEquals(dataKey2, encryptionMaterials.getEncryptedDataKeys().get(1)); + assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), + encryptionMaterials.getKeyringTrace().getEntries().get(0)); + assertEquals(new KeyringTraceEntry(PROVIDER_2, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), + encryptionMaterials.getKeyringTrace().getEntries().get(1)); + } + + @SuppressWarnings("unchecked") + @Test + void testOnEncryptWithNonKmsOrJceMasterKeyProvider() { + + MasterKeyProvider masterKeyProvider = mock(MasterKeyProvider.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .build(); + + Keyring keyring = new MasterKeyProviderKeyring(masterKeyProvider); + + MasterKey masterKey = mock(MasterKey.class); + + final String KEY_ID = "KeyId1"; + final String PROVIDER = "Provider1"; + + DataKey dataKey = new DataKey(PLAINTEXT_DATA_KEY, generate(100), KEY_ID.getBytes(PROVIDER_ENCODING), masterKey); + + when(masterKeyProvider.getMasterKeysForEncryption(isA(MasterKeyRequest.class))).thenReturn(singletonList(masterKey)); + when(masterKey.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), isA(DataKey.class))) + .thenReturn(dataKey); + when(masterKey.getProviderId()).thenReturn(PROVIDER); + when(masterKey.getKeyId()).thenReturn(KEY_ID); + + keyring.onEncrypt(encryptionMaterials); + + assertEncryptedDataKeyEquals(dataKey, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, ENCRYPTED_DATA_KEY), + encryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testOnEncryptWithNoMasterKeys() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + + when(masterKeyProvider.getMasterKeysForEncryption(isA(MasterKeyRequest.class))).thenReturn(emptyList()); + + assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); + } + + @Test + void testOnDecryptWithPlaintextDataKey() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintextDataKey(PLAINTEXT_DATA_KEY) + .build(); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, emptyList()); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + } + + @Test + void testOnDecrypt() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + KmsMasterKey masterKey = mock(KmsMasterKey.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + final String KEY_ID = "KeyId1"; + final String PROVIDER = "Provider1"; + + EncryptedDataKey encryptedDataKey = new KeyBlob(PROVIDER, + KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenReturn(new DataKey<>(PLAINTEXT_DATA_KEY, encryptedDataKey.getEncryptedDataKey(), + encryptedDataKey.getProviderInformation(), masterKey)); + when(masterKey.getProviderId()).thenReturn(PROVIDER); + when(masterKey.getKeyId()).thenReturn(KEY_ID); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, DECRYPTED_DATA_KEY, VERIFIED_ENCRYPTION_CONTEXT), + decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + @Test + void testOnDecryptMasterKeyCannotUnwrapDataKeyException() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey encryptedDataKey = mock(EncryptedDataKey.class); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenThrow(new CannotUnwrapDataKeyException()); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); + + assertFalse(decryptionMaterials.hasPlaintextDataKey()); + } + + @Test + void testOnDecryptMasterKeyOtherException() { + MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + EncryptedDataKey encryptedDataKey = mock(EncryptedDataKey.class); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenThrow(new AwsCryptoException()); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + assertThrows(AwsCryptoException.class, () -> keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey))); + } + + @Test + void testOnDecryptNonVerifiedEncryptionContext() { + MasterKeyProvider masterKeyProvider = mock(JceMasterKey.class); + JceMasterKey masterKey = mock(JceMasterKey.class); + + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) + .encryptionContext(ENCRYPTION_CONTEXT) + .build(); + + final String KEY_ID = "KeyId1"; + final String PROVIDER = "Provider1"; + + EncryptedDataKey encryptedDataKey = new KeyBlob(PROVIDER, + KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); + + when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) + .thenReturn(new DataKey<>(PLAINTEXT_DATA_KEY, encryptedDataKey.getEncryptedDataKey(), + encryptedDataKey.getProviderInformation(), masterKey)); + when(masterKey.getProviderId()).thenReturn(PROVIDER); + when(masterKey.getKeyId()).thenReturn(KEY_ID); + when(masterKey.isEncryptionContextSigned()).thenReturn(false); + + Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); + keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); + + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, DECRYPTED_DATA_KEY), + decryptionMaterials.getKeyringTrace().getEntries().get(0)); + } + + private static void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { + assertEquals(expected.getProviderId(), actual.getProviderId()); + assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); + assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java index 945aa17bc..3fe1fb416 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java @@ -66,16 +66,6 @@ void setup() throws Exception { boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { return !encryptedDataKey.getProviderId().equals(INVALID_DATA_KEY.getProviderId()); } - - @Override - KeyringTraceEntry traceOnEncrypt() { - return ENCRYPTED_DATA_KEY_TRACE; - } - - @Override - KeyringTraceEntry traceOnDecrypt() { - return DECRYPTED_DATA_KEY_TRACE; - } }; } From 1ee9643f6ea64a3310137d8734b0a552ff800e40 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Wed, 22 Jan 2020 16:00:07 -0800 Subject: [PATCH 07/18] Using original Materials classes instead of new Keyring classes (#152) * Using original Materials classes instead of new Keyring classes Since the CryptoMaterialsManager interface uses the original EncryptionMaterials and DecryptionMaterials, we would force customers to update their code once we deprecate those materials in favor of the new Keyring versions. To avoid this, Keyrings will now use modified versions of the original Materials classes, with certain methods deprecated. In addition, this commit removed the MasterKeyProviderKeyring, which was found to not be feasible for the Java ESDK since MasterKeys are provided in the result object, which would force MasterKeys to be incorporated into Keyrings. * Adding back hasCleartextDataKey methods --- .../keyrings/DecryptionMaterials.java | 227 ------------ .../keyrings/EncryptionMaterials.java | 270 -------------- .../encryptionsdk/keyrings/Keyring.java | 2 + .../encryptionsdk/keyrings/KmsKeyring.java | 23 +- .../keyrings/MasterKeyProviderKeyring.java | 137 -------- .../encryptionsdk/keyrings/MultiKeyring.java | 12 +- .../encryptionsdk/keyrings/RawKeyring.java | 22 +- .../keyrings/StandardKeyrings.java | 12 - .../model/DecryptionMaterials.java | 152 +++++++- .../model/EncryptionMaterials.java | 105 +++++- .../keyrings/KmsKeyringTest.java | 132 ++++--- .../MasterKeyProviderKeyringTest.java | 332 ------------------ .../keyrings/MultiKeyringTest.java | 22 +- .../keyrings/RawAesKeyringTest.java | 40 ++- .../keyrings/RawKeyringTest.java | 82 +++-- .../keyrings/RawRsaKeyringTest.java | 41 ++- .../DecryptionMaterialsTest.java | 103 +++--- .../EncryptionMaterialsTest.java | 114 +++--- 18 files changed, 561 insertions(+), 1267 deletions(-) delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyring.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java rename src/test/java/com/amazonaws/encryptionsdk/{keyrings => model}/DecryptionMaterialsTest.java (60%) rename src/test/java/com/amazonaws/encryptionsdk/{keyrings => model}/EncryptionMaterialsTest.java (63%) diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java deleted file mode 100644 index 1cf84058e..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterials.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.keyrings; - -import com.amazonaws.encryptionsdk.CryptoAlgorithm; - -import javax.crypto.SecretKey; -import java.security.PublicKey; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import static java.util.Objects.requireNonNull; -import static org.apache.commons.lang3.Validate.isTrue; - -/** - * Contains the cryptographic materials needed for a decryption operation with Keyrings. - */ -public final class DecryptionMaterials { - private final CryptoAlgorithm algorithmSuite; - private SecretKey plaintextDataKey; - private final PublicKey verificationKey; - private final Map encryptionContext; - private final KeyringTrace keyringTrace; - - private DecryptionMaterials(Builder b) { - requireNonNull(b.algorithmSuite, "algorithmSuite is required"); - requireNonNull(b.keyringTrace, "keyringTrace is required"); - requireNonNull(b.encryptionContext, "encryptionContext is required"); - validatePlaintextDataKey(b.algorithmSuite, b.plaintextDataKey); - validateVerificationKey(b.algorithmSuite, b.verificationKey); - - algorithmSuite = b.algorithmSuite; - plaintextDataKey = b.plaintextDataKey; - verificationKey = b.verificationKey; - encryptionContext = b.encryptionContext; - keyringTrace = b.keyringTrace; - } - - /** - * The algorithm suite to use for this decryption operation. - */ - public CryptoAlgorithm getAlgorithmSuite() { - return algorithmSuite; - } - - /** - * Returns true if a plaintext data key has been populated. - * - * @return True if plaintext data key is populated, false otherwise. - */ - public boolean hasPlaintextDataKey() { - return this.plaintextDataKey != null; - } - - /** - * A data key to be used as input for encryption. - * - * @return The plaintext data key. - * @throws IllegalStateException if plaintext data key has not been populated. - */ - public SecretKey getPlaintextDataKey() throws IllegalStateException { - if (!hasPlaintextDataKey()) { - throw new IllegalStateException("plaintextDataKey has not been populated"); - } - return plaintextDataKey; - } - - /** - * Sets the plaintext data key. The plaintext data key must not already be populated. - * - * @param plaintextDataKey The plaintext data key. - * @param keyringTraceEntry The keyring trace entry recording this action. - */ - public void setPlaintextDataKey(SecretKey plaintextDataKey, KeyringTraceEntry keyringTraceEntry) { - if (hasPlaintextDataKey()) { - throw new IllegalStateException("plaintextDataKey was already populated"); - } - requireNonNull(plaintextDataKey, "plaintextDataKey is required"); - requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); - validatePlaintextDataKey(algorithmSuite, plaintextDataKey); - this.plaintextDataKey = plaintextDataKey; - keyringTrace.add(keyringTraceEntry); - } - - /** - * Returns true if verification key has been populated. - * - * @return True if verification key is populated, false otherwise. - */ - public boolean hasVerificationKey() { - return verificationKey != null; - } - - /** - * The verification key used for signature verification. - * - * @return The verification key. - * @throws IllegalStateException if a verification key has not been populated. - */ - public PublicKey getVerificationKey() throws IllegalStateException { - if (!hasVerificationKey()) { - throw new IllegalStateException(String.format( - "Signature verification is not supported by AlgorithmSuite %s", algorithmSuite.name())); - } - return verificationKey; - } - - public Map getEncryptionContext() { - return encryptionContext; - } - - public KeyringTrace getKeyringTrace() { - return keyringTrace; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - DecryptionMaterials that = (DecryptionMaterials) o; - return algorithmSuite == that.algorithmSuite && - Objects.equals(plaintextDataKey, that.plaintextDataKey) && - Objects.equals(verificationKey, that.verificationKey) && - Objects.equals(encryptionContext, that.encryptionContext) && - Objects.equals(keyringTrace, that.keyringTrace); - } - - @Override - public int hashCode() { - return Objects.hash(algorithmSuite, plaintextDataKey, verificationKey, encryptionContext, keyringTrace); - } - - public static Builder newBuilder(CryptoAlgorithm algorithm) { - return new Builder(algorithm); - } - - public Builder toBuilder() { - return new Builder(this); - } - - private void validatePlaintextDataKey(CryptoAlgorithm algorithmSuite, SecretKey plaintextDataKey) throws IllegalArgumentException { - if (plaintextDataKey != null) { - isTrue(algorithmSuite.getDataKeyLength() == plaintextDataKey.getEncoded().length, - String.format("Incorrect key length. Expected %s but got %s", - algorithmSuite.getDataKeyLength(), plaintextDataKey.getEncoded().length)); - isTrue(algorithmSuite.getDataKeyAlgo().equalsIgnoreCase(plaintextDataKey.getAlgorithm()), - String.format("Incorrect key algorithm. Expected %s but got %s", - algorithmSuite.getDataKeyAlgo(), plaintextDataKey.getAlgorithm())); - } - } - - /** - * Validates that a verification key is specified if and only if - * the given algorithm suite supports signature verification. - */ - private void validateVerificationKey(CryptoAlgorithm algorithmSuite, PublicKey verificationKey) throws IllegalArgumentException { - if (algorithmSuite.getTrailingSignatureAlgo() == null && verificationKey != null) { - throw new IllegalArgumentException( - String.format("Algorithm Suite %s does not support signature verification", algorithmSuite.name())); - } else if (algorithmSuite.getTrailingSignatureAlgo() != null && verificationKey == null) { - throw new IllegalArgumentException( - String.format("Algorithm %s requires a verification key for signature verification", algorithmSuite.name())); - } - } - - public static final class Builder { - private CryptoAlgorithm algorithmSuite; - private SecretKey plaintextDataKey; - private PublicKey verificationKey; - private Map encryptionContext = Collections.emptyMap(); - private KeyringTrace keyringTrace = new KeyringTrace(); - - private Builder(CryptoAlgorithm algorithmSuite) { - this.algorithmSuite = algorithmSuite; - } - - private Builder(DecryptionMaterials result) { - this.algorithmSuite = result.algorithmSuite; - this.plaintextDataKey = result.plaintextDataKey; - this.verificationKey = result.verificationKey; - this.encryptionContext = result.encryptionContext; - this.keyringTrace = result.keyringTrace; - } - - public Builder algorithmSuite(CryptoAlgorithm algorithmSuite) { - this.algorithmSuite = algorithmSuite; - return this; - } - - public Builder plaintextDataKey(SecretKey plaintextDataKey) { - this.plaintextDataKey = plaintextDataKey; - return this; - } - - public Builder verificationKey(PublicKey verificationKey) { - this.verificationKey = verificationKey; - return this; - } - - public Builder encryptionContext(Map encryptionContext) { - this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); - return this; - } - - public Builder keyringTrace(KeyringTrace keyringTrace) { - this.keyringTrace = keyringTrace; - return this; - } - - public DecryptionMaterials build() { - return new DecryptionMaterials(this); - } - } -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java deleted file mode 100644 index d52f5d355..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterials.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.keyrings; - -import com.amazonaws.encryptionsdk.CryptoAlgorithm; -import com.amazonaws.encryptionsdk.EncryptedDataKey; - -import javax.crypto.SecretKey; -import java.security.PrivateKey; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static java.util.Objects.requireNonNull; -import static org.apache.commons.lang3.Validate.isTrue; - -/** - * Contains the cryptographic materials needed for an encryption operation with Keyrings. - */ -public final class EncryptionMaterials { - private final CryptoAlgorithm algorithmSuite; - private final Map encryptionContext; - private final List encryptedDataKeys; - private SecretKey plaintextDataKey; - private final PrivateKey signingKey; - private final KeyringTrace keyringTrace; - - private EncryptionMaterials(Builder b) { - requireNonNull(b.algorithmSuite, "algorithmSuite is required"); - requireNonNull(b.keyringTrace, "keyringTrace is required"); - requireNonNull(b.encryptionContext, "encryptionContext is required"); - validatePlaintextDataKey(b.algorithmSuite, b.plaintextDataKey); - validateSigningKey(b.algorithmSuite, b.signingKey); - this.algorithmSuite = b.algorithmSuite; - this.encryptionContext = b.encryptionContext; - this.encryptedDataKeys = b.encryptedDataKeys; - this.plaintextDataKey = b.plaintextDataKey; - this.signingKey = b.signingKey; - this.keyringTrace = b.keyringTrace; - } - - public Builder toBuilder() { - return new Builder(this); - } - - public static Builder newBuilder(CryptoAlgorithm algorithmSuite) { - return new Builder(algorithmSuite); - } - - /** - * The algorithm suite to be used for encryption. - */ - public CryptoAlgorithm getAlgorithmSuite() { - return algorithmSuite; - } - - /** - * The encryption context associated with this encryption. - */ - public Map getEncryptionContext() { - return encryptionContext; - } - - /** - * An unmodifiable list of the encrypted data keys that correspond to the plaintext data key. - */ - public List getEncryptedDataKeys() { - return Collections.unmodifiableList(encryptedDataKeys); - } - - /** - * Add an encrypted data key to the list of encrypted data keys. - * - * @param encryptedDataKey The encrypted data key to add. - * @param keyringTraceEntry The keyring trace entry recording this action. - */ - public void addEncryptedDataKey(EncryptedDataKey encryptedDataKey, KeyringTraceEntry keyringTraceEntry) { - requireNonNull(encryptedDataKey, "encryptedDataKey is required"); - requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); - encryptedDataKeys.add(encryptedDataKey); - keyringTrace.add(keyringTraceEntry); - } - - /** - * Returns true if a plaintext data key has been populated. - * - * @return True if plaintext data key is populated, false otherwise. - */ - public boolean hasPlaintextDataKey() { - return this.plaintextDataKey != null; - } - - /** - * A data key to be used as input for encryption. - * - * @return The plaintext data key. - * @throws IllegalStateException if plain text data key has not been populated. - */ - public SecretKey getPlaintextDataKey() throws IllegalStateException { - if (!hasPlaintextDataKey()) { - throw new IllegalStateException("plaintextDataKey has not been populated"); - } - return plaintextDataKey; - } - - /** - * Sets the plaintext data key. The plaintext data key must not already be populated. - * - * @param plaintextDataKey The plaintext data key. - * @param keyringTraceEntry The keyring trace entry recording this action. - */ - public void setPlaintextDataKey(SecretKey plaintextDataKey, KeyringTraceEntry keyringTraceEntry) { - if (hasPlaintextDataKey()) { - throw new IllegalStateException("plaintextDataKey was already populated"); - } - requireNonNull(plaintextDataKey, "plaintextDataKey is required"); - requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); - validatePlaintextDataKey(algorithmSuite, plaintextDataKey); - this.plaintextDataKey = plaintextDataKey; - keyringTrace.add(keyringTraceEntry); - } - - /** - * Returns true if a signing key has been populated. - * - * @return True if signing key is populated, false otherwise. - */ - public boolean hasSigningKey() { - return this.signingKey != null; - } - - - /** - * The key to be used as the signing key for signature verification during encryption. - * - * @return The signing key. - * @throws IllegalStateException if signing key has not been populated. - */ - public PrivateKey getSigningKey() throws IllegalStateException { - if (!hasSigningKey()) { - throw new IllegalStateException(String.format( - "Signing is not supported by AlgorithmSuite %s", algorithmSuite.name())); - } - return signingKey; - } - - /** - * A keyring trace containing all of the actions that keyrings have taken on this set of encryption materials. - */ - public KeyringTrace getKeyringTrace() { - return keyringTrace; - } - - /** - * Validates that the given plaintext data key fits the specification - * for the data key algorithm specified in the given algorithm suite. - */ - private void validatePlaintextDataKey(CryptoAlgorithm algorithmSuite, SecretKey plaintextDataKey) throws IllegalArgumentException { - if (plaintextDataKey != null) { - isTrue(algorithmSuite.getDataKeyLength() == plaintextDataKey.getEncoded().length, - String.format("Incorrect data key length. Expected %s but got %s", - algorithmSuite.getDataKeyLength(), plaintextDataKey.getEncoded().length)); - isTrue(algorithmSuite.getDataKeyAlgo().equalsIgnoreCase(plaintextDataKey.getAlgorithm()), - String.format("Incorrect data key algorithm. Expected %s but got %s", - algorithmSuite.getDataKeyAlgo(), plaintextDataKey.getAlgorithm())); - } - } - - /** - * Validates that a signing key is specified only if and only if - * the given algorithm suite supports signature verification. - */ - private void validateSigningKey(CryptoAlgorithm algorithmSuite, PrivateKey signingKey) throws IllegalArgumentException { - if (algorithmSuite.getTrailingSignatureAlgo() == null && signingKey != null) { - throw new IllegalArgumentException( - String.format("Algorithm Suite %s does not support signing", algorithmSuite.name())); - } else if (algorithmSuite.getTrailingSignatureAlgo() != null && signingKey == null) { - throw new IllegalArgumentException( - String.format("Algorithm Suite %s requires a signing key for signing", algorithmSuite.name())); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EncryptionMaterials that = (EncryptionMaterials) o; - return algorithmSuite == that.algorithmSuite && - Objects.equals(encryptionContext, that.encryptionContext) && - Objects.equals(encryptedDataKeys, that.encryptedDataKeys) && - Objects.equals(plaintextDataKey, that.plaintextDataKey) && - Objects.equals(signingKey, that.signingKey) && - Objects.equals(keyringTrace, that.keyringTrace); - } - - @Override - public int hashCode() { - return Objects.hash(algorithmSuite, encryptionContext, encryptedDataKeys, plaintextDataKey, signingKey, keyringTrace); - } - - public static class Builder { - private CryptoAlgorithm algorithmSuite; - private Map encryptionContext = Collections.emptyMap(); - private List encryptedDataKeys = new ArrayList<>(); - private SecretKey plaintextDataKey; - private PrivateKey signingKey; - private KeyringTrace keyringTrace = new KeyringTrace(); - - private Builder(CryptoAlgorithm algorithmSuite) { - this.algorithmSuite = algorithmSuite; - } - - private Builder(EncryptionMaterials r) { - algorithmSuite = r.algorithmSuite; - encryptionContext = r.encryptionContext; - encryptedDataKeys = r.encryptedDataKeys; - plaintextDataKey = r.plaintextDataKey; - signingKey = r.signingKey; - keyringTrace = r.keyringTrace; - } - - public EncryptionMaterials build() { - return new EncryptionMaterials(this); - } - - public Builder algorithmSuite(CryptoAlgorithm algorithmSuite) { - this.algorithmSuite = algorithmSuite; - return this; - } - - public Builder encryptionContext(Map encryptionContext) { - this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); - return this; - } - - public Builder encryptedDataKeys(List encryptedDataKeys) { - this.encryptedDataKeys = new ArrayList<>(encryptedDataKeys); - return this; - } - - public Builder plaintextDataKey(SecretKey plaintextDataKey) { - this.plaintextDataKey = plaintextDataKey; - return this; - } - - public Builder signingKey(PrivateKey signingKey) { - this.signingKey = signingKey; - return this; - } - - public Builder keyringTrace(KeyringTrace keyringTrace) { - this.keyringTrace = keyringTrace; - return this; - } - } -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java index 20f4c69f5..93002270d 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java @@ -14,6 +14,8 @@ package com.amazonaws.encryptionsdk.keyrings; import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import java.util.List; diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java index 4b3974501..528b4a1dc 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java @@ -21,6 +21,9 @@ import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.GenerateDataKeyResult; import com.amazonaws.encryptionsdk.kms.KmsUtils; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.KeyBlob; import java.util.ArrayList; import java.util.HashSet; @@ -77,7 +80,7 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { // If the input encryption materials do not contain a plaintext data key and this keyring does not // have a generator defined, OnEncrypt MUST not modify the encryption materials and MUST fail. - if (!encryptionMaterials.hasPlaintextDataKey() && generatorKeyId == null) { + if (!encryptionMaterials.hasCleartextDataKey() && generatorKeyId == null) { throw new AwsCryptoException("Encryption materials must contain either a plaintext data key or a generator"); } @@ -85,7 +88,7 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { // If the input encryption materials do not contain a plaintext data key and a generator is defined onEncrypt // MUST attempt to generate a new plaintext data key and encrypt that data key by calling KMS GenerateDataKey. - if (!encryptionMaterials.hasPlaintextDataKey()) { + if (!encryptionMaterials.hasCleartextDataKey()) { generateDataKey(encryptionMaterials); } else if (generatorKeyId != null) { // If this keyring's generator is defined and was not used to generate a data key, OnEncrypt @@ -102,19 +105,19 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { private void generateDataKey(final EncryptionMaterials encryptionMaterials) { final GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey(generatorKeyId, - encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext()); + encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); - encryptionMaterials.setPlaintextDataKey(result.getPlaintextDataKey(), + encryptionMaterials.setCleartextDataKey(result.getPlaintextDataKey(), new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.GENERATED_DATA_KEY)); - encryptionMaterials.addEncryptedDataKey(result.getEncryptedDataKey(), + encryptionMaterials.addEncryptedDataKey(new KeyBlob(result.getEncryptedDataKey()), new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); } private void encryptDataKey(final String keyId, final EncryptionMaterials encryptionMaterials) { final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey(keyId, - encryptionMaterials.getPlaintextDataKey(), encryptionMaterials.getEncryptionContext()); + encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); - encryptionMaterials.addEncryptedDataKey(encryptedDataKey, + encryptionMaterials.addEncryptedDataKey(new KeyBlob(encryptedDataKey), new KeyringTraceEntry(KMS_PROVIDER_ID, keyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); } @@ -123,7 +126,7 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List> implements Keyring { - - private final MasterKeyProvider masterKeyProvider; - - MasterKeyProviderKeyring(MasterKeyProvider masterKeyProvider) { - requireNonNull(masterKeyProvider, "masterKeyProvider is required"); - - this.masterKeyProvider = masterKeyProvider; - } - - @Override - public void onEncrypt(EncryptionMaterials encryptionMaterials) { - requireNonNull(encryptionMaterials, "encryptionMaterials are required"); - - final List masterKeys = masterKeyProvider.getMasterKeysForEncryption(MasterKeyRequest.newBuilder() - .setEncryptionContext(encryptionMaterials.getEncryptionContext()).build()); - - if (masterKeys == null || masterKeys.isEmpty()) { - throw new AwsCryptoException("No master keys available from the master key provider."); - } - - final K primaryMasterKey = masterKeys.get(0); - final List masterKeysToEncryptWith = new ArrayList<>(masterKeys); - - if (!encryptionMaterials.hasPlaintextDataKey()) { - final DataKey dataKey = primaryMasterKey.generateDataKey( - encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext()); - encryptionMaterials.setPlaintextDataKey(dataKey.getKey(), new KeyringTraceEntry( - primaryMasterKey.getProviderId(), primaryMasterKey.getKeyId(), KeyringTraceFlag.GENERATED_DATA_KEY)); - encryptionMaterials.addEncryptedDataKey(dataKey, encryptTraceEntry(primaryMasterKey)); - // The primary master key has already been used for encryption, so remove it from the list to encrypt with - masterKeysToEncryptWith.remove(primaryMasterKey); - } - - final DataKey dataKey = new DataKey<>(encryptionMaterials.getPlaintextDataKey(), EMPTY_BYTE_ARRAY, - EMPTY_BYTE_ARRAY, primaryMasterKey); - - for (K masterKey : masterKeysToEncryptWith) { - final EncryptedDataKey encryptedDataKey = masterKey.encryptDataKey(encryptionMaterials.getAlgorithmSuite(), - encryptionMaterials.getEncryptionContext(), dataKey); - encryptionMaterials.addEncryptedDataKey(encryptedDataKey, encryptTraceEntry(masterKey)); - } - } - - @Override - public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { - requireNonNull(decryptionMaterials, "decryptionMaterials are required"); - requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); - - if (decryptionMaterials.hasPlaintextDataKey()) { - return; - } - - final DataKey dataKey; - try { - dataKey = masterKeyProvider.decryptDataKey(decryptionMaterials.getAlgorithmSuite(), encryptedDataKeys, - decryptionMaterials.getEncryptionContext()); - } catch (CannotUnwrapDataKeyException e) { - return; - } - - decryptionMaterials.setPlaintextDataKey(dataKey.getKey(), decryptTraceEntry(dataKey.getMasterKey())); - } - - private boolean signedEncryptionContext(MasterKey masterKey) { - if (masterKey instanceof KmsMasterKey) { - return true; - } - - if (masterKey instanceof JceMasterKey) { - return ((JceMasterKey) masterKey).isEncryptionContextSigned(); - } - - return false; - } - - private KeyringTraceEntry encryptTraceEntry(MasterKey masterKey) { - final List flags = new ArrayList<>(); - flags.add(ENCRYPTED_DATA_KEY); - - if (signedEncryptionContext(masterKey)) { - flags.add(SIGNED_ENCRYPTION_CONTEXT); - } - - return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{})); - } - - private KeyringTraceEntry decryptTraceEntry(MasterKey masterKey) { - final List flags = new ArrayList<>(); - flags.add(DECRYPTED_DATA_KEY); - - if (signedEncryptionContext(masterKey)) { - flags.add(VERIFIED_ENCRYPTION_CONTEXT); - } - - return new KeyringTraceEntry(masterKey.getProviderId(), masterKey.getKeyId(), flags.toArray(new KeyringTraceFlag[]{})); - } -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java index 7d957b281..ce6d9600c 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java @@ -16,6 +16,8 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import java.util.ArrayList; import java.util.List; @@ -50,9 +52,9 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { generatorKeyring.onEncrypt(encryptionMaterials); } - if (!encryptionMaterials.hasPlaintextDataKey()) { - throw new AwsCryptoException("Either a generator keyring must be supplied that produces a plaintext " + - "data key or a plaintext data key must already be present in the encryption materials."); + if (!encryptionMaterials.hasCleartextDataKey()) { + throw new AwsCryptoException("Either a generator keyring must be supplied that produces a cleartext " + + "data key or a cleartext data key must already be present in the encryption materials."); } for (Keyring keyring : childrenKeyrings) { @@ -65,7 +67,7 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List masterKeyProvider) { - return new MasterKeyProviderKeyring<>(masterKeyProvider); - } /** * Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create, diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java index 94423b884..722246237 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java @@ -1,26 +1,96 @@ package com.amazonaws.encryptionsdk.model; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DataKey; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceEntry; + +import javax.crypto.SecretKey; import java.security.PublicKey; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; -import com.amazonaws.encryptionsdk.DataKey; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY; +import static org.apache.commons.lang3.Validate.isTrue; public final class DecryptionMaterials { - private final DataKey dataKey; + private final CryptoAlgorithm algorithm; + private final Map encryptionContext; + private DataKey dataKey; private final PublicKey trailingSignatureKey; + private final KeyringTrace keyringTrace; private DecryptionMaterials(Builder b) { + algorithm = b.algorithm; + encryptionContext = b.encryptionContext; dataKey = b.getDataKey(); trailingSignatureKey = b.getTrailingSignatureKey(); + keyringTrace = b.keyringTrace; } + /** + * The algorithm suite to use for this decryption operation. + */ + public CryptoAlgorithm getAlgorithm() { + return algorithm; + } + + /** + * The encryption context + */ + public Map getEncryptionContext() { + return encryptionContext; + } + + /** + * @deprecated Replaced by {@link #getCleartextDataKey()} + */ + @Deprecated public DataKey getDataKey() { return dataKey; } + /** + * Sets the cleartext data key. The cleartext data key must not already be populated. + * + * @param cleartextDataKey The cleartext data key. + * @param keyringTraceEntry The keyring trace entry recording this action. + */ + public void setCleartextDataKey(SecretKey cleartextDataKey, KeyringTraceEntry keyringTraceEntry) { + if (hasCleartextDataKey()) { + throw new IllegalStateException("cleartextDataKey was already populated"); + } + requireNonNull(cleartextDataKey, "cleartextDataKey is required"); + requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + validateCleartextDataKey(algorithm, cleartextDataKey); + this.dataKey = new DataKey<>(cleartextDataKey, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, null); + keyringTrace.add(keyringTraceEntry); + } + + public SecretKey getCleartextDataKey() { + return dataKey == null ? null : dataKey.getKey(); + } + + /** + * Returns true if a cleartext data key has been populated. + * + * @return True if cleartext data key is populated, false otherwise. + */ + public boolean hasCleartextDataKey() { + return this.dataKey != null; + } + public PublicKey getTrailingSignatureKey() { return trailingSignatureKey; } + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + public static Builder newBuilder() { return new Builder(); } @@ -29,26 +99,94 @@ public Builder toBuilder() { return new Builder(this); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DecryptionMaterials that = (DecryptionMaterials) o; + + return algorithm == that.algorithm && + Objects.equals(getCleartextDataKey(), that.getCleartextDataKey()) && + Objects.equals(trailingSignatureKey, that.trailingSignatureKey) && + Objects.equals(encryptionContext, that.encryptionContext) && + Objects.equals(keyringTrace, that.keyringTrace); + } + + @Override + public int hashCode() { + return Objects.hash(algorithm, getCleartextDataKey(), trailingSignatureKey, encryptionContext, keyringTrace); + } + + private void validateCleartextDataKey(CryptoAlgorithm algorithm, SecretKey cleartextDataKey) throws IllegalArgumentException { + if (algorithm != null && cleartextDataKey != null) { + isTrue(algorithm.getDataKeyLength() == cleartextDataKey.getEncoded().length, + String.format("Incorrect key length. Expected %s but got %s", + algorithm.getDataKeyLength(), cleartextDataKey.getEncoded().length)); + isTrue(algorithm.getDataKeyAlgo().equalsIgnoreCase(cleartextDataKey.getAlgorithm()), + String.format("Incorrect key algorithm. Expected %s but got %s", + algorithm.getDataKeyAlgo(), cleartextDataKey.getAlgorithm())); + } + } + public static final class Builder { + private CryptoAlgorithm algorithm; + private Map encryptionContext = Collections.emptyMap(); private DataKey dataKey; private PublicKey trailingSignatureKey; + private KeyringTrace keyringTrace = new KeyringTrace(); private Builder(DecryptionMaterials result) { + this.algorithm = result.getAlgorithm(); + this.encryptionContext = result.getEncryptionContext(); this.dataKey = result.getDataKey(); this.trailingSignatureKey = result.getTrailingSignatureKey(); + this.keyringTrace = result.keyringTrace; } private Builder() {} + public CryptoAlgorithm getAlgorithm() { + return algorithm; + } + + public Builder setAlgorithm(CryptoAlgorithm algorithm) { + requireNonNull(algorithm, "algorithm is required"); + this.algorithm = algorithm; + return this; + } + + public Map getEncryptionContext() { + return encryptionContext; + } + + public Builder setEncryptionContext(Map encryptionContext) { + requireNonNull(encryptionContext, "encryptionContext is required"); + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + @Deprecated public DataKey getDataKey() { return dataKey; } + @Deprecated public Builder setDataKey(DataKey dataKey) { this.dataKey = dataKey; return this; } + /** + * Sets the cleartext data key. + * + * @param cleartextDataKey The cleartext data key. + */ + public Builder setCleartextDataKey(SecretKey cleartextDataKey) { + requireNonNull(cleartextDataKey, "cleartextDataKey is required"); + this.dataKey = new DataKey<>(cleartextDataKey, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, null); + return this; + } + public PublicKey getTrailingSignatureKey() { return trailingSignatureKey; } @@ -58,6 +196,16 @@ public Builder setTrailingSignatureKey(PublicKey trailingSignatureKey) { return this; } + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + public Builder setKeyringTrace(KeyringTrace keyringTrace) { + requireNonNull(keyringTrace, "keyringTrace is required"); + this.keyringTrace = keyringTrace; + return this; + } + public DecryptionMaterials build() { return new DecryptionMaterials(this); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java index 1a40d7c36..ed46dabf8 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java @@ -1,5 +1,11 @@ package com.amazonaws.encryptionsdk.model; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceEntry; + import javax.crypto.SecretKey; import java.security.PrivateKey; import java.util.ArrayList; @@ -9,8 +15,9 @@ import java.util.Map; import java.util.Objects; -import com.amazonaws.encryptionsdk.CryptoAlgorithm; -import com.amazonaws.encryptionsdk.MasterKey; +import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; /** * Contains the cryptographic materials needed for an encryption operation. @@ -21,9 +28,10 @@ public final class EncryptionMaterials { private final CryptoAlgorithm algorithm; private final Map encryptionContext; private final List encryptedDataKeys; - private final SecretKey cleartextDataKey; + private SecretKey cleartextDataKey; private final PrivateKey trailingSignatureKey; private final List masterKeys; + private final KeyringTrace keyringTrace; private EncryptionMaterials(Builder b) { this.algorithm = b.algorithm; @@ -32,6 +40,7 @@ private EncryptionMaterials(Builder b) { this.cleartextDataKey = b.cleartextDataKey; this.trailingSignatureKey = b.trailingSignatureKey; this.masterKeys = b.getMasterKeys(); + this.keyringTrace = b.keyringTrace; } public Builder toBuilder() { @@ -61,7 +70,20 @@ public Map getEncryptionContext() { * The KeyBlobs to serialize (in cleartext) into the encrypted message. */ public List getEncryptedDataKeys() { - return encryptedDataKeys; + return unmodifiableList(encryptedDataKeys); + } + + /** + * Add an encrypted data key to the list of encrypted data keys. + * + * @param encryptedDataKey The encrypted data key to add. + * @param keyringTraceEntry The keyring trace entry recording this action. + */ + public void addEncryptedDataKey(KeyBlob encryptedDataKey, KeyringTraceEntry keyringTraceEntry) { + requireNonNull(encryptedDataKey, "encryptedDataKey is required"); + requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + encryptedDataKeys.add(encryptedDataKey); + keyringTrace.add(keyringTraceEntry); } /** @@ -72,6 +94,32 @@ public SecretKey getCleartextDataKey() { return cleartextDataKey; } + /** + * Sets the cleartext data key. The cleartext data key must not already be populated. + * + * @param cleartextDataKey The cleartext data key. + * @param keyringTraceEntry The keyring trace entry recording this action. + */ + public void setCleartextDataKey(SecretKey cleartextDataKey, KeyringTraceEntry keyringTraceEntry) { + if (hasCleartextDataKey()) { + throw new IllegalStateException("cleartextDataKey was already populated"); + } + requireNonNull(cleartextDataKey, "cleartextDataKey is required"); + requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + validateCleartextDataKey(algorithm, cleartextDataKey); + this.cleartextDataKey = cleartextDataKey; + keyringTrace.add(keyringTraceEntry); + } + + /** + * Returns true if a cleartext data key has been populated. + * + * @return True is a cleartext data key has been populated, false otherwise. + */ + public boolean hasCleartextDataKey() { + return this.cleartextDataKey != null; + } + /** * The private key to be used to sign the message trailer. Must be present if any only if required by the * crypto algorithm, and the key type must likewise match the algorithm in use. @@ -86,11 +134,36 @@ public PrivateKey getTrailingSignatureKey() { /** * Contains a list of all MasterKeys that could decrypt this message. + * + * @deprecated {@link MasterKey}s have been replaced by {@link Keyring}s */ + @Deprecated public List getMasterKeys() { return masterKeys; } + /** + * A keyring trace containing all of the actions that keyrings have taken on this set of encryption materials. + */ + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + /** + * Validates that the given plaintext data key fits the specification + * for the data key algorithm specified in the given algorithm suite. + */ + private void validateCleartextDataKey(CryptoAlgorithm algorithmSuite, SecretKey cleartextDataKey) throws IllegalArgumentException { + if (algorithmSuite != null && cleartextDataKey != null) { + isTrue(algorithmSuite.getDataKeyLength() == cleartextDataKey.getEncoded().length, + String.format("Incorrect data key length. Expected %s but got %s", + algorithmSuite.getDataKeyLength(), cleartextDataKey.getEncoded().length)); + isTrue(algorithmSuite.getDataKeyAlgo().equalsIgnoreCase(cleartextDataKey.getAlgorithm()), + String.format("Incorrect data key algorithm. Expected %s but got %s", + algorithmSuite.getDataKeyAlgo(), cleartextDataKey.getAlgorithm())); + } + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -100,21 +173,23 @@ public List getMasterKeys() { Objects.equals(encryptedDataKeys, that.encryptedDataKeys) && Objects.equals(cleartextDataKey, that.cleartextDataKey) && Objects.equals(trailingSignatureKey, that.trailingSignatureKey) && - Objects.equals(masterKeys, that.masterKeys); + Objects.equals(masterKeys, that.masterKeys) && + Objects.equals(keyringTrace, that.keyringTrace); } @Override public int hashCode() { return Objects.hash(algorithm, encryptionContext, encryptedDataKeys, cleartextDataKey, trailingSignatureKey, - masterKeys); + masterKeys, keyringTrace); } public static class Builder { private CryptoAlgorithm algorithm; private Map encryptionContext = Collections.emptyMap(); - private List encryptedDataKeys = null; + private List encryptedDataKeys = new ArrayList<>(); private SecretKey cleartextDataKey; private PrivateKey trailingSignatureKey; private List masterKeys = Collections.emptyList(); + private KeyringTrace keyringTrace = new KeyringTrace(); private Builder() {} @@ -125,6 +200,7 @@ private Builder(EncryptionMaterials r) { cleartextDataKey = r.cleartextDataKey; trailingSignatureKey = r.trailingSignatureKey; setMasterKeys(r.masterKeys); + keyringTrace = r.keyringTrace; } public EncryptionMaterials build() { @@ -154,7 +230,7 @@ public List getEncryptedDataKeys() { } public Builder setEncryptedDataKeys(List encryptedDataKeys) { - this.encryptedDataKeys = Collections.unmodifiableList(new ArrayList<>(encryptedDataKeys)); + this.encryptedDataKeys = new ArrayList<>(encryptedDataKeys); return this; } @@ -176,12 +252,23 @@ public Builder setTrailingSignatureKey(PrivateKey trailingSignatureKey) { return this; } + @Deprecated public List getMasterKeys() { return masterKeys; } + @Deprecated public Builder setMasterKeys(List masterKeys) { - this.masterKeys = Collections.unmodifiableList(new ArrayList<>(masterKeys)); + this.masterKeys = unmodifiableList(new ArrayList<>(masterKeys)); + return this; + } + + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + public Builder setKeyringTrace(KeyringTrace keyringTrace) { + this.keyringTrace = keyringTrace; return this; } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java index 6ed3629ff..30ba8ba41 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java @@ -21,6 +21,8 @@ import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,6 +43,7 @@ import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -97,9 +100,10 @@ void testMalformedArns() { assertThrows(MalformedArnException.class, () -> new KmsKeyring(dataKeyEncryptionDao, null, "badArn")); assertThrows(MalformedArnException.class, () -> new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList("badArn"), GENERATOR_KEY_ID)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); @@ -107,7 +111,7 @@ void testMalformedArns() { encryptedDataKeys.add(ENCRYPTED_KEY_1); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); // Malformed Arn for a non KMS provider shouldn't fail encryptedDataKeys.clear(); @@ -122,26 +126,28 @@ void testGeneratorKeyInKeyIds() { @Test void testEncryptDecryptExistingDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); assertEquals(3, encryptionMaterials.getEncryptedDataKeys().size()); - assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_GENERATOR_KEY)); - assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_KEY_1)); - assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_KEY_2)); + assertEncryptedDataKeyEquals(ENCRYPTED_KEY_1, encryptionMaterials.getEncryptedDataKeys().get(0)); + assertEncryptedDataKeyEquals(ENCRYPTED_KEY_2, encryptionMaterials.getEncryptedDataKeys().get(1)); + assertEncryptedDataKeyEquals(ENCRYPTED_GENERATOR_KEY, encryptionMaterials.getEncryptedDataKeys().get(2)); assertEquals(3, encryptionMaterials.getKeyringTrace().getEntries().size()); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_GENERATOR_KEY_TRACE)); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_2_TRACE)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); @@ -150,7 +156,7 @@ void testEncryptDecryptExistingDataKey() { encryptedDataKeys.add(ENCRYPTED_KEY_2); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -158,15 +164,16 @@ void testEncryptDecryptExistingDataKey() { @Test void testEncryptNullDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); when(dataKeyEncryptionDao.generateDataKey(GENERATOR_KEY_ID, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenReturn(new DataKeyEncryptionDao.GenerateDataKeyResult(PLAINTEXT_DATA_KEY, ENCRYPTED_GENERATOR_KEY)); keyring.onEncrypt(encryptionMaterials); - assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); assertEquals(4, encryptionMaterials.getKeyringTrace().getEntries().size()); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(GENERATED_DATA_KEY_TRACE)); @@ -174,9 +181,10 @@ void testEncryptNullDataKey() { assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_2_TRACE)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); @@ -185,7 +193,7 @@ void testEncryptNullDataKey() { encryptedDataKeys.add(ENCRYPTED_KEY_2); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -193,10 +201,11 @@ void testEncryptNullDataKey() { @Test void testEncryptNullGenerator() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .keyringTrace(new KeyringTrace()) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setKeyringTrace(new KeyringTrace()) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); Keyring keyring = new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList(KEY_ID_1), null); @@ -204,9 +213,9 @@ void testEncryptNullGenerator() { keyring.onEncrypt(encryptionMaterials); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); - assertTrue(encryptionMaterials.getEncryptedDataKeys().contains(ENCRYPTED_KEY_1)); + assertEncryptedDataKeyEquals(ENCRYPTED_KEY_1, encryptionMaterials.getEncryptedDataKeys().get(0)); - assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().contains(ENCRYPTED_KEY_1_TRACE)); @@ -216,30 +225,32 @@ void testEncryptNullGenerator() { void testDiscoveryEncrypt() { keyring = new KmsKeyring(dataKeyEncryptionDao, null, null); - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); - assertFalse(encryptionMaterials.hasPlaintextDataKey()); + assertFalse(encryptionMaterials.hasCleartextDataKey()); assertEquals(0, encryptionMaterials.getKeyringTrace().getEntries().size()); } @Test - void testEncryptNoGeneratorOrPlaintextDataKey() { + void testEncryptNoGeneratorOrCleartextDataKey() { List keyIds = new ArrayList<>(); keyIds.add(KEY_ID_1); keyring = new KmsKeyring(dataKeyEncryptionDao, keyIds, null); - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE).build(); + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder().setAlgorithm(ALGORITHM_SUITE).build(); assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); } @Test void testDecryptFirstKeyFails() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); @@ -249,7 +260,7 @@ void testDecryptFirstKeyFails() { encryptedDataKeys.add(ENCRYPTED_KEY_2); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -257,8 +268,9 @@ void testDecryptFirstKeyFails() { @Test void testDecryptMismatchedDataKeyException() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new MismatchedDataKeyException()); @@ -268,9 +280,10 @@ void testDecryptMismatchedDataKeyException() { @Test void testDecryptFirstKeyWrongProvider() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); EncryptedDataKey wrongProviderKey = new KeyBlob("OtherProvider", KEY_ID_1.getBytes(PROVIDER_ENCODING), new byte[]{}); @@ -280,7 +293,7 @@ void testDecryptFirstKeyWrongProvider() { encryptedDataKeys.add(ENCRYPTED_KEY_2); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -290,9 +303,10 @@ void testDecryptFirstKeyWrongProvider() { void testDiscoveryDecrypt() { keyring = new KmsKeyring(dataKeyEncryptionDao, null, null); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); @@ -300,7 +314,7 @@ void testDiscoveryDecrypt() { encryptedDataKeys.add(ENCRYPTED_KEY_2); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -308,27 +322,35 @@ void testDiscoveryDecrypt() { @Test void testDecryptAlreadyDecryptedDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .encryptionContext(ENCRYPTION_CONTEXT) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_GENERATOR_KEY)); - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test void testDecryptNoDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); - assertFalse(decryptionMaterials.hasPlaintextDataKey()); + assertFalse(decryptionMaterials.hasCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } + + private void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { + assertEquals(expected.getProviderId(), actual.getProviderId()); + assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); + assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); + } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java deleted file mode 100644 index 64b8b26dd..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderKeyringTest.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.keyrings; - -import com.amazonaws.encryptionsdk.CryptoAlgorithm; -import com.amazonaws.encryptionsdk.DataKey; -import com.amazonaws.encryptionsdk.EncryptedDataKey; -import com.amazonaws.encryptionsdk.MasterKey; -import com.amazonaws.encryptionsdk.MasterKeyProvider; -import com.amazonaws.encryptionsdk.MasterKeyRequest; -import com.amazonaws.encryptionsdk.exception.AwsCryptoException; -import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; -import com.amazonaws.encryptionsdk.jce.JceMasterKey; -import com.amazonaws.encryptionsdk.kms.KmsMasterKey; -import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; -import com.amazonaws.encryptionsdk.model.KeyBlob; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; -import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.DECRYPTED_DATA_KEY; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; -import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class MasterKeyProviderKeyringTest { - - private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; - private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); - private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); - - @Test - void testOnEncryptWithoutPlaintextDataKey() { - - MasterKeyProvider masterKeyProvider = mock(JceMasterKey.class); - - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .build(); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - - JceMasterKey masterKey1 = mock(JceMasterKey.class); - JceMasterKey masterKey2 = mock(JceMasterKey.class); - - List masterKeys = new ArrayList<>(); - masterKeys.add(masterKey1); - masterKeys.add(masterKey2); - - ArgumentCaptor masterKeyRequestCaptor = ArgumentCaptor.forClass(MasterKeyRequest.class); - ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(DataKey.class); - - final String KEY_ID_1 = "KeyId1"; - final String KEY_ID_2 = "KeyId2"; - final String PROVIDER_1 = "Provider1"; - final String PROVIDER_2 = "Provider2"; - - DataKey dataKey1 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_1.getBytes(PROVIDER_ENCODING), masterKey1); - DataKey dataKey2 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_2.getBytes(PROVIDER_ENCODING), masterKey2); - - when(masterKeyProvider.getMasterKeysForEncryption(masterKeyRequestCaptor.capture())).thenReturn(masterKeys); - when(masterKey1.generateDataKey(ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenReturn(dataKey1); - when(masterKey2.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) - .thenReturn(dataKey2); - when(masterKey1.getProviderId()).thenReturn(PROVIDER_1); - when(masterKey1.getKeyId()).thenReturn(KEY_ID_1); - when(masterKey2.getProviderId()).thenReturn(PROVIDER_2); - when(masterKey2.getKeyId()).thenReturn(KEY_ID_2); - when(masterKey1.isEncryptionContextSigned()).thenReturn(true); - when(masterKey2.isEncryptionContextSigned()).thenReturn(false); - - keyring.onEncrypt(encryptionMaterials); - - assertEquals(ENCRYPTION_CONTEXT, masterKeyRequestCaptor.getValue().getEncryptionContext()); - assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getValue().getKey()); - assertEncryptedDataKeyEquals(dataKey1, encryptionMaterials.getEncryptedDataKeys().get(0)); - assertEncryptedDataKeyEquals(dataKey2, encryptionMaterials.getEncryptedDataKeys().get(1)); - assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, GENERATED_DATA_KEY), - encryptionMaterials.getKeyringTrace().getEntries().get(0)); - assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), - encryptionMaterials.getKeyringTrace().getEntries().get(1)); - assertEquals(new KeyringTraceEntry(PROVIDER_2, KEY_ID_2, ENCRYPTED_DATA_KEY), - encryptionMaterials.getKeyringTrace().getEntries().get(2)); - } - - @Test - void testOnEncryptWithPlaintextDataKey() { - - MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); - - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .build(); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - - KmsMasterKey masterKey1 = mock(KmsMasterKey.class); - KmsMasterKey masterKey2 = mock(KmsMasterKey.class); - - List masterKeys = new ArrayList<>(); - masterKeys.add(masterKey1); - masterKeys.add(masterKey2); - - ArgumentCaptor masterKeyRequestCaptor = ArgumentCaptor.forClass(MasterKeyRequest.class); - ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(DataKey.class); - - final String KEY_ID_1 = "KeyId1"; - final String KEY_ID_2 = "KeyId2"; - final String PROVIDER_1 = "Provider1"; - final String PROVIDER_2 = "Provider2"; - - DataKey dataKey1 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_1.getBytes(PROVIDER_ENCODING), masterKey1); - DataKey dataKey2 = new DataKey<>(PLAINTEXT_DATA_KEY, generate(100), KEY_ID_2.getBytes(PROVIDER_ENCODING), masterKey2); - - when(masterKeyProvider.getMasterKeysForEncryption(masterKeyRequestCaptor.capture())).thenReturn(masterKeys); - when(masterKey1.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) - .thenReturn(dataKey1); - when(masterKey2.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), dataKeyCaptor.capture())) - .thenReturn(dataKey2); - when(masterKey1.getProviderId()).thenReturn(PROVIDER_1); - when(masterKey1.getKeyId()).thenReturn(KEY_ID_1); - when(masterKey2.getProviderId()).thenReturn(PROVIDER_2); - when(masterKey2.getKeyId()).thenReturn(KEY_ID_2); - - keyring.onEncrypt(encryptionMaterials); - - assertEquals(ENCRYPTION_CONTEXT, masterKeyRequestCaptor.getValue().getEncryptionContext()); - assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getAllValues().get(0).getKey()); - assertEquals(PLAINTEXT_DATA_KEY, dataKeyCaptor.getAllValues().get(1).getKey()); - assertEncryptedDataKeyEquals(dataKey1, encryptionMaterials.getEncryptedDataKeys().get(0)); - assertEncryptedDataKeyEquals(dataKey2, encryptionMaterials.getEncryptedDataKeys().get(1)); - assertEquals(new KeyringTraceEntry(PROVIDER_1, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), - encryptionMaterials.getKeyringTrace().getEntries().get(0)); - assertEquals(new KeyringTraceEntry(PROVIDER_2, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT), - encryptionMaterials.getKeyringTrace().getEntries().get(1)); - } - - @SuppressWarnings("unchecked") - @Test - void testOnEncryptWithNonKmsOrJceMasterKeyProvider() { - - MasterKeyProvider masterKeyProvider = mock(MasterKeyProvider.class); - - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .build(); - - Keyring keyring = new MasterKeyProviderKeyring(masterKeyProvider); - - MasterKey masterKey = mock(MasterKey.class); - - final String KEY_ID = "KeyId1"; - final String PROVIDER = "Provider1"; - - DataKey dataKey = new DataKey(PLAINTEXT_DATA_KEY, generate(100), KEY_ID.getBytes(PROVIDER_ENCODING), masterKey); - - when(masterKeyProvider.getMasterKeysForEncryption(isA(MasterKeyRequest.class))).thenReturn(singletonList(masterKey)); - when(masterKey.encryptDataKey(eq(ALGORITHM_SUITE), eq(ENCRYPTION_CONTEXT), isA(DataKey.class))) - .thenReturn(dataKey); - when(masterKey.getProviderId()).thenReturn(PROVIDER); - when(masterKey.getKeyId()).thenReturn(KEY_ID); - - keyring.onEncrypt(encryptionMaterials); - - assertEncryptedDataKeyEquals(dataKey, encryptionMaterials.getEncryptedDataKeys().get(0)); - assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, ENCRYPTED_DATA_KEY), - encryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testOnEncryptWithNoMasterKeys() { - MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); - - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .build(); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - - when(masterKeyProvider.getMasterKeysForEncryption(isA(MasterKeyRequest.class))).thenReturn(emptyList()); - - assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); - } - - @Test - void testOnDecryptWithPlaintextDataKey() { - MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .build(); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - keyring.onDecrypt(decryptionMaterials, emptyList()); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); - } - - @Test - void testOnDecrypt() { - MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); - KmsMasterKey masterKey = mock(KmsMasterKey.class); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .build(); - - final String KEY_ID = "KeyId1"; - final String PROVIDER = "Provider1"; - - EncryptedDataKey encryptedDataKey = new KeyBlob(PROVIDER, - KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - - when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) - .thenReturn(new DataKey<>(PLAINTEXT_DATA_KEY, encryptedDataKey.getEncryptedDataKey(), - encryptedDataKey.getProviderInformation(), masterKey)); - when(masterKey.getProviderId()).thenReturn(PROVIDER); - when(masterKey.getKeyId()).thenReturn(KEY_ID); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); - assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, DECRYPTED_DATA_KEY, VERIFIED_ENCRYPTION_CONTEXT), - decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - @Test - void testOnDecryptMasterKeyCannotUnwrapDataKeyException() { - MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .build(); - - EncryptedDataKey encryptedDataKey = mock(EncryptedDataKey.class); - - when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) - .thenThrow(new CannotUnwrapDataKeyException()); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); - - assertFalse(decryptionMaterials.hasPlaintextDataKey()); - } - - @Test - void testOnDecryptMasterKeyOtherException() { - MasterKeyProvider masterKeyProvider = mock(KmsMasterKeyProvider.class); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .build(); - - EncryptedDataKey encryptedDataKey = mock(EncryptedDataKey.class); - - when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) - .thenThrow(new AwsCryptoException()); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - assertThrows(AwsCryptoException.class, () -> keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey))); - } - - @Test - void testOnDecryptNonVerifiedEncryptionContext() { - MasterKeyProvider masterKeyProvider = mock(JceMasterKey.class); - JceMasterKey masterKey = mock(JceMasterKey.class); - - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .build(); - - final String KEY_ID = "KeyId1"; - final String PROVIDER = "Provider1"; - - EncryptedDataKey encryptedDataKey = new KeyBlob(PROVIDER, - KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - - when(masterKeyProvider.decryptDataKey(ALGORITHM_SUITE, singletonList(encryptedDataKey), ENCRYPTION_CONTEXT)) - .thenReturn(new DataKey<>(PLAINTEXT_DATA_KEY, encryptedDataKey.getEncryptedDataKey(), - encryptedDataKey.getProviderInformation(), masterKey)); - when(masterKey.getProviderId()).thenReturn(PROVIDER); - when(masterKey.getKeyId()).thenReturn(KEY_ID); - when(masterKey.isEncryptionContextSigned()).thenReturn(false); - - Keyring keyring = StandardKeyrings.masterKeyProvider(masterKeyProvider); - keyring.onDecrypt(decryptionMaterials, singletonList(encryptedDataKey)); - - assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getPlaintextDataKey()); - assertEquals(new KeyringTraceEntry(PROVIDER, KEY_ID, DECRYPTED_DATA_KEY), - decryptionMaterials.getKeyringTrace().getEntries().get(0)); - } - - private static void assertEncryptedDataKeyEquals(EncryptedDataKey expected, EncryptedDataKey actual) { - assertEquals(expected.getProviderId(), actual.getProviderId()); - assertArrayEquals(expected.getProviderInformation(), actual.getProviderInformation()); - assertArrayEquals(expected.getEncryptedDataKey(), actual.getEncryptedDataKey()); - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java index 75e43a676..a9d414754 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java @@ -15,6 +15,8 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -22,6 +24,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import javax.crypto.SecretKey; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -44,6 +47,7 @@ class MultiKeyringTest { @Mock EncryptionMaterials encryptionMaterials; @Mock DecryptionMaterials decryptionMaterials; @Mock List encryptedDataKeys; + @Mock SecretKey cleartextDataKey; final List childrenKeyrings = new ArrayList<>(); @BeforeEach @@ -63,7 +67,7 @@ void testConstructor() { @Test void testOnEncryptWithGenerator() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); - when(encryptionMaterials.hasPlaintextDataKey()).thenReturn(true); + when(encryptionMaterials.hasCleartextDataKey()).thenReturn(true); keyring.onEncrypt(encryptionMaterials); @@ -75,7 +79,7 @@ void testOnEncryptWithGenerator() { @Test void testOnEncryptWithoutGenerator() { MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); - when(encryptionMaterials.hasPlaintextDataKey()).thenReturn(true); + when(encryptionMaterials.hasCleartextDataKey()).thenReturn(true); keyring.onEncrypt(encryptionMaterials); @@ -87,7 +91,7 @@ void testOnEncryptWithoutGenerator() { @Test void testOnEncryptNoPlaintextDataKey() { MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); - when(encryptionMaterials.hasPlaintextDataKey()).thenReturn(false); + when(encryptionMaterials.hasCleartextDataKey()).thenReturn(false); assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); } @@ -96,7 +100,7 @@ void testOnEncryptNoPlaintextDataKey() { void testOnDecryptWithPlaintextDataKey() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); - when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(true); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(true); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); verifyNoInteractions(generatorKeyring, keyring1, keyring2); @@ -106,7 +110,7 @@ void testOnDecryptWithPlaintextDataKey() { void testOnDecryptWithGenerator() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); - when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); InOrder inOrder = inOrder(generatorKeyring, keyring1); @@ -119,7 +123,7 @@ void testOnDecryptWithGenerator() { void testOnDecryptWithoutGenerator() { MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); - when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); InOrder inOrder = inOrder(keyring1, keyring2); @@ -132,7 +136,7 @@ void testOnDecryptWithoutGenerator() { void testOnDecryptFailureThenSuccess() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); - when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false).thenReturn(true); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false).thenReturn(true); doThrow(new IllegalStateException()).when(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); @@ -147,7 +151,7 @@ void testOnDecryptFailureThenSuccess() { void testOnDecryptFailure() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); - when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false); doThrow(new AwsCryptoException()).when(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); doThrow(new IllegalStateException()).when(keyring1).onDecrypt(decryptionMaterials, encryptedDataKeys); doThrow(new IllegalArgumentException()).when(keyring2).onDecrypt(decryptionMaterials, encryptedDataKeys); @@ -172,7 +176,7 @@ void testOnDecryptFailure() { void testOnDecryptNoFailuresNoPlaintextDataKeys() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); - when(decryptionMaterials.hasPlaintextDataKey()).thenReturn(false, false, false, false); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false, false, false, false); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); InOrder inOrder = inOrder(generatorKeyring, keyring1, keyring2); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java index 4183e9a06..556e63132 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java @@ -15,6 +15,8 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.Test; @@ -53,10 +55,11 @@ void testValidToDecrypt() { @Test void testEncryptDecryptExistingDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) - .plaintextDataKey(DATA_KEY) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setCleartextDataKey(DATA_KEY) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); @@ -75,14 +78,15 @@ void testEncryptDecryptExistingDataKey() { assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); - assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); assertEquals(2, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); @@ -92,15 +96,16 @@ void testEncryptDecryptExistingDataKey() { @Test void testEncryptDecryptGenerateDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); - assertNotNull(encryptionMaterials.getPlaintextDataKey()); - assertEquals(encryptionMaterials.getPlaintextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); + assertNotNull(encryptionMaterials.getCleartextDataKey()); + assertEquals(encryptionMaterials.getCleartextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); final EncryptedDataKey actualEncryptedDataKey = encryptionMaterials.getEncryptedDataKeys().get(0); @@ -115,14 +120,15 @@ void testEncryptDecryptGenerateDataKey() { assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().contains(KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); - assertEquals(encryptionMaterials.getPlaintextDataKey(), decryptionMaterials.getPlaintextDataKey()); + assertEquals(encryptionMaterials.getCleartextDataKey(), decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); assertEquals(2, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java index 3fe1fb416..60482d403 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java @@ -16,6 +16,8 @@ import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.internal.JceKeyCipher; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -71,10 +73,11 @@ boolean validToDecrypt(EncryptedDataKey encryptedDataKey) { @Test void testEncryptDecryptExistingDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) - .plaintextDataKey(DATA_KEY) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setCleartextDataKey(DATA_KEY) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); @@ -84,32 +87,34 @@ void testEncryptDecryptExistingDataKey() { assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().size()); assertEquals(ENCRYPTED_DATA_KEY_TRACE, encryptionMaterials.getKeyringTrace().getEntries().get(0)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); - assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @Test void testEncryptNullDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(byte[].class); when(jceKeyCipher.encryptKey(dataKeyCaptor.capture(), eq(KEYNAME), eq(KEYNAMESPACE), eq(ENCRYPTION_CONTEXT))).thenReturn(ENCRYPTED_DATA_KEY); keyring.onEncrypt(encryptionMaterials); - assertEquals(encryptionMaterials.getPlaintextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); - assertArrayEquals(encryptionMaterials.getPlaintextDataKey().getEncoded(), dataKeyCaptor.getValue()); + assertEquals(encryptionMaterials.getCleartextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); + assertArrayEquals(encryptionMaterials.getCleartextDataKey().getEncoded(), dataKeyCaptor.getValue()); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); - assertNotNull(encryptionMaterials.getPlaintextDataKey()); + assertTrue(encryptionMaterials.hasCleartextDataKey()); assertEncryptedDataKeyEquals(ENCRYPTED_DATA_KEY, encryptionMaterials.getEncryptedDataKeys().get(0)); assertEquals(2, encryptionMaterials.getKeyringTrace().getEntries().size()); assertEquals(GENERATED_DATA_KEY_TRACE, encryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -118,50 +123,54 @@ void testEncryptNullDataKey() { @Test void testDecryptAlreadyDecryptedDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .plaintextDataKey(DATA_KEY) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setCleartextDataKey(DATA_KEY) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); - assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test void testDecryptNoValidDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.singletonList(INVALID_DATA_KEY)); - assertFalse(decryptionMaterials.hasPlaintextDataKey()); + assertFalse(decryptionMaterials.hasCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test void testDecryptNoDataKey() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); - assertFalse(decryptionMaterials.hasPlaintextDataKey()); + assertFalse(decryptionMaterials.hasCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); } @Test void testDecryptMultipleKeysOneInvalid() { - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); final List edks = new ArrayList<>(); @@ -170,7 +179,7 @@ void testDecryptMultipleKeysOneInvalid() { keyring.onDecrypt(decryptionMaterials, edks); - assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @@ -178,9 +187,10 @@ void testDecryptMultipleKeysOneInvalid() { void testDecryptMultipleKeysOneException() throws GeneralSecurityException { final EncryptedDataKey BAD_DATA_KEY = new KeyBlob("exceptionProvider", new byte[]{1, 2, 3}, new byte[]{4, 5, 6}); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); when(jceKeyCipher.decryptKey(BAD_DATA_KEY, KEYNAME, ENCRYPTION_CONTEXT)) @@ -192,7 +202,7 @@ void testDecryptMultipleKeysOneException() throws GeneralSecurityException { keyring.onDecrypt(decryptionMaterials, edks); - assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java index 6e43d5d37..1f3f1fe63 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java @@ -14,6 +14,8 @@ package com.amazonaws.encryptionsdk.keyrings; import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.BeforeAll; @@ -31,7 +33,6 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; class RawRsaKeyringTest { @@ -61,10 +62,11 @@ void testValidToDecrypt() { @Test void testEncryptDecryptExistingDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) - .plaintextDataKey(DATA_KEY) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setCleartextDataKey(DATA_KEY) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); @@ -81,14 +83,15 @@ void testEncryptDecryptExistingDataKey() { assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); - assertEquals(DATA_KEY, decryptionMaterials.getPlaintextDataKey()); + assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); assertEquals(1, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); @@ -97,15 +100,16 @@ void testEncryptDecryptExistingDataKey() { @Test void testEncryptDecryptGenerateDataKey() { - EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder(ALGORITHM) - .keyringTrace(new KeyringTrace()) - .encryptionContext(ENCRYPTION_CONTEXT) + EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setKeyringTrace(new KeyringTrace()) + .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); keyring.onEncrypt(encryptionMaterials); - assertNotNull(encryptionMaterials.getPlaintextDataKey()); - assertEquals(encryptionMaterials.getPlaintextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); + assertTrue(encryptionMaterials.hasCleartextDataKey()); + assertEquals(encryptionMaterials.getCleartextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); final EncryptedDataKey actualEncryptedDataKey = encryptionMaterials.getEncryptedDataKeys().get(0); @@ -118,14 +122,15 @@ void testEncryptDecryptGenerateDataKey() { assertEquals(1, encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().size()); assertTrue(encryptionMaterials.getKeyringTrace().getEntries().get(1).getFlags().contains(KeyringTraceFlag.ENCRYPTED_DATA_KEY)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder(ALGORITHM) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(new KeyringTrace()) + DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); - assertEquals(encryptionMaterials.getPlaintextDataKey(), decryptionMaterials.getPlaintextDataKey()); + assertEquals(encryptionMaterials.getCleartextDataKey(), decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); assertEquals(KEYNAMESPACE, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyNamespace()); assertEquals(1, decryptionMaterials.getKeyringTrace().getEntries().get(0).getFlags().size()); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java similarity index 60% rename from src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java rename to src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java index 68687d4ad..444908478 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/DecryptionMaterialsTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at @@ -11,10 +11,13 @@ * specific language governing permissions and limitations under the License. */ -package com.amazonaws.encryptionsdk.keyrings; +package com.amazonaws.encryptionsdk.model; import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceEntry; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -28,8 +31,11 @@ import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class DecryptionMaterialsTest { @@ -49,68 +55,47 @@ static void setup() throws Exception { VERIFICATION_KEY = keyPair.getPublic(); } - @Test - void testBuilderNullCryptoAlgorithm() { - assertThrows(NullPointerException.class, () -> DecryptionMaterials.newBuilder(null).build()); - } - @Test void testBuilder() { - DecryptionMaterials result = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(KEYRING_TRACE) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .verificationKey(VERIFICATION_KEY) + DecryptionMaterials result = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(KEYRING_TRACE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setTrailingSignatureKey(VERIFICATION_KEY) .build(); - assertEquals(ALGORITHM_SUITE, result.getAlgorithmSuite()); + assertEquals(ALGORITHM_SUITE, result.getAlgorithm()); assertEquals(ENCRYPTION_CONTEXT, result.getEncryptionContext()); assertEquals(KEYRING_TRACE, result.getKeyringTrace()); - assertEquals(PLAINTEXT_DATA_KEY, result.getPlaintextDataKey()); - assertEquals(VERIFICATION_KEY, result.getVerificationKey()); + assertEquals(PLAINTEXT_DATA_KEY, result.getCleartextDataKey()); + assertEquals(VERIFICATION_KEY, result.getTrailingSignatureKey()); } @Test void testInvalidPlaintextDataKey() { SecretKey wrongLength = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength() + 1), ALGORITHM_SUITE.getDataKeyAlgo()); - assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .plaintextDataKey(wrongLength) - .verificationKey(VERIFICATION_KEY) - .build()); - SecretKey wrongAlgorithm = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), "InvalidAlgorithm"); - assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .plaintextDataKey(wrongAlgorithm) - .verificationKey(VERIFICATION_KEY) - .build()); - DecryptionMaterials materials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .verificationKey(VERIFICATION_KEY) + + DecryptionMaterials materials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setTrailingSignatureKey(VERIFICATION_KEY) .build(); assertThrows(IllegalArgumentException.class, () -> materials - .setPlaintextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); + .setCleartextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); assertThrows(IllegalArgumentException.class, () -> materials - .setPlaintextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); - } - - @Test - void testInvalidVerificationKey() { - assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .verificationKey(null) - .build()); - assertThrows(IllegalArgumentException.class, () -> DecryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) - .verificationKey(VERIFICATION_KEY) - .build()); - + .setCleartextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); } @Test void testToBuilder() { - DecryptionMaterials expected = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(KEYRING_TRACE) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .verificationKey(VERIFICATION_KEY) + DecryptionMaterials expected = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(KEYRING_TRACE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setTrailingSignatureKey(VERIFICATION_KEY) .build(); DecryptionMaterials actual = expected.toBuilder().build(); @@ -121,28 +106,34 @@ void testToBuilder() { @Test void testSetPlaintextDataKey() { - DecryptionMaterials materials = DecryptionMaterials.newBuilder(ALGORITHM_SUITE) - .verificationKey(VERIFICATION_KEY) + DecryptionMaterials materials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setTrailingSignatureKey(VERIFICATION_KEY) .build(); - assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(null, KEYRING_TRACE_ENTRY)); - assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, null)); + assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, null)); - materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); - assertEquals(PLAINTEXT_DATA_KEY, materials.getPlaintextDataKey()); + materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(PLAINTEXT_DATA_KEY, materials.getCleartextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, materials.getDataKey().getKey()); assertEquals(1, materials.getKeyringTrace().getEntries().size()); assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); - assertThrows(IllegalStateException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); + assertThrows(IllegalStateException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); } @Test void testGetOptionalProperties() { - DecryptionMaterials materials = DecryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) - .build(); - - assertThrows(IllegalStateException.class, materials::getPlaintextDataKey); - assertThrows(IllegalStateException.class, materials::getVerificationKey); + DecryptionMaterials materials = DecryptionMaterials.newBuilder() + .build(); + + assertNull(materials.getAlgorithm()); + assertNull(materials.getCleartextDataKey()); + assertFalse(materials.hasCleartextDataKey()); + assertNull(materials.getTrailingSignatureKey()); + assertTrue(materials.getEncryptionContext().isEmpty()); + assertTrue(materials.getKeyringTrace().getEntries().isEmpty()); } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java similarity index 63% rename from src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java rename to src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java index d1c207dbd..54c89ac59 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/EncryptionMaterialsTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at @@ -11,11 +11,13 @@ * specific language governing permissions and limitations under the License. */ -package com.amazonaws.encryptionsdk.keyrings; +package com.amazonaws.encryptionsdk.model; import com.amazonaws.encryptionsdk.CryptoAlgorithm; -import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceEntry; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -32,8 +34,11 @@ import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(MockitoExtension.class) class EncryptionMaterialsTest { @@ -44,7 +49,7 @@ class EncryptionMaterialsTest { private static final KeyringTraceEntry KEYRING_TRACE_ENTRY = new KeyringTraceEntry("Namespace", "Name", KeyringTraceFlag.ENCRYPTED_DATA_KEY); private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); @Mock - private static EncryptedDataKey ENCRYPTED_DATA_KEY; + private static KeyBlob ENCRYPTED_DATA_KEY; private static PrivateKey SIGNING_KEY; @BeforeAll @@ -56,72 +61,50 @@ static void setup() throws Exception { SIGNING_KEY = keyPair.getPrivate(); } - @Test - void testBuilderNullCryptoAlgorithm() { - assertThrows(NullPointerException.class, () -> EncryptionMaterials.newBuilder(null).build()); - } - @Test void testBuilder() { - EncryptionMaterials result = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(KEYRING_TRACE) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .encryptedDataKeys(Collections.singletonList(ENCRYPTED_DATA_KEY)) - .signingKey(SIGNING_KEY) + EncryptionMaterials result = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(KEYRING_TRACE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptedDataKeys(Collections.singletonList(ENCRYPTED_DATA_KEY)) + .setTrailingSignatureKey(SIGNING_KEY) .build(); - assertEquals(ALGORITHM_SUITE, result.getAlgorithmSuite()); + assertEquals(ALGORITHM_SUITE, result.getAlgorithm()); assertEquals(ENCRYPTION_CONTEXT, result.getEncryptionContext()); assertEquals(KEYRING_TRACE, result.getKeyringTrace()); - assertEquals(PLAINTEXT_DATA_KEY, result.getPlaintextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, result.getCleartextDataKey()); assertEquals(1, result.getEncryptedDataKeys().size()); assertEquals(ENCRYPTED_DATA_KEY, result.getEncryptedDataKeys().get(0)); - assertEquals(SIGNING_KEY, result.getSigningKey()); + assertEquals(SIGNING_KEY, result.getTrailingSignatureKey()); } @Test void testInvalidPlaintextDataKey() { SecretKey wrongLength = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength() + 1), ALGORITHM_SUITE.getDataKeyAlgo()); - assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .plaintextDataKey(wrongLength) - .signingKey(SIGNING_KEY) - .build()); - SecretKey wrongAlgorithm = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), "InvalidAlgorithm"); - assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .plaintextDataKey(wrongAlgorithm) - .signingKey(SIGNING_KEY) - .build()); - EncryptionMaterials materials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .signingKey(SIGNING_KEY) + EncryptionMaterials materials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setTrailingSignatureKey(SIGNING_KEY) .build(); assertThrows(IllegalArgumentException.class, () -> materials - .setPlaintextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); + .setCleartextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); assertThrows(IllegalArgumentException.class, () -> materials - .setPlaintextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); - } - - @Test - void testInvalidSigningKey() { - assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .signingKey(null) - .build()); - assertThrows(IllegalArgumentException.class, () -> EncryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) - .signingKey(SIGNING_KEY) - .build()); - + .setCleartextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); } @Test void testToBuilder() { - EncryptionMaterials expected = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .encryptionContext(ENCRYPTION_CONTEXT) - .keyringTrace(KEYRING_TRACE) - .plaintextDataKey(PLAINTEXT_DATA_KEY) - .encryptedDataKeys(Collections.singletonList(ENCRYPTED_DATA_KEY)) - .signingKey(SIGNING_KEY) + EncryptionMaterials expected = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .setKeyringTrace(KEYRING_TRACE) + .setCleartextDataKey(PLAINTEXT_DATA_KEY) + .setEncryptedDataKeys(Collections.singletonList(ENCRYPTED_DATA_KEY)) + .setTrailingSignatureKey(SIGNING_KEY) .build(); EncryptionMaterials actual = expected.toBuilder().build(); @@ -132,8 +115,9 @@ void testToBuilder() { @Test void testAddEncryptedDataKey() { - EncryptionMaterials materials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .signingKey(SIGNING_KEY) + EncryptionMaterials materials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setTrailingSignatureKey(SIGNING_KEY) .build(); assertThrows(NullPointerException.class, () -> materials.addEncryptedDataKey(null, KEYRING_TRACE_ENTRY)); @@ -148,28 +132,34 @@ void testAddEncryptedDataKey() { @Test void testSetPlaintextDataKey() { - EncryptionMaterials materials = EncryptionMaterials.newBuilder(ALGORITHM_SUITE) - .signingKey(SIGNING_KEY) + EncryptionMaterials materials = EncryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setTrailingSignatureKey(SIGNING_KEY) .build(); - assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(null, KEYRING_TRACE_ENTRY)); - assertThrows(NullPointerException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, null)); + assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, null)); - materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); - assertEquals(PLAINTEXT_DATA_KEY, materials.getPlaintextDataKey()); + materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(PLAINTEXT_DATA_KEY, materials.getCleartextDataKey()); assertEquals(1, materials.getKeyringTrace().getEntries().size()); assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); - assertThrows(IllegalStateException.class, () -> materials.setPlaintextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); + assertThrows(IllegalStateException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); } @Test void testGetOptionalProperties() { - EncryptionMaterials materials = EncryptionMaterials.newBuilder(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256) - .build(); - - assertThrows(IllegalStateException.class, materials::getPlaintextDataKey); - assertThrows(IllegalStateException.class, materials::getSigningKey); + EncryptionMaterials materials = EncryptionMaterials.newBuilder() + .build(); + + assertNull(materials.getAlgorithm()); + assertNull(materials.getCleartextDataKey()); + assertFalse(materials.hasCleartextDataKey()); + assertTrue(materials.getEncryptedDataKeys().isEmpty()); + assertNull(materials.getTrailingSignatureKey()); + assertTrue(materials.getKeyringTrace().getEntries().isEmpty()); + assertTrue(materials.getEncryptionContext().isEmpty()); } } From 8bdb1d4ab3fa26de618fda5f86e826b5b5bf9daa Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Tue, 11 Feb 2020 18:17:37 -0800 Subject: [PATCH 08/18] Incorporate Keyrings into AwsCrypto and deprecate MasterKeyProviders. (#151) * Incorporate Keyrings into AwsCrypto and deprecate MasterKeyProviders. * Update example code to use keyrings * Using try-with-resources for AwsCrypto streams * Splitting MKP and keyring unit tests * Making decryptData with ParsedCiphertext public * Mark KeyStoreProvider as deprecated * Reword some comments on the Basic Encryption example * Add test for compability of Keyrings with MasterKeyProviders * Create individual request types for each AwsCrypto method * Make EncryptionMaterials, DecryptionMaterials and KeyringTrace immutable * Rename KmsKeying and related classes to AwsKmsKeyring * Create builders for the standard keyrings * Create AwsKmsCmkId type to represent AWS KMS Key Ids * Add factory methods to Keyring builders * Add comment on not making a defensive copy of plaintext/ciphertext * Limit ability to create discovery AWS KMS Keyrings to explicit creation * Add withKeyring to CachingCMM builder * Fix DecryptRequestTest * Fix Junit 4 assertions in JUnit5 tests * Renaming StaticKeyring to TestKeyring * Adding convenience methods the create builders internally * Updating wording and adding more Deprecated annotations * Enable AwsKms Client Caching by default to match KmsMasterKeyProvider * Making tests opt-out instead of opt-in and update TestVectorRunner (#154) * Making tests opt-out instead of opt-in and update TestVectorRunner JUnit5 doesn't support test suites yet (see https://github.com/junit-team/junit5/issues/744) and the existing test suites do not support the new JUnit5 tests that are being used for keyrings. This change removes the test suites, and configures Maven to include all tests except those marked with certain JUnit tags. Additionally, this change updates the TestVectorRunner to also test Keyrings and removes the redundant XCompat tests. * Client caching is now enabled by default in AwsKmsClientSupplier * Rename slow tag to ad_hoc and fix TestVectorRunner * Renaming StandardKeyring builder methods and other minors changes * Fixing test * Updating tests to use assertThrows * Additional example code for Keyrings (#155) * Additional example code for Keyrings * Updating wording * Remove AWS from AWS KMS keyring and make keyring lowercase --- pom.xml | 55 ++- .../examples/BasicEncryptionExample.java | 67 +-- .../examples/EscrowedEncryptExample.java | 193 ++++---- .../crypto/examples/FileStreamingExample.java | 144 ++++-- .../crypto/examples/RawAesKeyringExample.java | 100 ++++ .../examples/RawRsaKeyringDecryptExample.java | 56 +++ .../examples/RawRsaKeyringEncryptExample.java | 63 +++ .../LambdaDecryptAndWriteExample.java | 96 ++++ .../MultiRegionRecordPusherExample.java | 125 +++++ .../amazonaws/encryptionsdk/AwsCrypto.java | 445 +++++++++++++++--- .../encryptionsdk/AwsCryptoInputStream.java | 146 ++++++ .../encryptionsdk/AwsCryptoOutputStream.java | 153 ++++++ .../encryptionsdk/AwsCryptoRequest.java | 78 +++ .../encryptionsdk/AwsCryptoResult.java | 100 ++++ .../CreateDecryptingInputStreamRequest.java | 79 ++++ .../CreateDecryptingOutputStreamRequest.java | 79 ++++ .../CreateEncryptingInputStreamRequest.java | 107 +++++ .../CreateEncryptingOutputStreamRequest.java | 107 +++++ .../encryptionsdk/CryptoInputStream.java | 17 + .../encryptionsdk/CryptoOutputStream.java | 17 +- .../amazonaws/encryptionsdk/CryptoResult.java | 3 + .../com/amazonaws/encryptionsdk/DataKey.java | 3 + .../encryptionsdk/DecryptRequest.java | 90 ++++ .../DefaultCryptoMaterialsManager.java | 195 +++++--- .../encryptionsdk/EncryptRequest.java | 94 ++++ .../EstimateCiphertextSizeRequest.java | 89 ++++ .../amazonaws/encryptionsdk/MasterKey.java | 4 + .../encryptionsdk/MasterKeyProvider.java | 4 + .../encryptionsdk/MasterKeyRequest.java | 4 + .../CachingCryptoMaterialsManager.java | 37 +- .../exception/UnsupportedRegionException.java | 2 +- .../encryptionsdk/internal/Constants.java | 5 + .../internal/DecryptionHandler.java | 27 ++ .../internal/EncryptionHandler.java | 21 + .../internal/LazyMessageCryptoHandler.java | 6 + .../internal/MessageCryptoHandler.java | 13 + .../encryptionsdk/jce/JceMasterKey.java | 4 + .../encryptionsdk/jce/KeyStoreProvider.java | 4 + .../{KmsKeyring.java => AwsKmsKeyring.java} | 99 ++-- .../keyrings/AwsKmsKeyringBuilder.java | 121 +++++ .../encryptionsdk/keyrings/Keyring.java | 10 +- .../encryptionsdk/keyrings/KeyringTrace.java | 48 +- .../encryptionsdk/keyrings/MultiKeyring.java | 24 +- .../encryptionsdk/keyrings/RawAesKeyring.java | 2 +- .../keyrings/RawAesKeyringBuilder.java | 77 +++ .../encryptionsdk/keyrings/RawKeyring.java | 25 +- .../encryptionsdk/keyrings/RawRsaKeyring.java | 2 +- .../keyrings/RawRsaKeyringBuilder.java | 102 ++++ .../keyrings/StandardKeyrings.java | 85 ++-- ...upplier.java => AwsKmsClientSupplier.java} | 61 ++- .../encryptionsdk/kms/AwsKmsCmkId.java | 138 ++++++ ...o.java => AwsKmsDataKeyEncryptionDao.java} | 28 +- .../kms/DataKeyEncryptionDao.java | 8 +- .../encryptionsdk/kms/KmsMasterKey.java | 15 +- .../kms/KmsMasterKeyProvider.java | 4 + .../amazonaws/encryptionsdk/kms/KmsUtils.java | 82 ---- .../model/DecryptionMaterials.java | 19 +- .../model/EncryptionMaterials.java | 40 +- .../multi/MultipleProviderFactory.java | 4 + .../examples/BasicEncryptionExampleTest.java | 12 +- .../examples/EscrowedEncryptExampleTest.java | 31 ++ .../examples/FileStreamingExampleTest.java | 44 ++ .../examples/RawAesKeyringExampleTest.java | 24 + .../RawRsaKeyringDecryptExampleTest.java | 37 ++ .../RawRsaKeyringEncryptExampleTest.java | 54 +++ .../encryptionsdk/AllTestsSuite.java | 63 --- .../encryptionsdk/AwsCryptoTest.java | 247 ++++++++-- .../encryptionsdk/CryptoInputStreamTest.java | 3 +- .../encryptionsdk/CryptoOutputStreamTest.java | 6 +- .../encryptionsdk/DecryptRequestTest.java | 66 +++ .../DefaultCryptoMaterialsManagerTest.java | 170 ++++++- .../encryptionsdk/EncryptRequestTest.java | 73 +++ .../encryptionsdk/FastTestsOnlySuite.java | 89 ---- .../encryptionsdk/IntegrationTestSuite.java | 15 - .../encryptionsdk/SlowTestCategory.java | 14 - .../amazonaws/encryptionsdk/TestUtils.java | 14 + .../encryptionsdk/TestVectorRunner.java | 177 +++---- .../encryptionsdk/XCompatDecryptTest.java | 225 --------- .../internal/DecryptionHandlerTest.java | 16 +- .../FrameEncryptionHandlerVeryLongTest.java | 15 +- .../encryptionsdk/internal/TestKeyring.java | 189 ++++++++ ...eyringTest.java => AwsKmsKeyringTest.java} | 129 ++--- .../keyrings/KeyringTraceTest.java | 12 +- .../MasterKeyProviderCompatibilityTest.java | 139 ++++++ .../keyrings/MultiKeyringTest.java | 15 +- .../keyrings/RawAesKeyringTest.java | 12 +- .../keyrings/RawKeyringTest.java | 18 +- .../keyrings/RawRsaKeyringTest.java | 12 +- ...est.java => AwsKmsClientSupplierTest.java} | 32 +- .../encryptionsdk/kms/AwsKmsCmkIdTest.java | 62 +++ ...va => AwsKmsDataKeyEncryptionDaoTest.java} | 50 +- .../KMSProviderBuilderIntegrationTests.java | 73 +-- .../encryptionsdk/kms/KmsMasterKeyTest.java | 9 +- .../encryptionsdk/kms/KmsUtilsTest.java | 55 --- .../kms/XCompatKmsDecryptTest.java | 115 ----- .../model/DecryptionMaterialsTest.java | 26 +- .../model/EncryptionMaterialsTest.java | 38 +- 97 files changed, 4678 insertions(+), 1529 deletions(-) create mode 100644 src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/AwsCryptoInputStream.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/AwsCryptoOutputStream.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/AwsCryptoRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/AwsCryptoResult.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingInputStreamRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingOutputStreamRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingInputStreamRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingOutputStreamRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/DecryptRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/EncryptRequest.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/EstimateCiphertextSizeRequest.java rename src/main/java/com/amazonaws/encryptionsdk/keyrings/{KmsKeyring.java => AwsKmsKeyring.java} (60%) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringBuilder.java create mode 100644 src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java rename src/main/java/com/amazonaws/encryptionsdk/kms/{KmsClientSupplier.java => AwsKmsClientSupplier.java} (75%) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java rename src/main/java/com/amazonaws/encryptionsdk/kms/{KmsDataKeyEncryptionDao.java => AwsKmsDataKeyEncryptionDao.java} (84%) delete mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/DecryptRequestTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/EncryptRequestTest.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/FastTestsOnlySuite.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/IntegrationTestSuite.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/SlowTestCategory.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/XCompatDecryptTest.java create mode 100644 src/test/java/com/amazonaws/encryptionsdk/internal/TestKeyring.java rename src/test/java/com/amazonaws/encryptionsdk/keyrings/{KmsKeyringTest.java => AwsKmsKeyringTest.java} (72%) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java rename src/test/java/com/amazonaws/encryptionsdk/kms/{KmsClientSupplierTest.java => AwsKmsClientSupplierTest.java} (81%) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkIdTest.java rename src/test/java/com/amazonaws/encryptionsdk/kms/{KmsDataKeyEncryptionDaoTest.java => AwsKmsDataKeyEncryptionDaoTest.java} (86%) delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java delete mode 100644 src/test/java/com/amazonaws/encryptionsdk/kms/XCompatKmsDecryptTest.java diff --git a/pom.xml b/pom.xml index a8774878b..22b369c1e 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ org.junit.jupiter - junit-jupiter-engine + junit-jupiter 5.5.2 test @@ -80,6 +80,19 @@ test + + com.amazonaws + aws-lambda-java-core + 1.2.0 + test + + + + com.amazonaws + aws-lambda-java-events + 2.2.7 + test + com.google.code.findbugs @@ -197,7 +210,7 @@ - full-test-suite + test-suite true @@ -208,20 +221,17 @@ maven-surefire-plugin 2.22.0 - - **/AllTestsSuite.java - + ad_hoc + fast-tests-only - - false - @@ -229,9 +239,32 @@ maven-surefire-plugin 2.22.0 - - **/FastTestsOnlySuite.java - + ad_hoc, integration + + true + + + 120 + + + + + + + + + integration + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + integration diff --git a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java index 748cea536..ce1dcca3c 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java @@ -19,13 +19,16 @@ import java.util.Map; import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.CryptoResult; -import com.amazonaws.encryptionsdk.kms.KmsMasterKey; -import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; /** *

- * Encrypts and then decrypts data using an AWS KMS customer master key. + * Encrypts and then decrypts data using an AWS Key Management Service (AWS KMS) customer master key. * *

* Arguments: @@ -39,48 +42,54 @@ public class BasicEncryptionExample { private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); public static void main(final String[] args) { - final String keyArn = args[0]; - - encryptAndDecrypt(keyArn); + encryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); } - static void encryptAndDecrypt(final String keyArn) { + static void encryptAndDecrypt(final AwsKmsCmkId keyArn) { // 1. Instantiate the SDK final AwsCrypto crypto = new AwsCrypto(); - // 2. Instantiate a KMS master key provider - final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder().withKeysForEncryption(keyArn).build(); + // 2. Instantiate a KMS keyring. Supply the key ARN for the generator key + // that generates a data key. While using a key ARN is a best practice, + // for encryption operations you can also use an alias name or alias ARN. + final Keyring keyring = StandardKeyrings.awsKms(keyArn); // 3. Create an encryption context // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - // 4. Encrypt the data - final CryptoResult encryptResult = crypto.encryptData(masterKeyProvider, EXAMPLE_DATA, encryptionContext); + // 4. Encrypt the data with the keyring and encryption context + final AwsCryptoResult encryptResult = crypto.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(EXAMPLE_DATA).build()); final byte[] ciphertext = encryptResult.getResult(); - // 5. Decrypt the data - final CryptoResult decryptResult = crypto.decryptData(masterKeyProvider, ciphertext); + // 5. Decrypt the data. You can use the same keyring to encrypt and decrypt, but for decryption + // the key IDs must be in the key ARN format. + final AwsCryptoResult decryptResult = crypto.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); - // 6. Before verifying the plaintext, verify that the customer master key that - // was used in the encryption operation was the one supplied to the master key provider. - if (!decryptResult.getMasterKeyIds().get(0).equals(keyArn)) { + // 6. To verify the CMK that was actually used in the decrypt operation, inspect the keyring trace. + if(!decryptResult.getKeyringTrace().getEntries().get(0).getKeyName().equals(keyArn.toString())) { throw new IllegalStateException("Wrong key ID!"); } - // 7. Also, verify that the encryption context in the result contains the - // encryption context supplied to the encryptData method. Because the - // SDK can add values to the encryption context, don't require that - // the entire context matches. - if (!encryptionContext.entrySet().stream() - .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { - throw new IllegalStateException("Wrong Encryption Context!"); - } + // 7. To verify that the encryption context used to decrypt the data was the encryption context you expected, + // examine the encryption context in the result. This helps to ensure that you decrypted the ciphertext that + // you intended. + // + // When verifying, test that your expected encryption context is a subset of the actual encryption context, + // not an exact match. The Encryption SDK adds the signing key to the encryption context when appropriate. + assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); // 8. Verify that the decrypted plaintext matches the original plaintext assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); diff --git a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java index cdf72dc88..32be46198 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java @@ -1,11 +1,11 @@ /* * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -13,156 +13,145 @@ package com.amazonaws.crypto.examples; -import java.io.FileInputStream; -import java.io.FileOutputStream; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.CryptoOutputStream; -import com.amazonaws.encryptionsdk.MasterKeyProvider; -import com.amazonaws.encryptionsdk.jce.JceMasterKey; -import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; -import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; -import com.amazonaws.util.IOUtils; +import java.util.Arrays; /** *

- * Encrypts a file using both KMS and an asymmetric key pair. + * Encrypts data using both KMS and an asymmetric key pair. * *

* Arguments: *

    - *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master + *
  2. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html - * - *
  3. Name of file containing plaintext data to encrypt *
* - * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but - * still want the option of decrypting your data offline independently of KMS. This sample + * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but + * still want the option of decrypting your data offline independently of KMS. This sample * demonstrates one way to do this. - * + * * The sample encrypts data under both a KMS customer master key (CMK) and an "escrowed" RSA key pair - * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, + * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, * at any time, you can use the private RSA key to decrypt the ciphertext independent of KMS. * - * This sample uses the JCEMasterKey class to generate a RSA public-private key pair - * and saves the key pair in memory. In practice, you would store the private key in a secure offline + * This sample uses a RawRsaKeyring to generate a RSA public-private key pair + * and saves the key pair in memory. In practice, you would store the private key in a secure offline * location, such as an offline HSM, and distribute the public key to your development team. * */ public class EscrowedEncryptExample { - private static PublicKey publicEscrowKey; - private static PrivateKey privateEscrowKey; + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) throws GeneralSecurityException { + escrowEncryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); + } - public static void main(final String[] args) throws Exception { + static void escrowEncryptAndDecrypt(AwsKmsCmkId kmsArn) throws GeneralSecurityException { // This sample generates a new random key for each operation. - // In practice, you would distribute the public key and save the private key in secure - // storage. - generateEscrowKeyPair(); + // In practice, you would distribute the public key and save the private key in secure storage. + final KeyPair escrowKeyPair = generateEscrowKeyPair(); + + // Encrypt the data under both a KMS Key and an escrowed RSA Key + byte[] encryptedData = standardEncrypt(kmsArn, escrowKeyPair.getPublic()); - final String kmsArn = args[0]; - final String fileName = args[1]; + // Decrypt the data using the KMS Key + byte[] standardDecryptedData = standardDecrypt(kmsArn, encryptedData); - standardEncrypt(kmsArn, fileName); - standardDecrypt(kmsArn, fileName); + // Decrypt the data using the escrowed RSA Key + byte[] escrowedDecryptedData = escrowDecrypt(encryptedData, escrowKeyPair.getPrivate()); - escrowDecrypt(fileName); + // Verify both decrypted data instances are the same as the original plaintext + assert Arrays.equals(standardDecryptedData, EXAMPLE_DATA); + assert Arrays.equals(escrowedDecryptedData, EXAMPLE_DATA); } - private static void standardEncrypt(final String kmsArn, final String fileName) throws Exception { + private static byte[] standardEncrypt(final AwsKmsCmkId kmsArn, final PublicKey publicEscrowKey) { // Encrypt with the KMS CMK and the escrowed public key + // 1. Instantiate the SDK final AwsCrypto crypto = new AwsCrypto(); - // 2. Instantiate a KMS master key provider - final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); - - // 3. Instantiate a JCE master key provider - // Because the user does not have access to the private escrow key, - // they pass in "null" for the private key parameter. - final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", - "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); - - // 4. Combine the providers into a single master key provider - final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); - - // 5. Encrypt the file - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - final FileInputStream in = new FileInputStream(fileName); - final FileOutputStream out = new FileOutputStream(fileName + ".encrypted"); - final CryptoOutputStream encryptingStream = crypto.createEncryptingStream(provider, out); - - IOUtils.copy(in, encryptingStream); - in.close(); - encryptingStream.close(); + // 2. Instantiate a KMS keyring, supplying the keyArn as the generator for generating a data key. + final Keyring kmsKeyring = StandardKeyrings.awsKms(kmsArn); + + // 3. Instantiate a RawRsaKeyring + // Because the user does not have access to the private escrow key, + // they do not provide the private key parameter. + final Keyring rsaKeyring = StandardKeyrings.rawRsaBuilder() + .keyNamespace("Escrow") + .keyName("Escrow") + .publicKey(publicEscrowKey) + .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .build(); + + // 4. Combine the providers into a single MultiKeyring + final Keyring keyring = StandardKeyrings.multi(kmsKeyring, rsaKeyring); + + // 5. Encrypt the data with the keyring. + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + return crypto.encrypt(EncryptRequest.builder() + .keyring(keyring) + .plaintext(EXAMPLE_DATA).build()) + .getResult(); } - private static void standardDecrypt(final String kmsArn, final String fileName) throws Exception { - // Decrypt with the KMS CMK and the escrow public key. You can use a combined provider, - // as shown here, or just the KMS master key provider. + private static byte[] standardDecrypt(final AwsKmsCmkId kmsArn, final byte[] cipherText) { + // Decrypt with the KMS CMK // 1. Instantiate the SDK final AwsCrypto crypto = new AwsCrypto(); - // 2. Instantiate a KMS master key provider - final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); - - // 3. Instantiate a JCE master key provider - // Because the user does not have access to the private - // escrow key, they pass in "null" for the private key parameter. - final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", - "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); - - // 4. Combine the providers into a single master key provider - final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); - - // 5. Decrypt the file - // To simplify the code, we omit the encryption context. Production code should always - // 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); - IOUtils.copy(in, decryptingStream); - in.close(); - decryptingStream.close(); + // 2. Instantiate a KMS keyring, supplying the keyArn as the generator for generating a data key. + final Keyring kmsKeyring = StandardKeyrings.awsKms(kmsArn); + + // 4. Decrypt the data with the keyring. + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + return crypto.decrypt(DecryptRequest.builder() + .keyring(kmsKeyring) + .ciphertext(cipherText).build()).getResult(); } - private static void escrowDecrypt(final String fileName) throws Exception { + private static byte[] escrowDecrypt(final byte[] cipherText, final PrivateKey privateEscrowKey) { // You can decrypt the stream using only the private key. // This method does not call KMS. // 1. Instantiate the SDK final AwsCrypto crypto = new AwsCrypto(); - // 2. Instantiate a JCE master key provider - // This method call uses the escrowed private key, not null - final JceMasterKey escrowPriv = JceMasterKey.getInstance(publicEscrowKey, privateEscrowKey, "Escrow", "Escrow", - "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); - - // 3. Decrypt the file - // To simplify the code, we omit the encryption context. Production code should always - // 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 + ".deescrowed"); - final CryptoOutputStream decryptingStream = crypto.createDecryptingStream(escrowPriv, out); - IOUtils.copy(in, decryptingStream); - in.close(); - decryptingStream.close(); - + // 2. Instantiate a RawRsaKeyring using the escrowed private key + final Keyring rsaKeyring = StandardKeyrings.rawRsaBuilder() + .keyNamespace("Escrow") + .keyName("Escrow") + .privateKey(privateEscrowKey) + .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .build(); + + // 3. Decrypt the data with the keyring + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + return crypto.decrypt(DecryptRequest.builder() + .keyring(rsaKeyring) + .ciphertext(cipherText).build()).getResult(); } - private static void generateEscrowKeyPair() throws GeneralSecurityException { + private static KeyPair generateEscrowKeyPair() throws GeneralSecurityException { final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); kg.initialize(4096); // Escrow keys should be very strong - final KeyPair keyPair = kg.generateKeyPair(); - publicEscrowKey = keyPair.getPublic(); - privateEscrowKey = keyPair.getPrivate(); - + return kg.generateKeyPair(); } } diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java index 22ade3b3b..94cd38853 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java @@ -1,11 +1,11 @@ /* * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except * in compliance with the License. A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -13,21 +13,26 @@ package com.amazonaws.crypto.examples; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoInputStream; +import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.util.IOUtils; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.BufferedReader; +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; import java.security.SecureRandom; import java.util.Collections; import java.util.Map; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.CryptoInputStream; -import com.amazonaws.encryptionsdk.MasterKey; -import com.amazonaws.encryptionsdk.jce.JceMasterKey; -import com.amazonaws.util.IOUtils; +import java.util.Objects; /** *

@@ -40,61 +45,106 @@ * * *

- * This program demonstrates using a standard Java {@link SecretKey} object as a {@link MasterKey} to + * This program demonstrates using a standard Java {@link SecretKey} object in a {@link Keyring} to * encrypt and decrypt streaming data. */ public class FileStreamingExample { - private static String srcFile; public static void main(String[] args) throws IOException { - srcFile = args[0]; + final File srcFile = new File(args[0]); + final File encryptedFile = new File(args[1]); + final File decryptedFile = new File(args[2]); + + encryptAndDecrypt(srcFile, encryptedFile, decryptedFile); + + } - // In this example, we generate a random key. In practice, - // you would get a key from an existing store - SecretKey cryptoKey = retrieveEncryptionKey(); + static void encryptAndDecrypt(final File srcFile, final File encryptedFile, final File decryptedFile) throws IOException { + // 1. Instantiate the SDK + final AwsCrypto crypto = new AwsCrypto(); - // Create a JCE master key provider using the random key and an AES-GCM encryption algorithm - JceMasterKey masterKey = JceMasterKey.getInstance(cryptoKey, "Example", "RandomKey", "AES/GCM/NoPadding"); + // 2. Get an encryption key. In this example, we generate a random key. + // In practice, you would get a key from an existing key store. + final SecretKey cryptoKey = generateEncryptKey(); - // Instantiate the SDK - AwsCrypto crypto = new AwsCrypto(); + // 3. Instantiate a RawAesKeyring using the random key + final Keyring keyring = StandardKeyrings.rawAesBuilder() + .keyNamespace("Example") + .keyName("RandomKey") + .wrappingKey(cryptoKey) + .build(); - // Create an encryption context to identify this ciphertext - Map context = Collections.singletonMap("Example", "FileStreaming"); + // 4. Create an encryption context + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = Collections.singletonMap("Example", "FileStreaming"); + + // 5. Create the encrypting input stream with the keyring and encryption context. + // Because the file might be too large to load into memory, + // we stream the data, instead of loading it all at once. + try (final AwsCryptoInputStream encryptingStream = crypto.createEncryptingInputStream( + CreateEncryptingInputStreamRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .inputStream(new FileInputStream(srcFile)).build())) { + + // 6. Copy the encrypted data into the encrypted file. + try (FileOutputStream out = new FileOutputStream(encryptedFile)) { + IOUtils.copy(encryptingStream, out); + } + } - // Because the file might be to large to load into memory, we stream the data, instead of - //loading it all at once. - FileInputStream in = new FileInputStream(srcFile); - CryptoInputStream encryptingStream = crypto.createEncryptingStream(masterKey, in, context); + // 7. Create the decrypting input stream with the keyring. + try (final AwsCryptoInputStream decryptingStream = crypto.createDecryptingInputStream( + CreateDecryptingInputStreamRequest.builder() + .keyring(keyring) + .inputStream(new FileInputStream(encryptedFile)).build())) { - FileOutputStream out = new FileOutputStream(srcFile + ".encrypted"); - IOUtils.copy(encryptingStream, out); - encryptingStream.close(); - out.close(); + // 8. Verify that the encryption context that was used to decrypt the data is the one that you expect. + // This helps to ensure that the ciphertext that you decrypted was the one that you intended. + // + // When verifying, test that your expected encryption context is a subset of the actual encryption context, + // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. + assert "FileStreaming".equals(decryptingStream.getAwsCryptoResult().getEncryptionContext().get("Example")); - // Decrypt the file. Verify the encryption context before returning the plaintext. - in = new FileInputStream(srcFile + ".encrypted"); - CryptoInputStream decryptingStream = crypto.createDecryptingStream(masterKey, in); - // Does it contain the expected encryption context? - if (!"FileStreaming".equals(decryptingStream.getCryptoResult().getEncryptionContext().get("Example"))) { - throw new IllegalStateException("Bad encryption context"); + // 9. Copy the plaintext data to a file + try (FileOutputStream out = new FileOutputStream(decryptedFile)) { + IOUtils.copy(decryptingStream, out); + } } - // Return the plaintext data - out = new FileOutputStream(srcFile + ".decrypted"); - IOUtils.copy(decryptingStream, out); - decryptingStream.close(); - out.close(); + // 10. Compare the decrypted file to the original + compareFiles(decryptedFile, srcFile); } /** - * In practice, this key would be saved in a secure location. - * For this demo, we generate a new random key for each operation. + * In practice, this key would be saved in a secure location. + * In this example, we generate a new random key for each operation. */ - private static SecretKey retrieveEncryptionKey() { + private static SecretKey generateEncryptKey() { SecureRandom rnd = new SecureRandom(); byte[] rawKey = new byte[16]; // 128 bits rnd.nextBytes(rawKey); return new SecretKeySpec(rawKey, "AES"); } -} \ No newline at end of file + + private static void compareFiles(File file1, File file2) throws IOException { + assert file1.length() == file2.length(); + + try (BufferedReader file1Reader = Files.newBufferedReader(file1.toPath()); + BufferedReader file2Reader = Files.newBufferedReader(file2.toPath())) { + String file1Line; + String file2Line; + + while ((file1Line = file1Reader.readLine()) != null && + (file2Line = file2Reader.readLine()) != null) { + assert Objects.equals(file1Line, file2Line); + } + } + } + +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java new file mode 100644 index 000000000..9af19ca54 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +/** + *

+ * Encrypts and then decrypts data using the Raw AES keyring. + */ +public class RawAesKeyringExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + encryptAndDecrypt(); + } + + static void encryptAndDecrypt() { + // 1. Instantiate the SDK + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Get an encryption key. In this example, we generate a random key. + // In practice, you would get a key from an existing key store + final SecretKey cryptoKey = generateEncryptKey(); + + // 3. Instantiate a Raw AES keyring with the encryption key + final Keyring keyring = StandardKeyrings.rawAesBuilder() + .keyNamespace("ExampleKeyNamespace") + .keyName("ExampleKeyName") + .wrappingKey(cryptoKey).build(); + + // 4. Create an encryption context + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 5. Encrypt the data with the keyring and encryption context + final AwsCryptoResult encryptResult = crypto.encrypt(EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(EXAMPLE_DATA).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // 6. Decrypt the data + final AwsCryptoResult decryptResult = crypto.decrypt(DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + + // 7. Verify that the encryption context that was used to decrypt the data is the one that you expect. + // This helps to ensure that the ciphertext that you decrypted was the one that you intended. + // + // When verifying, test that your expected encryption context is a subset of the actual encryption context, + // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. + assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); + + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } + + /** + * In practice, this key would be saved in a secure location. + * For this demo, we generate a new random key for each operation. + */ + private static SecretKey generateEncryptKey() { + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[16]; // 128 bits + rnd.nextBytes(rawKey); + return new SecretKeySpec(rawKey, "AES"); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java new file mode 100644 index 000000000..a5f3ba488 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.KeyPair; + +/** + *

+ * Decrypts data using the Raw RSA keyring. + */ +public class RawRsaKeyringDecryptExample { + + public static byte[] decrypt(byte[] ciphertext, KeyPair keyPair) { + // 1. Instantiate the SDK + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a Raw RSA keyring with the private key + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + .keyNamespace("ExampleKeyNamespace") + .keyName("ExampleKeyName") + .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .privateKey(keyPair.getPrivate()).build(); + + // 3. Decrypt the ciphertext with the keyring + final AwsCryptoResult decryptResult = crypto.decrypt(DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + + // 4. Verify that the encryption context that was used to decrypt the data is the one that you expect. + // This helps to ensure that the ciphertext that you decrypted was the one that you intended. + // + // When verifying, test that your expected encryption context is a subset of the actual encryption context, + // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. + assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); + + // 5. Return the decrypted byte array result + return decryptResult.getResult(); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java new file mode 100644 index 000000000..480ac0092 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.nio.charset.StandardCharsets; +import java.security.PublicKey; +import java.util.Collections; +import java.util.Map; + +/** + * Encrypts data using the Raw RSA keyring. + */ +public class RawRsaKeyringEncryptExample { + + static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static byte[] encrypt(PublicKey publicKey) { + // 1. Instantiate the SDK + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a Raw RSA keyring with the public key + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + .keyNamespace("ExampleKeyNamespace") + .keyName("ExampleKeyName") + .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .publicKey(publicKey).build(); + + // 3. Create an encryption context + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data with the keyring and encryption context + final AwsCryptoResult encryptResult = crypto.encrypt(EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(EXAMPLE_DATA).build()); + + // 5. Return the encrypted byte array result + return encryptResult.getResult(); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java new file mode 100644 index 000000000..6cc926d26 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples.datakeycaching; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; +import com.amazonaws.services.dynamodbv2.document.DynamoDB; +import com.amazonaws.services.dynamodbv2.document.Item; +import com.amazonaws.services.dynamodbv2.document.Table; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent; +import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord; +import com.amazonaws.util.BinaryUtils; + +/** + * Decrypts all incoming Kinesis records and writes records to DynamoDB. + */ +public class LambdaDecryptAndWriteExample implements RequestHandler { + private static final long MAX_ENTRY_AGE_MILLISECONDS = 600000; + private static final int MAX_CACHE_ENTRIES = 100; + private final CachingCryptoMaterialsManager cachingMaterialsManager_; + private final AwsCrypto crypto_; + private final Table table_; + + /** + * This code doesn't set the max bytes or max message security thresholds that are enforced + * only on data keys used for encryption. + * + * @param cmkArn The AWS KMS customer master key to use for decryption + * @param tableName The name of the DynamoDB table name that stores decrypted messages + */ + public LambdaDecryptAndWriteExample(final String cmkArn, final String tableName) { + cachingMaterialsManager_ = CachingCryptoMaterialsManager.newBuilder() + .withKeyring(StandardKeyrings.awsKms(AwsKmsCmkId.fromString(cmkArn))) + .withCache(new LocalCryptoMaterialsCache(MAX_CACHE_ENTRIES)) + .withMaxAge(MAX_ENTRY_AGE_MILLISECONDS, TimeUnit.MILLISECONDS) + .build(); + crypto_ = new AwsCrypto(); + table_ = new DynamoDB(AmazonDynamoDBClientBuilder.defaultClient()).getTable(tableName); + } + + /** + * Decrypts Kinesis events and writes the data to DynamoDB + * + * @param event The KinesisEvent to decrypt + * @param context The lambda context + */ + @Override + public Void handleRequest(KinesisEvent event, Context context) { + for (KinesisEventRecord record : event.getRecords()) { + ByteBuffer ciphertextBuffer = record.getKinesis().getData(); + byte[] ciphertext = BinaryUtils.copyAllBytesFrom(ciphertextBuffer); + + // Decrypt and unpack record + AwsCryptoResult plaintextResult = crypto_.decrypt( + DecryptRequest.builder() + .cryptoMaterialsManager(cachingMaterialsManager_) + .ciphertext(ciphertext).build()); + + // Verify the encryption context value + String streamArn = record.getEventSourceARN(); + String streamName = streamArn.substring(streamArn.indexOf("/") + 1); + if (!streamName.equals(plaintextResult.getEncryptionContext().get("stream"))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // Write record to DynamoDB + String jsonItem = new String(plaintextResult.getResult(), StandardCharsets.UTF_8); + table_.putItem(Item.fromJSON(jsonItem)); + } + + return null; + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java new file mode 100644 index 000000000..d7d7e071f --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java @@ -0,0 +1,125 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples.datakeycaching; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.regions.Region; +import com.amazonaws.services.kinesis.AmazonKinesis; +import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; +import com.amazonaws.util.json.Jackson; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static java.util.Collections.emptyList; + +/** + * Pushes data to Kinesis Streams in multiple Regions. + */ +public class MultiRegionRecordPusherExample { + private static final long MAX_ENTRY_AGE_MILLISECONDS = 300000; + private static final long MAX_ENTRY_USES = 100; + private static final int MAX_CACHE_ENTRIES = 100; + private final String streamName_; + private final ArrayList kinesisClients_; + private final CachingCryptoMaterialsManager cachingMaterialsManager_; + private final AwsCrypto crypto_; + + /** + * Creates an instance of this object with Kinesis clients for all target Regions + * and a cached key provider containing AWS KMS master keys in all target Regions. + */ + public MultiRegionRecordPusherExample(final Region[] regions, final String kmsAliasName, final String streamName) { + streamName_ = streamName; + crypto_ = new AwsCrypto(); + kinesisClients_ = new ArrayList<>(); + + final DefaultAWSCredentialsProviderChain credentialsProvider = new DefaultAWSCredentialsProviderChain(); + + // Build AwsKmsKeyring and AmazonKinesisClient objects for each target Region + final List keyrings = new ArrayList<>(); + + for (Region region : regions) { + kinesisClients_.add(AmazonKinesisClientBuilder.standard() + .withCredentials(credentialsProvider) + .withRegion(region.getName()) + .build()); + + keyrings.add(StandardKeyrings.awsKmsBuilder() + .awsKmsClientSupplier(AwsKmsClientSupplier.builder() + .credentialsProvider(credentialsProvider) + .allowedRegions(Collections.singleton(region.getName())) + .build()) + .generatorKeyId(AwsKmsCmkId.fromString(kmsAliasName)).build()); + } + + // Collect keyrings into a single multi-keyring and add cache. In this example, the keyring for the + // first region is used as the generatorKeyring to generate a data key. + final List childrenKeyrings = keyrings.size() > 1 ? keyrings.subList(1, keyrings.size()) : emptyList(); + final Keyring keyring = StandardKeyrings.multi(keyrings.get(0), childrenKeyrings); + + cachingMaterialsManager_ = CachingCryptoMaterialsManager.newBuilder() + .withKeyring(keyring) + .withCache(new LocalCryptoMaterialsCache(MAX_CACHE_ENTRIES)) + .withMaxAge(MAX_ENTRY_AGE_MILLISECONDS, TimeUnit.MILLISECONDS) + .withMessageUseLimit(MAX_ENTRY_USES) + .build(); + } + + /** + * JSON serializes and encrypts the received record data and pushes it to all target streams. + */ + public void putRecord(final Map data) { + String partitionKey = UUID.randomUUID().toString(); + Map encryptionContext = Collections.singletonMap("stream", streamName_); + + // JSON serialize data + String jsonData = Jackson.toJsonString(data); + + // Encrypt data + AwsCryptoResult result = crypto_.encrypt( + EncryptRequest.builder() + .cryptoMaterialsManager(cachingMaterialsManager_) + .plaintext(jsonData.getBytes()) + .encryptionContext(encryptionContext) + .build()); + + byte[] encryptedData = result.getResult(); + + // Put records to Kinesis stream in all Regions + for (AmazonKinesis regionalKinesisClient : kinesisClients_) { + regionalKinesisClient.putRecord( + streamName_, + ByteBuffer.wrap(encryptedData), + partitionKey + ); + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java index fecd7333c..61d185fe8 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java +++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCrypto.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; +import java.util.function.Consumer; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.BadCiphertextException; @@ -27,31 +28,46 @@ import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler; import com.amazonaws.encryptionsdk.internal.ProcessingSummary; import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; +import static java.util.Objects.requireNonNull; + /** * Provides the primary entry-point to the AWS Encryption SDK. All encryption and decryption * operations should start here. Most people will want to use either - * {@link #encryptData(MasterKeyProvider, byte[], Map)} and - * {@link #decryptData(MasterKeyProvider, byte[])} to encrypt/decrypt things. + * {@link #encrypt(EncryptRequest)} and + * {@link #decrypt(DecryptRequest)} to encrypt/decrypt things. * *

* The core concepts (and classes) in this SDK are: *

    *
  • {@link AwsCrypto} - *
  • {@link DataKey} + *
  • {@link Keyring} + *
  • {@link EncryptedDataKey} *
  • {@link MasterKey} *
  • {@link MasterKeyProvider} *
* *

* {@link AwsCrypto} provides the primary way to encrypt/decrypt data. It can operate on - * byte-arrays, streams, or {@link java.lang.String Strings}. This data is encrypted using the - * specifed {@link CryptoAlgorithm} and a {@link DataKey} which is unique to each encrypted message. - * This {@code DataKey} is then encrypted using one (or more) {@link MasterKey MasterKeys}. The - * process is reversed on decryption with the code selecting a copy of the {@code DataKey} protected - * by a usable {@code MasterKey}, decrypting the {@code DataKey}, and then decrypted the message. + * byte-arrays and streams. This data is encrypted using the specified {@link CryptoAlgorithm} and a + * {@code plaintextDataKey} which is unique to each encrypted message. This {@code plaintextDataKey} is then encrypted + * using one (or more) {@link MasterKey MasterKeys} or a {@link Keyring}. The process is reversed on decryption with the + * code selecting an {@code EncryptedDataKey} embedded in the encrypted message, decrypting the {@code EncryptedDataKey} + * using a {@link MasterKey MasterKey} or {@link Keyring}, and then decrypting the message. + * + *

+ * Note: + * {@link MasterKey}s and {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. Keyrings + * provide a simplified architecture over {@link MasterKey}s and {@link MasterKeyProvider}s; The keyring combines the + * similar responsibilities of {@link MasterKey}s and {@link MasterKeyProvider}s into one concept, as well as removes + * all key management logic from {@link CryptoMaterialsManager}s. + * + * Due to this simplified architecture, {@link MasterKey}s and {@link MasterKeyProvider}s are deprecated and will be + * removed in the future, and new implementations should use {@link Keyring}s. The below discussion on + * {@link MasterKeyProvider}s is provided for informational purposes as you transition to {@link Keyring}s. * *

* The main way to get a {@code MasterKey} is through the use of a {@link MasterKeyProvider}. This @@ -150,7 +166,11 @@ public int getEncryptionFrameSize() { * * This method is equivalent to calling {@link #estimateCiphertextSize(CryptoMaterialsManager, int, Map)} with a * {@link DefaultCryptoMaterialsManager} based on the given provider. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #estimateCiphertextSize(EstimateCiphertextSizeRequest)} */ + @Deprecated public > long estimateCiphertextSize( final MasterKeyProvider provider, final int plaintextSize, @@ -162,34 +182,31 @@ public > long estimateCiphertextSize( /** * Returns the best estimate for the output length of encrypting a plaintext with the provided * {@code plaintextSize} and {@code encryptionContext}. The actual ciphertext may be shorter. + * + * @deprecated Replaced by {@link #estimateCiphertextSize(EstimateCiphertextSizeRequest)} */ + @Deprecated public long estimateCiphertextSize( CryptoMaterialsManager materialsManager, final int plaintextSize, final Map encryptionContext ) { - EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder() - .setContext(encryptionContext) - .setRequestedAlgorithm(getEncryptionAlgorithm()) - // We're not actually encrypting any data, so don't consume any bytes from the cache's limits. We do need to - // pass /something/ though, or the cache will be bypassed (as it'll assume this is a streaming encrypt of - // unknown size). - .setPlaintextSize(0) - .build(); - - final MessageCryptoHandler cryptoHandler = new EncryptionHandler( - getEncryptionFrameSize(), - checkAlgorithm(materialsManager.getMaterialsForEncrypt(request)) - ); - - return cryptoHandler.estimateOutputSize(plaintextSize); + return estimateCiphertextSize(EstimateCiphertextSizeRequest.builder() + .cryptoMaterialsManager(materialsManager) + .encryptionContext(encryptionContext) + .plaintextSize(plaintextSize) + .build()); } /** * Returns the equivalent to calling * {@link #estimateCiphertextSize(MasterKeyProvider, int, Map)} with an empty * {@code encryptionContext}. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #estimateCiphertextSize(EstimateCiphertextSizeRequest)} */ + @Deprecated public > long estimateCiphertextSize( final MasterKeyProvider provider, final int plaintextSize @@ -201,7 +218,10 @@ public > long estimateCiphertextSize( * Returns the equivalent to calling * {@link #estimateCiphertextSize(CryptoMaterialsManager, int, Map)} with an empty * {@code encryptionContext}. + * + * @deprecated Replaced by {@link #estimateCiphertextSize(EstimateCiphertextSizeRequest)} */ + @Deprecated public long estimateCiphertextSize( final CryptoMaterialsManager materialsManager, final int plaintextSize @@ -210,13 +230,59 @@ public long estimateCiphertextSize( } /** - * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey - * DataKeys} that are in turn protected by {@link MasterKey MasterKeys} provided by + * Returns the best estimate for the output length of encrypting a plaintext with the plaintext size specified in + * the provided {@link EstimateCiphertextSizeRequest}. The actual ciphertext may be shorter. + * + * @param request The {@link EstimateCiphertextSizeRequest} + * @return The estimated output length in bytes + */ + public long estimateCiphertextSize(final EstimateCiphertextSizeRequest request) { + requireNonNull(request, "request is required"); + + final EncryptionMaterialsRequest encryptionMaterialsRequest = EncryptionMaterialsRequest.newBuilder() + .setContext(request.encryptionContext()) + .setRequestedAlgorithm(getEncryptionAlgorithm()) + // We're not actually encrypting any data, so don't consume any bytes from the cache's limits. We do need to + // pass /something/ though, or the cache will be bypassed (as it'll assume this is a streaming encrypt of + // unknown size). + .setPlaintextSize(0) + .build(); + + final MessageCryptoHandler cryptoHandler = new EncryptionHandler( + getEncryptionFrameSize(), + checkAlgorithm(request.cryptoMaterialsManager().getMaterialsForEncrypt(encryptionMaterialsRequest)) + ); + + return cryptoHandler.estimateOutputSize(request.plaintextSize()); + } + + /** + * Returns the best estimate for the output length of encrypting a plaintext with the plaintext size specified in + * the provided {@link EstimateCiphertextSizeRequest}. The actual ciphertext may be shorter. + *

+ * This is a convenience which creates an instance of the {@link EstimateCiphertextSizeRequest.Builder} avoiding the need to + * create one manually via {@link EstimateCiphertextSizeRequest#builder()} + *

+ * + * @param request A Consumer that will call methods on EstimateCiphertextSizeRequest.Builder to create a request. + * @return The estimated output length in bytes + */ + public long estimateCiphertextSize(final Consumer request) { + return estimateCiphertextSize(EstimateCiphertextSizeRequest.builder().applyMutation(request).build()); + } + + /** + * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey}s + * that are in turn protected by {@link MasterKey MasterKeys} provided by * {@code provider}. * * This method is equivalent to calling {@link #encryptData(CryptoMaterialsManager, byte[], Map)} using a * {@link DefaultCryptoMaterialsManager} based on the given provider. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #encrypt(EncryptRequest)} */ + @Deprecated public > CryptoResult encryptData( final MasterKeyProvider provider, final byte[] plaintext, @@ -230,38 +296,29 @@ public > CryptoResult encryptData( /** * Returns an encrypted form of {@code plaintext} that has been protected with {@link DataKey * DataKeys} that are in turn protected by the given CryptoMaterialsProvider. + * + * @deprecated Replaced by {@link #encrypt(EncryptRequest)} */ + @Deprecated public CryptoResult encryptData( CryptoMaterialsManager materialsManager, final byte[] plaintext, final Map encryptionContext ) { - EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder() - .setContext(encryptionContext) - .setRequestedAlgorithm(getEncryptionAlgorithm()) - .setPlaintext(plaintext) - .build(); - - final MessageCryptoHandler cryptoHandler = new EncryptionHandler( - getEncryptionFrameSize(), - checkAlgorithm(materialsManager.getMaterialsForEncrypt(request)) - ); - - final int outSizeEstimate = cryptoHandler.estimateOutputSize(plaintext.length); - final byte[] out = new byte[outSizeEstimate]; - int outLen = cryptoHandler.processBytes(plaintext, 0, plaintext.length, out, 0).getBytesWritten(); - outLen += cryptoHandler.doFinal(out, outLen); - - final byte[] outBytes = Utils.truncate(out, outLen); - - //noinspection unchecked - return new CryptoResult(outBytes, cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); + return encrypt(EncryptRequest.builder() + .cryptoMaterialsManager(materialsManager) + .plaintext(plaintext) + .encryptionContext(encryptionContext).build()).toCryptoResult(); } /** * Returns the equivalent to calling {@link #encryptData(MasterKeyProvider, byte[], Map)} with * an empty {@code encryptionContext}. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #encrypt(EncryptRequest)} */ + @Deprecated public > CryptoResult encryptData(final MasterKeyProvider provider, final byte[] plaintext) { return encryptData(provider, plaintext, EMPTY_MAP); @@ -270,7 +327,10 @@ public > CryptoResult encryptData(final Master /** * Returns the equivalent to calling {@link #encryptData(CryptoMaterialsManager, byte[], Map)} with * an empty {@code encryptionContext}. + * + * @deprecated Replaced by {@link #encrypt(EncryptRequest)} */ + @Deprecated public CryptoResult encryptData( final CryptoMaterialsManager materialsManager, final byte[] plaintext @@ -278,11 +338,59 @@ public > CryptoResult encryptData(final Master return encryptData(materialsManager, plaintext, EMPTY_MAP); } + /** + * Returns an encrypted form of {@code plaintext} that has been protected with either the {@link CryptoMaterialsManager} + * or the {@link Keyring} specified in the {@link EncryptRequest}. + * + * @param request The {@link EncryptRequest} + * @return An {@link AwsCryptoResult} containing the encrypted data + */ + public AwsCryptoResult encrypt(final EncryptRequest request) { + requireNonNull(request, "request is required"); + + final EncryptionMaterialsRequest encryptionMaterialsRequest = EncryptionMaterialsRequest.newBuilder() + .setContext(request.encryptionContext()) + .setRequestedAlgorithm(getEncryptionAlgorithm()) + .setPlaintext(request.plaintext()) + .build(); + + final EncryptionMaterials encryptionMaterials = + checkAlgorithm(request.cryptoMaterialsManager().getMaterialsForEncrypt(encryptionMaterialsRequest)); + + final MessageCryptoHandler cryptoHandler = new EncryptionHandler( + getEncryptionFrameSize(), encryptionMaterials); + + final int outSizeEstimate = cryptoHandler.estimateOutputSize(request.plaintext().length); + final byte[] out = new byte[outSizeEstimate]; + int outLen = cryptoHandler.processBytes(request.plaintext(), 0, request.plaintext().length, out, 0).getBytesWritten(); + outLen += cryptoHandler.doFinal(out, outLen); + + final byte[] outBytes = Utils.truncate(out, outLen); + + return new AwsCryptoResult<>(outBytes, encryptionMaterials.getKeyringTrace(), + cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); + } + + /** + * Returns an encrypted form of {@code plaintext} that has been protected with either the {@link CryptoMaterialsManager} + * or the {@link Keyring} specified in the {@link EncryptRequest}. + *

+ * This is a convenience which creates an instance of the {@link EncryptRequest.Builder} avoiding the need to + * create one manually via {@link EncryptRequest#builder()} + *

+ * + * @param request A Consumer that will call methods on EncryptRequest.Builder to create a request. + * @return An {@link AwsCryptoResult} containing the encrypted data + */ + public AwsCryptoResult encrypt(final Consumer request) { + return encrypt(EncryptRequest.builder().applyMutation(request).build()); + } + /** * Calls {@link #encryptData(MasterKeyProvider, byte[], Map)} on the UTF-8 encoded bytes of * {@code plaintext} and base64 encodes the result. - * @deprecated Use the {@link #encryptData(MasterKeyProvider, byte[], Map)} and - * {@link #decryptData(MasterKeyProvider, byte[])} APIs instead. {@code encryptString} and {@code decryptString} + * @deprecated Use the {@link #encrypt(EncryptRequest)} and + * {@link #decrypt(DecryptRequest)} APIs instead. {@code encryptString} and {@code decryptString} * work as expected if you use them together. However, to work with other language implementations of the AWS * Encryption SDK, you need to base64-decode the output of {@code encryptString} and base64-encode the input to * {@code decryptString}. These deprecated APIs will be removed in the future. @@ -358,7 +466,11 @@ public > CryptoResult encryptString(final Mast * Decrypts the provided {@code ciphertext} by requesting that the {@code provider} unwrap any * usable {@link DataKey} in the ciphertext and then decrypts the ciphertext using that * {@code DataKey}. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #decrypt(DecryptRequest)} */ + @Deprecated public > CryptoResult decryptData(final MasterKeyProvider provider, final byte[] ciphertext) { return decryptData(Utils.assertNonNull(provider, "provider"), new @@ -372,7 +484,10 @@ public > CryptoResult decryptData(final Master * @param materialsManager the {@link CryptoMaterialsManager} to use for decryption operations. * @param ciphertext the ciphertext to attempt to decrypt. * @return the {@link CryptoResult} with the decrypted data. + * + * @deprecated Replaced by {@link #decrypt(DecryptRequest)} */ + @Deprecated public CryptoResult decryptData( final CryptoMaterialsManager materialsManager, final byte[] ciphertext @@ -383,8 +498,12 @@ public > CryptoResult decryptData(final Master /** * @see #decryptData(MasterKeyProvider, byte[]) + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #decrypt(DecryptRequest)} */ @SuppressWarnings("unchecked") + @Deprecated public > CryptoResult decryptData( final MasterKeyProvider provider, final ParsedCiphertext ciphertext) { Utils.assertNonNull(provider, "provider"); @@ -393,22 +512,38 @@ public > CryptoResult decryptData( /** * @see #decryptData(CryptoMaterialsManager, byte[]) + * + * @deprecated Replaced by {@link #decrypt(DecryptRequest)} */ + @Deprecated public CryptoResult decryptData( final CryptoMaterialsManager materialsManager, final ParsedCiphertext ciphertext ) { - Utils.assertNonNull(materialsManager, "materialsManager"); + return decrypt(DecryptRequest.builder() + .cryptoMaterialsManager(materialsManager) + .parsedCiphertext(ciphertext).build()).toCryptoResult(); + } - final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager, ciphertext); + /** + * Decrypts the provided {@link ParsedCiphertext} using the {@link CryptoMaterialsManager} or the {@link Keyring} + * specified in the {@link DecryptRequest}. + * + * @param request The {@link DecryptRequest} + * @return An {@link AwsCryptoResult} containing the decrypted data + */ + public AwsCryptoResult decrypt(final DecryptRequest request) { + requireNonNull(request, "request is required"); - final byte[] ciphertextBytes = ciphertext.getCiphertext(); - final int contentLen = ciphertextBytes.length - ciphertext.getOffset(); + final MessageCryptoHandler cryptoHandler = DecryptionHandler.create( + request.cryptoMaterialsManager(), request.parsedCiphertext()); + + final byte[] ciphertextBytes = request.parsedCiphertext().getCiphertext(); + final int contentLen = ciphertextBytes.length - request.parsedCiphertext().getOffset(); final int outSizeEstimate = cryptoHandler.estimateOutputSize(contentLen); final byte[] out = new byte[outSizeEstimate]; - final ProcessingSummary processed = cryptoHandler.processBytes(ciphertextBytes, ciphertext.getOffset(), - contentLen, out, - 0); + final ProcessingSummary processed = cryptoHandler.processBytes(ciphertextBytes, request.parsedCiphertext().getOffset(), + contentLen, out, 0); if (processed.getBytesProcessed() != contentLen) { throw new BadCiphertextException("Unable to process entire ciphertext. May have trailing data."); } @@ -417,8 +552,23 @@ public > CryptoResult decryptData( final byte[] outBytes = Utils.truncate(out, outLen); - //noinspection unchecked - return new CryptoResult(outBytes, cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); + return new AwsCryptoResult<>(outBytes, cryptoHandler.getKeyringTrace(), + cryptoHandler.getMasterKeys(), cryptoHandler.getHeaders()); + } + + /** + * Decrypts the provided {@link ParsedCiphertext} using the {@link CryptoMaterialsManager} or the {@link Keyring} + * specified in the {@link DecryptRequest}. + *

+ * This is a convenience which creates an instance of the {@link DecryptRequest.Builder} avoiding the need to + * create one manually via {@link DecryptRequest#builder()} + *

+ * + * @param request A Consumer that will call methods on DecryptRequest.Builder to create a request. + * @return An {@link AwsCryptoResult} containing the decrypted data + */ + public AwsCryptoResult decrypt(final Consumer request) { + return decrypt(DecryptRequest.builder().applyMutation(request).build()); } /** @@ -475,7 +625,11 @@ public > CryptoResult decryptString( * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherOutputStream + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #createEncryptingOutputStream(CreateEncryptingOutputStreamRequest)} */ + @Deprecated public > CryptoOutputStream createEncryptingStream( final MasterKeyProvider provider, final OutputStream os, @@ -492,20 +646,31 @@ public > CryptoOutputStream createEncryptingStream( * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherOutputStream + * + * @deprecated Replaced by {@link #createEncryptingOutputStream(CreateEncryptingOutputStreamRequest)} */ + @Deprecated public CryptoOutputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os, final Map encryptionContext ) { - return new CryptoOutputStream<>(os, getEncryptingStreamHandler(materialsManager, encryptionContext)); + return createEncryptingOutputStream(CreateEncryptingOutputStreamRequest.builder() + .cryptoMaterialsManager(materialsManager) + .outputStream(os) + .encryptionContext(encryptionContext) + .build()).toCryptoOutputStream(); } /** * Returns the equivalent to calling * {@link #createEncryptingStream(MasterKeyProvider, OutputStream, Map)} with an empty * {@code encryptionContext}. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #createEncryptingOutputStream(CreateEncryptingOutputStreamRequest)} */ + @Deprecated public > CryptoOutputStream createEncryptingStream( final MasterKeyProvider provider, final OutputStream os) { @@ -516,7 +681,10 @@ public > CryptoOutputStream createEncryptingStream( * Returns the equivalent to calling * {@link #createEncryptingStream(CryptoMaterialsManager, OutputStream, Map)} with an empty * {@code encryptionContext}. + * + * @deprecated Replaced by {@link #createEncryptingOutputStream(CreateEncryptingOutputStreamRequest)} */ + @Deprecated public CryptoOutputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os @@ -524,13 +692,44 @@ public CryptoOutputStream createEncryptingStream( return createEncryptingStream(materialsManager, os, EMPTY_MAP); } + /** + * Returns a {@link AwsCryptoOutputStream} which encrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + * @see #encrypt(EncryptRequest) + * @see javax.crypto.CipherOutputStream + */ + public AwsCryptoOutputStream createEncryptingOutputStream(final CreateEncryptingOutputStreamRequest request) { + return new AwsCryptoOutputStream(request.outputStream(), getEncryptingStreamHandler( + request.cryptoMaterialsManager(), request.encryptionContext())); + } + + /** + * Returns a {@link AwsCryptoOutputStream} which encrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + *

+ * This is a convenience which creates an instance of the {@link CreateEncryptingOutputStreamRequest.Builder} avoiding the need to + * create one manually via {@link CreateEncryptingOutputStreamRequest#builder()} + *

+ * + * @see #encrypt(EncryptRequest) + * @see javax.crypto.CipherOutputStream + */ + public AwsCryptoOutputStream createEncryptingOutputStream(final Consumer request) { + return createEncryptingOutputStream(CreateEncryptingOutputStreamRequest.builder().applyMutation(request).build()); + } + /** * Returns a {@link CryptoInputStream} which encrypts the data after reading it from the * underlying {@link InputStream}. * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherInputStream + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #createEncryptingInputStream(CreateEncryptingInputStreamRequest)} */ + @Deprecated public > CryptoInputStream createEncryptingStream( final MasterKeyProvider provider, final InputStream is, @@ -547,22 +746,30 @@ public > CryptoInputStream createEncryptingStream( * * @see #encryptData(MasterKeyProvider, byte[], Map) * @see javax.crypto.CipherInputStream + * + * @deprecated Replaced by {@link #createEncryptingInputStream(CreateEncryptingInputStreamRequest)} */ + @Deprecated public CryptoInputStream createEncryptingStream( CryptoMaterialsManager materialsManager, final InputStream is, final Map encryptionContext ) { - final MessageCryptoHandler cryptoHandler = getEncryptingStreamHandler(materialsManager, encryptionContext); - - return new CryptoInputStream<>(is, cryptoHandler); + return createEncryptingInputStream(CreateEncryptingInputStreamRequest.builder() + .cryptoMaterialsManager(materialsManager) + .encryptionContext(encryptionContext) + .inputStream(is).build()).toCryptoInputStream(); } /** * Returns the equivalent to calling * {@link #createEncryptingStream(MasterKeyProvider, InputStream, Map)} with an empty * {@code encryptionContext}. + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #createEncryptingInputStream(CreateEncryptingInputStreamRequest)} */ + @Deprecated public > CryptoInputStream createEncryptingStream( final MasterKeyProvider provider, final InputStream is @@ -574,12 +781,47 @@ public > CryptoInputStream createEncryptingStream( * Returns the equivalent to calling * {@link #createEncryptingStream(CryptoMaterialsManager, InputStream, Map)} with an empty * {@code encryptionContext}. + * + * @deprecated Replaced by {@link #createEncryptingInputStream(CreateEncryptingInputStreamRequest)} */ + @Deprecated public CryptoInputStream createEncryptingStream( final CryptoMaterialsManager materialsManager, final InputStream is ) { - return createEncryptingStream(materialsManager, is, EMPTY_MAP); + return createEncryptingInputStream(CreateEncryptingInputStreamRequest.builder() + .cryptoMaterialsManager(materialsManager) + .inputStream(is).build()).toCryptoInputStream(); + } + + /** + * Returns a {@link AwsCryptoInputStream} which encrypts the data after reading it from the + * underlying {@link InputStream}. + * + * @see #encrypt(EncryptRequest) + * @see javax.crypto.CipherInputStream + */ + public AwsCryptoInputStream createEncryptingInputStream(final CreateEncryptingInputStreamRequest request) { + requireNonNull(request, "request is required"); + final MessageCryptoHandler cryptoHandler = getEncryptingStreamHandler( + request.cryptoMaterialsManager(), request.encryptionContext()); + + return new AwsCryptoInputStream(request.inputStream(), cryptoHandler); + } + + /** + * Returns a {@link AwsCryptoInputStream} which encrypts the data after reading it from the + * underlying {@link InputStream}. + *

+ * This is a convenience which creates an instance of the {@link CreateEncryptingInputStreamRequest.Builder} avoiding the need to + * create one manually via {@link CreateEncryptingInputStreamRequest#builder()} + *

+ * + * @see #encrypt(EncryptRequest) + * @see javax.crypto.CipherInputStream + */ + public AwsCryptoInputStream createEncryptingInputStream(final Consumer request) { + return createEncryptingInputStream(CreateEncryptingInputStreamRequest.builder().applyMutation(request).build()); } /** @@ -588,7 +830,11 @@ public CryptoInputStream createEncryptingStream( * * @see #decryptData(MasterKeyProvider, byte[]) * @see javax.crypto.CipherOutputStream + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #createDecryptingOutputStream(CreateDecryptingOutputStreamRequest)} */ + @Deprecated public > CryptoOutputStream createDecryptingStream( final MasterKeyProvider provider, final OutputStream os) { final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider); @@ -601,7 +847,11 @@ public > CryptoOutputStream createDecryptingStream( * * @see #decryptData(MasterKeyProvider, byte[]) * @see javax.crypto.CipherInputStream + * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * Replaced by {@link #createDecryptingInputStream(CreateDecryptingInputStreamRequest)} */ + @Deprecated public > CryptoInputStream createDecryptingStream( final MasterKeyProvider provider, final InputStream is) { final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(provider); @@ -614,12 +864,44 @@ public > CryptoInputStream createDecryptingStream( * * @see #decryptData(CryptoMaterialsManager, byte[]) * @see javax.crypto.CipherOutputStream + * + * @deprecated Replaced by {@link #createDecryptingOutputStream(CreateDecryptingOutputStreamRequest)} */ + @Deprecated public CryptoOutputStream createDecryptingStream( final CryptoMaterialsManager materialsManager, final OutputStream os ) { - final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager); - return new CryptoOutputStream(os, cryptoHandler); + return createDecryptingOutputStream(CreateDecryptingOutputStreamRequest.builder() + .cryptoMaterialsManager(materialsManager) + .outputStream(os).build()).toCryptoOutputStream(); + } + + /** + * Returns a {@link AwsCryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + * + * @see #decrypt(DecryptRequest) + * @see javax.crypto.CipherOutputStream + */ + public AwsCryptoOutputStream createDecryptingOutputStream(final CreateDecryptingOutputStreamRequest request) { + final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(request.cryptoMaterialsManager()); + return new AwsCryptoOutputStream(request.outputStream(), cryptoHandler); + } + + + /** + * Returns a {@link AwsCryptoOutputStream} which decrypts the data prior to passing it onto the + * underlying {@link OutputStream}. + *

+ * This is a convenience which creates an instance of the {@link CreateDecryptingOutputStreamRequest.Builder} avoiding the need to + * create one manually via {@link CreateDecryptingOutputStreamRequest#builder()} + *

+ * + * @see #decrypt(DecryptRequest) + * @see javax.crypto.CipherOutputStream + */ + public AwsCryptoOutputStream createDecryptingOutputStream(final Consumer request) { + return createDecryptingOutputStream(CreateDecryptingOutputStreamRequest.builder().applyMutation(request).build()); } /** @@ -628,12 +910,45 @@ public CryptoOutputStream createDecryptingStream( * * @see #encryptData(CryptoMaterialsManager, byte[], Map) * @see javax.crypto.CipherInputStream + * + * @deprecated Replaced by {@link #createDecryptingInputStream(CreateDecryptingInputStreamRequest)} */ + @Deprecated public CryptoInputStream createDecryptingStream( final CryptoMaterialsManager materialsManager, final InputStream is ) { - final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(materialsManager); - return new CryptoInputStream(is, cryptoHandler); + return createDecryptingInputStream(CreateDecryptingInputStreamRequest.builder() + .cryptoMaterialsManager(materialsManager) + .inputStream(is).build()).toCryptoInputStream(); + } + + /** + * Returns a {@link AwsCryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. + * + * @see #encrypt(EncryptRequest) + * @see javax.crypto.CipherInputStream + */ + public AwsCryptoInputStream createDecryptingInputStream(final CreateDecryptingInputStreamRequest request) { + requireNonNull(request, "request is required"); + + final MessageCryptoHandler cryptoHandler = DecryptionHandler.create(request.cryptoMaterialsManager()); + return new AwsCryptoInputStream(request.inputStream(), cryptoHandler); + } + + /** + * Returns a {@link AwsCryptoInputStream} which decrypts the data after reading it from the + * underlying {@link InputStream}. + *

+ * This is a convenience which creates an instance of the {@link CreateDecryptingInputStreamRequest.Builder} avoiding the need to + * create one manually via {@link CreateDecryptingInputStreamRequest#builder()} + *

+ * + * @see #encrypt(EncryptRequest) + * @see javax.crypto.CipherInputStream + */ + public AwsCryptoInputStream createDecryptingInputStream(final Consumer request) { + return createDecryptingInputStream(CreateDecryptingInputStreamRequest.builder().applyMutation(request).build()); } private MessageCryptoHandler getEncryptingStreamHandler( diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoInputStream.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoInputStream.java new file mode 100644 index 000000000..81f56bde4 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoInputStream.java @@ -0,0 +1,146 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.exception.BadCiphertextException; +import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler; + +import java.io.IOException; +import java.io.InputStream; + +/** + * An AwsCryptoInputStream is a subclass of java.io.InputStream. It performs cryptographic + * transformation of the bytes passing through it. + * + *

+ * The AwsCryptoInputStream wraps a provided InputStream object and performs cryptographic + * transformation of the bytes read from the wrapped InputStream. It uses the cryptography handler + * provided during construction to invoke methods that perform the cryptographic transformations. + * + *

+ * In short, reading from the AwsCryptoInputStream returns bytes that are the cryptographic + * transformations of the bytes read from the wrapped InputStream. + * + *

+ * For example, if the cryptography handler provides methods for decryption, the AwsCryptoInputStream + * will read ciphertext bytes from the wrapped InputStream, decrypt, and return them as plaintext + * bytes. + * + *

+ * This class adheres strictly to the semantics, especially the failure semantics, of its ancestor + * class java.io.InputStream. This class overrides all the methods specified in its ancestor class. + * + *

+ * To instantiate an instance of this class, please see {@link AwsCrypto}. + * + */ +public class AwsCryptoInputStream extends InputStream { + + private final CryptoInputStream cryptoInputStream; + + /** + * Constructs an AwsCryptoInputStream that wraps the provided InputStream object. It performs + * cryptographic transformation of the bytes read from the wrapped InputStream using the methods + * provided in the provided CryptoHandler implementation. + * + * @param inputStream + * the inputStream object to be wrapped. + * @param cryptoHandler + * the cryptoHandler implementation that provides the methods to use in performing + * cryptographic transformation of the bytes read from the inputStream. + */ + AwsCryptoInputStream(final InputStream inputStream, final MessageCryptoHandler cryptoHandler) { + cryptoInputStream = new CryptoInputStream<>(inputStream, cryptoHandler); + } + + /** + * {@inheritDoc} + * + * @throws BadCiphertextException + * This is thrown only during decryption if b contains invalid or corrupt + * ciphertext. + */ + @Override + public int read(final byte[] b, final int off, final int len) throws IllegalArgumentException, IOException, + BadCiphertextException { + return cryptoInputStream.read(b, off, len); + } + + /** + * {@inheritDoc} + * + * @throws BadCiphertextException + * This is thrown only during decryption if b contains invalid or corrupt + * ciphertext. + */ + @Override + public int read(final byte[] b) throws IllegalArgumentException, IOException, BadCiphertextException { + return cryptoInputStream.read(b); + } + + /** + * {@inheritDoc} + * + * @throws BadCiphertextException + * if b contains invalid or corrupt ciphertext. This is thrown only during + * decryption. + */ + @Override + public int read() throws IOException, BadCiphertextException { + return cryptoInputStream.read(); + } + + @Override + public void close() throws IOException { + cryptoInputStream.close(); + } + + /** + * Returns metadata associated with the performed cryptographic operation. + */ + @Override + public int available() throws IOException { + return cryptoInputStream.available(); + } + + /** + * Sets an upper bound on the size of the input data. This method should be called before reading any data from the + * stream. If this method is not called prior to reading any data, performance may be reduced (notably, it will not + * be possible to cache data keys when encrypting). + * + * Among other things, this size is used to enforce limits configured on the {@link CachingCryptoMaterialsManager}. + * + * If the input size set here is exceeded, an exception will be thrown, and the encryption or decryption will fail. + * + * @param size Maximum input size. + */ + public void setMaxInputLength(long size) { + cryptoInputStream.setMaxInputLength(size); + } + + /** + * Gets the {@link AwsCryptoResult}. + * + * @return The {@link AwsCryptoResult} + * @throws IOException if an input/output exception occurs while processing the result + */ + public AwsCryptoResult getAwsCryptoResult() throws IOException { + return cryptoInputStream.getAwsCryptoResult(this); + } + + CryptoInputStream toCryptoInputStream() { + return cryptoInputStream; + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoOutputStream.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoOutputStream.java new file mode 100644 index 000000000..13e5f3a2e --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoOutputStream.java @@ -0,0 +1,153 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.exception.BadCiphertextException; +import com.amazonaws.encryptionsdk.internal.MessageCryptoHandler; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An AwsCryptoOutputStream is a subclass of java.io.OutputStream. It performs cryptographic + * transformation of the bytes passing through it. + * + *

+ * The AwsCryptoOutputStream wraps a provided OutputStream object and performs cryptographic + * transformation of the bytes written to it. The transformed bytes are then written to the wrapped + * OutputStream. It uses the cryptography handler provided during construction to invoke methods + * that perform the cryptographic transformations. + * + *

+ * In short, writing to the AwsCryptoOutputStream results in those bytes being cryptographically + * transformed and written to the wrapped OutputStream. + * + *

+ * For example, if the crypto handler provides methods for decryption, the AwsCryptoOutputStream will + * decrypt the provided ciphertext bytes and write the plaintext bytes to the wrapped OutputStream. + * + *

+ * This class adheres strictly to the semantics, especially the failure semantics, of its ancestor + * class java.io.OutputStream. This class overrides all the methods specified in its ancestor class. + * + *

+ * To instantiate an instance of this class, please see {@link AwsCrypto}. + * + */ +public class AwsCryptoOutputStream extends OutputStream { + + private final CryptoOutputStream cryptoOutputStream; + + /** + * Constructs an AwsCryptoOutputStream that wraps the provided OutputStream object. It performs + * cryptographic transformation of the bytes written to it using the methods provided in the + * provided CryptoHandler implementation. The transformed bytes are then written to the wrapped + * OutputStream. + * + * @param outputStream + * the outputStream object to be wrapped. + * @param cryptoHandler + * the cryptoHandler implementation that provides the methods to use in performing + * cryptographic transformation of the bytes written to this stream. + */ + AwsCryptoOutputStream(final OutputStream outputStream, final MessageCryptoHandler cryptoHandler) { + cryptoOutputStream = new CryptoOutputStream<>(outputStream, cryptoHandler); + } + + /** + * {@inheritDoc} + * + * @throws BadCiphertextException + * This is thrown only during decryption if b contains invalid or corrupt + * ciphertext. + */ + @Override + public void write(final byte[] b) throws IllegalArgumentException, IOException, BadCiphertextException { + cryptoOutputStream.write(b); + } + + /** + * {@inheritDoc} + * + * @throws BadCiphertextException + * This is thrown only during decryption if b contains invalid or corrupt + * ciphertext. + */ + @Override + public void write(final byte[] b, final int off, final int len) throws IllegalArgumentException, IOException, + BadCiphertextException { + cryptoOutputStream.write(b, off, len); + } + + /** + * {@inheritDoc} + * + * @throws BadCiphertextException + * This is thrown only during decryption if b contains invalid or corrupt + * ciphertext. + */ + @Override + public void write(int b) throws IOException, BadCiphertextException { + cryptoOutputStream.write(b); + } + + /** + * Closes this output stream and releases any system resources associated + * with this stream. + * + *

+ * This method writes any final bytes to the underlying stream that complete + * the cryptographic transformation of the written bytes. It also calls close + * on the wrapped OutputStream. + * + * @throws IOException + * if an I/O error occurs. + * @throws BadCiphertextException + * This is thrown only during decryption if b contains invalid + * or corrupt ciphertext. + */ + @Override + public void close() throws IOException, BadCiphertextException { + cryptoOutputStream.close(); + } + + /** + * Sets an upper bound on the size of the input data. This method should be called before writing any data to the + * stream. If this method is not called prior to writing data, performance may be reduced (notably, it will not + * be possible to cache data keys when encrypting). + * + * Among other things, this size is used to enforce limits configured on the {@link CachingCryptoMaterialsManager}. + * + * If the size set here is exceeded, an exception will be thrown, and the encryption or decryption will fail. + * + * @param size Maximum input size. + */ + public void setMaxInputLength(long size) { + cryptoOutputStream.setMaxInputLength(size); + } + + /** + * Gets the {@link AwsCryptoResult}. + * + * @return The {@link AwsCryptoResult} + */ + public AwsCryptoResult getAwsCryptoResult() { + return cryptoOutputStream.getAwsCryptoResult(this); + } + + CryptoOutputStream toCryptoOutputStream() { + return cryptoOutputStream; + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoRequest.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoRequest.java new file mode 100644 index 000000000..658c044ad --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.keyrings.Keyring; + +import java.util.function.Consumer; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; + +class AwsCryptoRequest { + private final CryptoMaterialsManager cryptoMaterialsManager; + + AwsCryptoRequest(Builder builder) { + isTrue(builder.cryptoMaterialsManager != null || builder.keyring != null, + "Either a cryptoMaterialsManager or keyring is required"); + isTrue(builder.cryptoMaterialsManager == null || builder.keyring == null, + "Only one of cryptoMaterialsManager or keyring may be specified"); + + this.cryptoMaterialsManager = builder.cryptoMaterialsManager == null ? + new DefaultCryptoMaterialsManager(builder.keyring) : builder.cryptoMaterialsManager; + } + + public CryptoMaterialsManager cryptoMaterialsManager() { + return cryptoMaterialsManager; + } + + abstract static class Builder> { + + private CryptoMaterialsManager cryptoMaterialsManager; + private Keyring keyring; + + /** + * Sets the {@link CryptoMaterialsManager}. Either a {@link CryptoMaterialsManager} or a + * {@link Keyring} is required. + * + * @param cryptoMaterialsManager The {@link CryptoMaterialsManager} + * @return The Builder, for method chaining + */ + public T cryptoMaterialsManager(CryptoMaterialsManager cryptoMaterialsManager) { + requireNonNull(cryptoMaterialsManager, "cryptoMaterialsManager is required"); + this.cryptoMaterialsManager = cryptoMaterialsManager; + return getThis(); + } + + /** + * Sets the {@link Keyring}. Either a {@link CryptoMaterialsManager} or a + * {@link Keyring} is required. + * + * @param keyring The {@link Keyring} + * @return The Builder, for method chaining + */ + public T keyring(Keyring keyring) { + requireNonNull(keyring, "keyring is required"); + this.keyring = keyring; + return getThis(); + } + + abstract T getThis(); + + T applyMutation(Consumer mutator) { + mutator.accept(getThis()); + return getThis(); + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoResult.java b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoResult.java new file mode 100644 index 000000000..6bd38b78f --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/AwsCryptoResult.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; +import com.amazonaws.encryptionsdk.model.CiphertextHeaders; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Represents the result of an operation by {@link AwsCrypto}. It not only captures the + * {@code result} of the operation but also additional metadata such as the + * {@code encryptionContext}, {@code algorithm}, {@link KeyringTrace}, and any other information + * captured in the {@link CiphertextHeaders}. + * + * @param + * the type of the underlying {@code result} + */ +public class AwsCryptoResult { + private final T result; + private final KeyringTrace keyringTrace; + private final List masterKeys; + private final Map encryptionContext; + private final CiphertextHeaders headers; + + /** + * Note, does not make a defensive copy of any of the data. + */ + AwsCryptoResult(final T result, final KeyringTrace keyringTrace, final List masterKeys, final CiphertextHeaders headers) { + this.result = result; + this.keyringTrace = keyringTrace; + this.masterKeys = Collections.unmodifiableList(masterKeys); + this.headers = headers; + encryptionContext = this.headers.getEncryptionContextMap(); + } + + /** + * The actual result of the cryptographic operation. This is not a defensive copy and callers + * should not modify it. + * + * @return The result + */ + public T getResult() { + return result; + } + + /** + * The {@link KeyringTrace} containing all of the actions that keyrings have taken. + * + * @return The {@link KeyringTrace} + */ + public KeyringTrace getKeyringTrace() { + return keyringTrace; + } + + /** + * The encryption context. + * + * @return The encryption context + */ + public Map getEncryptionContext() { + return encryptionContext; + } + + /** + * Convenience method equivalent to {@link #getHeaders()}{@code .getCryptoAlgoId()}. + */ + public CryptoAlgorithm getAlgorithmSuite() { + return headers.getCryptoAlgoId(); + } + + /** + * The Ciphertext Headers. + * + * @return The CiphertextHeaders + */ + public CiphertextHeaders getHeaders() { + return headers; + } + + /** + * Package-private method for converting to a legacy CryptoResult. + */ + CryptoResult toCryptoResult() { + return new CryptoResult<>(result, masterKeys, headers); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingInputStreamRequest.java b/src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingInputStreamRequest.java new file mode 100644 index 000000000..989612f0b --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingInputStreamRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import java.io.InputStream; + +import static java.util.Objects.requireNonNull; + +public class CreateDecryptingInputStreamRequest extends AwsCryptoRequest { + + private final InputStream inputStream; + + private CreateDecryptingInputStreamRequest(Builder builder) { + super(builder); + + requireNonNull(builder.inputStream, "inputStream is required"); + this.inputStream = builder.inputStream; + } + + /** + * The {@link InputStream} to be read from. + * + * @return The {@link InputStream} to be read from. + */ + public InputStream inputStream() { + return this.inputStream; + } + + /** + * A builder for constructing an instance of {@code CreateDecryptingInputStreamRequest}. + * + * @return A builder for constructing an instance of {@code CreateDecryptingInputStreamRequest}. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private InputStream inputStream; + + /** + * Sets the {@link InputStream} + * + * @param inputStream The {@link InputStream} + * @return The Builder, for method chaining + */ + public Builder inputStream(InputStream inputStream) { + requireNonNull(inputStream, "inputStream is required"); + this.inputStream = inputStream; + return this; + } + + /** + * Constructs the CreateEncryptingInputStreamRequest instance. + * + * @return The CreateEncryptingInputStreamRequest instance + */ + public CreateDecryptingInputStreamRequest build() { + return new CreateDecryptingInputStreamRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingOutputStreamRequest.java b/src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingOutputStreamRequest.java new file mode 100644 index 000000000..372e9c1b9 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/CreateDecryptingOutputStreamRequest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import java.io.OutputStream; + +import static java.util.Objects.requireNonNull; + +public class CreateDecryptingOutputStreamRequest extends AwsCryptoRequest { + + private final OutputStream outputStream; + + private CreateDecryptingOutputStreamRequest(Builder builder) { + super(builder); + + requireNonNull(builder.outputStream, "outputStream is required"); + this.outputStream = builder.outputStream; + } + + /** + * The {@link OutputStream} to be read from. + * + * @return The {@link OutputStream} to be read from. + */ + public OutputStream outputStream() { + return this.outputStream; + } + + /** + * A builder for constructing an instance of {@code CreateDecryptingOutputStreamRequest}. + * + * @return A builder for constructing an instance of {@code CreateDecryptingOutputStreamRequest}. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private OutputStream outputStream; + + /** + * Sets the {@link OutputStream} + * + * @param outputStream The {@link OutputStream} + * @return The Builder, for method chaining + */ + public Builder outputStream(OutputStream outputStream) { + requireNonNull(outputStream, "outputStream is required"); + this.outputStream = outputStream; + return this; + } + + /** + * Constructs the CreateDecryptingOutputStreamRequest instance. + * + * @return The CreateDecryptingOutputStreamRequest instance + */ + public CreateDecryptingOutputStreamRequest build() { + return new CreateDecryptingOutputStreamRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingInputStreamRequest.java b/src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingInputStreamRequest.java new file mode 100644 index 000000000..98926977b --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingInputStreamRequest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public class CreateEncryptingInputStreamRequest extends AwsCryptoRequest { + + private final InputStream inputStream; + private final Map encryptionContext; + + private CreateEncryptingInputStreamRequest(Builder builder) { + super(builder); + + requireNonNull(builder.inputStream, "inputStream is required"); + requireNonNull(builder.encryptionContext, "encryptionContext is required"); + this.inputStream = builder.inputStream; + this.encryptionContext = builder.encryptionContext; + } + + /** + * The {@link InputStream} to be read from. + * + * @return The {@link InputStream} to be read from. + */ + public InputStream inputStream() { + return this.inputStream; + } + + /** + * The encryption context associated with this encryption. + * + * @return The encryption context associated with this encryption. + */ + public Map encryptionContext() { + return this.encryptionContext; + } + + /** + * A builder for constructing an instance of {@code CreateEncryptingInputStreamRequest}. + * + * @return A builder for constructing an instance of {@code CreateEncryptingInputStreamRequest}. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private InputStream inputStream; + private Map encryptionContext = Collections.emptyMap(); + + /** + * Sets the {@link InputStream} + * + * @param inputStream The {@link InputStream} + * @return The Builder, for method chaining + */ + public Builder inputStream(InputStream inputStream) { + requireNonNull(inputStream, "inputStream is required"); + this.inputStream = inputStream; + return this; + } + + /** + * Sets the (optional) encryption context map. + * + * @param encryptionContext The encryption context + * @return The Builder, for method chaining + */ + public Builder encryptionContext(Map encryptionContext) { + requireNonNull(encryptionContext, "encryptionContext is required"); + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + /** + * Constructs the CreateEncryptingInputStreamRequest instance. + * + * @return The CreateEncryptingInputStreamRequest instance + */ + public CreateEncryptingInputStreamRequest build() { + return new CreateEncryptingInputStreamRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingOutputStreamRequest.java b/src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingOutputStreamRequest.java new file mode 100644 index 000000000..bb76b78a8 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/CreateEncryptingOutputStreamRequest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public class CreateEncryptingOutputStreamRequest extends AwsCryptoRequest { + + private final OutputStream outputStream; + private final Map encryptionContext; + + private CreateEncryptingOutputStreamRequest(Builder builder) { + super(builder); + + requireNonNull(builder.outputStream, "outputStream is required"); + requireNonNull(builder.encryptionContext, "encryptionContext is required"); + this.outputStream = builder.outputStream; + this.encryptionContext = builder.encryptionContext; + } + + /** + * The {@link OutputStream} to be read from. + * + * @return The {@link OutputStream} to be read from. + */ + public OutputStream outputStream() { + return this.outputStream; + } + + /** + * The encryption context associated with this encryption. + * + * @return The encryption context associated with this encryption. + */ + public Map encryptionContext() { + return this.encryptionContext; + } + + /** + * A builder for constructing an instance of {@code CreateEncryptingOutputStreamRequest}. + * + * @return A builder for constructing an instance of {@code CreateEncryptingOutputStreamRequest}. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private OutputStream outputStream; + private Map encryptionContext = Collections.emptyMap(); + + /** + * Sets the {@link OutputStream} + * + * @param outputStream The {@link OutputStream} + * @return The Builder, for method chaining + */ + public Builder outputStream(OutputStream outputStream) { + requireNonNull(outputStream, "outputStream is required"); + this.outputStream = outputStream; + return this; + } + + /** + * Sets the (optional) encryption context map. + * + * @param encryptionContext The encryption context + * @return The Builder, for method chaining + */ + public Builder encryptionContext(Map encryptionContext) { + requireNonNull(encryptionContext, "encryptionContext is required"); + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + /** + * Constructs the CreateEncryptingOutputStreamRequest instance. + * + * @return The CreateEncryptingOutputStreamRequest instance + */ + public CreateEncryptingOutputStreamRequest build() { + return new CreateEncryptingOutputStreamRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java index 935e4fbc0..ee1588deb 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoInputStream.java @@ -51,7 +51,10 @@ * * @param * The type of {@link MasterKey}s used to manipulate the data. + * + * @deprecated Replaced by {@link AwsCryptoInputStream} */ +@Deprecated public class CryptoInputStream> extends InputStream { private static final int MAX_READ_LEN = 4096; @@ -254,4 +257,18 @@ public CryptoResult, K> getCryptoResult() throws BadCiphert (List) cryptoHandler_.getMasterKeys(), cryptoHandler_.getHeaders()); } + + AwsCryptoResult getAwsCryptoResult(AwsCryptoInputStream awsCryptoInputStream) throws IOException { + while (!cryptoHandler_.getHeaders().isComplete()) { + if (fillOutBytes() == -1) { + throw new BadCiphertextException("No CiphertextHeaders found."); + } + } + + return new AwsCryptoResult<>( + awsCryptoInputStream, + cryptoHandler_.getKeyringTrace(), + cryptoHandler_.getMasterKeys(), + cryptoHandler_.getHeaders()); + } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java index de17f4a2f..6a4cda59e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoOutputStream.java @@ -49,7 +49,10 @@ * * @param * The type of {@link MasterKey}s used to manipulate the data. + * + * @deprecated Replaced by {@link AwsCryptoOutputStream} */ +@Deprecated public class CryptoOutputStream> extends OutputStream { private final OutputStream outputStream_; @@ -181,4 +184,16 @@ public CryptoResult, K> getCryptoResult() { (List) cryptoHandler_.getMasterKeys(), cryptoHandler_.getHeaders()); } -} \ No newline at end of file + + AwsCryptoResult getAwsCryptoResult(AwsCryptoOutputStream awsCryptoOutputStream) { + if (!cryptoHandler_.getHeaders().isComplete()) { + throw new IllegalStateException("Ciphertext headers not yet written to stream"); + } + + return new AwsCryptoResult<>( + awsCryptoOutputStream, + cryptoHandler_.getKeyringTrace(), + cryptoHandler_.getMasterKeys(), + cryptoHandler_.getHeaders()); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java b/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java index 616ed3756..a550b4687 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java +++ b/src/main/java/com/amazonaws/encryptionsdk/CryptoResult.java @@ -30,7 +30,10 @@ * the type of the underlying {@code result} * @param * the type of the {@link MasterKey}s used in production of this result + * + * @deprecated Replaced by {@link AwsCryptoResult} */ +@Deprecated public class CryptoResult> { private final T result_; private final List masterKeys_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/DataKey.java b/src/main/java/com/amazonaws/encryptionsdk/DataKey.java index 21d174f00..a07287447 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/DataKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/DataKey.java @@ -20,7 +20,10 @@ * * @param * the type of {@link MasterKey} used to protect this {@code DataKey}. + * @deprecated This type depends on the deprecated {@link MasterKey} type. Use {@link SecretKey} or + * {@link EncryptedDataKey} instead. */ +@Deprecated public class DataKey> implements EncryptedDataKey { private final byte[] providerInformation_; private final byte[] encryptedDataKey_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/DecryptRequest.java b/src/main/java/com/amazonaws/encryptionsdk/DecryptRequest.java new file mode 100644 index 000000000..075df54cd --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/DecryptRequest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.isTrue; + +public class DecryptRequest extends AwsCryptoRequest { + + private final ParsedCiphertext parsedCiphertext; + + private DecryptRequest(Builder builder) { + super(builder); + + isTrue(builder.ciphertext != null || builder.parsedCiphertext != null, + "Either ciphertext or parsedCiphertext is required"); + isTrue(builder.ciphertext == null || builder.parsedCiphertext == null, + "Only one of ciphertext or parsedCiphertext may be specified"); + + this.parsedCiphertext = builder.parsedCiphertext == null ? + new ParsedCiphertext(builder.ciphertext) : builder.parsedCiphertext; + } + + public ParsedCiphertext parsedCiphertext() { + return this.parsedCiphertext; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private ParsedCiphertext parsedCiphertext; + private byte[] ciphertext; + + /** + * Sets the {@link ParsedCiphertext} to decrypt. Either {@link ParsedCiphertext} or a + * {@code byte[]} ciphertext is required. + * + * @param parsedCiphertext The {@link ParsedCiphertext} + * @return The Builder, for method chaining + */ + public Builder parsedCiphertext(ParsedCiphertext parsedCiphertext) { + requireNonNull(parsedCiphertext, "parsedCiphertext is required"); + this.parsedCiphertext = parsedCiphertext; + return this; + } + + /** + * Sets the ciphertext byte array to decrypt. Either {@link ParsedCiphertext} or a + * {@code byte[]} ciphertext is required. Note that this does not make a defensive + * copy of the ciphertext and so any modifications made to the backing array will be + * reflected in this Builder. + * + * @param ciphertext The ciphertext + * @return The Builder, for method chaining + */ + public Builder ciphertext(byte[] ciphertext) { + requireNonNull(ciphertext, "ciphertext is required"); + this.ciphertext = ciphertext; + return this; + } + + /** + * Constructs the DecryptRequest instance. + * + * @return The DecryptRequest instance + */ + public DecryptRequest build() { + return new DecryptRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java b/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java index d31c615b5..781d193f5 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java +++ b/src/main/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManager.java @@ -1,9 +1,11 @@ package com.amazonaws.encryptionsdk; -import static com.amazonaws.encryptionsdk.internal.Utils.assertNonNull; +import static com.amazonaws.encryptionsdk.AwsCrypto.getDefaultCryptoAlgorithm; +import static java.util.Objects.requireNonNull; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; import java.util.HashMap; @@ -13,61 +15,91 @@ import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; import com.amazonaws.encryptionsdk.internal.Constants; -import com.amazonaws.encryptionsdk.internal.Utils; import com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm; -import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; import com.amazonaws.encryptionsdk.model.KeyBlob; -import com.amazonaws.encryptionsdk.model.EncryptionMaterials; /** * The default implementation of {@link CryptoMaterialsManager}, used implicitly when passing a - * {@link MasterKeyProvider} to methods in {@link AwsCrypto}. + * {@link MasterKeyProvider} or {@link Keyring} to methods in {@link AwsCrypto}. * - * This default implementation delegates to a specific {@link MasterKeyProvider} specified at construction time. It also - * handles generating trailing signature keys when needed, placing them in the encryption context (and extracting them - * at decrypt time). + * This default implementation delegates to a specific {@link MasterKeyProvider} or {@link Keyring} specified at + * construction time. It also handles generating trailing signature keys when needed, placing them in the + * encryption context (and extracting them at decrypt time). */ public class DefaultCryptoMaterialsManager implements CryptoMaterialsManager { + // Exactly one of keyring or mkp should be null + private final Keyring keyring; private final MasterKeyProvider mkp; /** - * @param mkp The master key provider to delegate to + * @param masterKeyProvider The master key provider to delegate to + * + * @deprecated Replaced by {@link #DefaultCryptoMaterialsManager(Keyring)} */ - public DefaultCryptoMaterialsManager(MasterKeyProvider mkp) { - Utils.assertNonNull(mkp, "mkp"); - this.mkp = mkp; + @Deprecated + public DefaultCryptoMaterialsManager(MasterKeyProvider masterKeyProvider) { + requireNonNull(masterKeyProvider, "masterKeyProvider is required"); + this.mkp = masterKeyProvider; + this.keyring = null; } - @Override public EncryptionMaterials getMaterialsForEncrypt(EncryptionMaterialsRequest request) { - Map context = request.getContext(); + /** + * @param keyring The keyring to delegate to + */ + public DefaultCryptoMaterialsManager(Keyring keyring) { + requireNonNull(keyring, "keyring is required"); + this.keyring = keyring; + this.mkp = null; + } - CryptoAlgorithm algo = request.getRequestedAlgorithm(); - if (algo == null) { - algo = AwsCrypto.getDefaultCryptoAlgorithm(); + @Override + public EncryptionMaterials getMaterialsForEncrypt(EncryptionMaterialsRequest request) { + if(keyring != null) { + return getEncryptionMaterialsForKeyring(request); } - KeyPair trailingKeys = null; - if (algo.getTrailingSignatureLength() > 0) { - try { - trailingKeys = generateTrailingSigKeyPair(algo); - if (context.containsKey(Constants.EC_PUBLIC_KEY_FIELD)) { - throw new IllegalArgumentException("EncryptionContext contains reserved field " - + Constants.EC_PUBLIC_KEY_FIELD); - } - // make mutable - context = new HashMap<>(context); - context.put(Constants.EC_PUBLIC_KEY_FIELD, serializeTrailingKeyForEc(algo, trailingKeys)); - } catch (final GeneralSecurityException ex) { - throw new AwsCryptoException(ex); - } + return getEncryptionMaterialsForMasterKeyProvider(request); + } + + @Override + public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) { + if(keyring != null) { + return getDecryptionMaterialsForKeyring(request); } - final MasterKeyRequest.Builder mkRequestBuilder = MasterKeyRequest.newBuilder(); - mkRequestBuilder.setEncryptionContext(context); + return getDecryptionMaterialsForMasterKeyProvider(request); + } + + private EncryptionMaterials getEncryptionMaterialsForKeyring(EncryptionMaterialsRequest request) { + final CryptoAlgorithm algorithmSuite = request.getRequestedAlgorithm() != null ? + request.getRequestedAlgorithm() : getDefaultCryptoAlgorithm(); + final Map encryptionContext = new HashMap<>(request.getContext()); + final PrivateKey signingKey = getSigningKey(algorithmSuite, encryptionContext); + + final EncryptionMaterials encryptionMaterials = + EncryptionMaterials.newBuilder() + .setAlgorithm(algorithmSuite) + .setEncryptionContext(encryptionContext) + .setTrailingSignatureKey(signingKey) + .build(); - mkRequestBuilder.setStreaming(request.getPlaintextSize() == -1); + return keyring.onEncrypt(encryptionMaterials); + } + + private EncryptionMaterials getEncryptionMaterialsForMasterKeyProvider(EncryptionMaterialsRequest request) { + final Map encryptionContext = new HashMap<>(request.getContext()); + final CryptoAlgorithm algorithmSuite = request.getRequestedAlgorithm() != null ? + request.getRequestedAlgorithm() : getDefaultCryptoAlgorithm(); + final PrivateKey trailingSignatureKey = getSigningKey(algorithmSuite, encryptionContext); + + final MasterKeyRequest.Builder mkRequestBuilder = MasterKeyRequest.newBuilder() + .setEncryptionContext(encryptionContext) + .setStreaming(request.getPlaintextSize() == -1); if (request.getPlaintext() != null) { mkRequestBuilder.setPlaintext(request.getPlaintext()); } else { @@ -75,69 +107,102 @@ public DefaultCryptoMaterialsManager(MasterKeyProvider mkp) { } @SuppressWarnings("unchecked") - final List mks - = (List)assertNonNull(mkp, "provider") - .getMasterKeysForEncryption(mkRequestBuilder.build()); + final List masterKeys = (List) mkp.getMasterKeysForEncryption(mkRequestBuilder.build()); - if (mks.isEmpty()) { + if (masterKeys.isEmpty()) { throw new IllegalArgumentException("No master keys provided"); } - DataKey dataKey = mks.get(0).generateDataKey(algo, context); + final DataKey dataKey = masterKeys.get(0).generateDataKey(algorithmSuite, encryptionContext); - List keyBlobs = new ArrayList<>(mks.size()); + final List keyBlobs = new ArrayList<>(masterKeys.size()); keyBlobs.add(new KeyBlob(dataKey)); - for (int i = 1; i < mks.size(); i++) { + for (int i = 1; i < masterKeys.size(); i++) { //noinspection unchecked - keyBlobs.add(new KeyBlob(mks.get(i).encryptDataKey(algo, context, dataKey))); + keyBlobs.add(new KeyBlob(masterKeys.get(i).encryptDataKey(algorithmSuite, encryptionContext, dataKey))); } - //noinspection unchecked return EncryptionMaterials.newBuilder() - .setAlgorithm(algo) - .setCleartextDataKey(dataKey.getKey()) - .setEncryptedDataKeys(keyBlobs) - .setEncryptionContext(context) - .setTrailingSignatureKey(trailingKeys == null ? null : trailingKeys.getPrivate()) - .setMasterKeys(mks) - .build(); + .setAlgorithm(algorithmSuite) + .setCleartextDataKey(dataKey.getKey()) + .setEncryptedDataKeys(keyBlobs) + .setEncryptionContext(encryptionContext) + .setTrailingSignatureKey(trailingSignatureKey) + .setMasterKeys(masterKeys) + .build(); } - @Override public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) { - DataKey dataKey = mkp.decryptDataKey( + private DecryptionMaterials getDecryptionMaterialsForKeyring(DecryptionMaterialsRequest request) { + final PublicKey verificationKey = getVerificationKey(request); + + final DecryptionMaterials decryptionMaterials = + DecryptionMaterials.newBuilder() + .setAlgorithm(request.getAlgorithm()) + .setEncryptionContext(request.getEncryptionContext()) + .setTrailingSignatureKey(verificationKey) + .build(); + + final DecryptionMaterials result = keyring.onDecrypt(decryptionMaterials, request.getEncryptedDataKeys()); + + if(!result.hasCleartextDataKey()) { + throw new CannotUnwrapDataKeyException("Could not decrypt any data keys"); + } + + return result; + } + + private DecryptionMaterials getDecryptionMaterialsForMasterKeyProvider(DecryptionMaterialsRequest request) { + final DataKey dataKey = mkp.decryptDataKey( request.getAlgorithm(), request.getEncryptedDataKeys(), - request.getEncryptionContext() - ); + request.getEncryptionContext()); if (dataKey == null) { throw new CannotUnwrapDataKeyException("Could not decrypt any data keys"); } - PublicKey pubKey = null; + return DecryptionMaterials.newBuilder() + .setDataKey(dataKey) + .setTrailingSignatureKey(getVerificationKey(request)) + .build(); + } + + private PrivateKey getSigningKey(CryptoAlgorithm algorithmSuite, Map encryptionContext) { + if (algorithmSuite.getTrailingSignatureLength() > 0) { + try { + final KeyPair trailingKeys = generateTrailingSigKeyPair(algorithmSuite); + if (encryptionContext.containsKey(Constants.EC_PUBLIC_KEY_FIELD)) { + throw new IllegalArgumentException("EncryptionContext contains reserved field " + + Constants.EC_PUBLIC_KEY_FIELD); + } + encryptionContext.put(Constants.EC_PUBLIC_KEY_FIELD, serializeTrailingKeyForEc(algorithmSuite, trailingKeys)); + return trailingKeys.getPrivate(); + } catch (final GeneralSecurityException ex) { + throw new AwsCryptoException(ex); + } + } + + return null; + } + + private PublicKey getVerificationKey(DecryptionMaterialsRequest request) { if (request.getAlgorithm().getTrailingSignatureLength() > 0) { try { - String serializedPubKey = request.getEncryptionContext().get(Constants.EC_PUBLIC_KEY_FIELD); + final String serializedPubKey = request.getEncryptionContext().get(Constants.EC_PUBLIC_KEY_FIELD); if (serializedPubKey == null) { throw new AwsCryptoException("Missing trailing signature public key"); } - pubKey = deserializeTrailingKeyFromEc(request.getAlgorithm(), serializedPubKey); + return TrailingSignatureAlgorithm.forCryptoAlgorithm( + request.getAlgorithm()).deserializePublicKey(serializedPubKey); } catch (final IllegalStateException ex) { throw new AwsCryptoException(ex); } } - return DecryptionMaterials.newBuilder() - .setDataKey(dataKey) - .setTrailingSignatureKey(pubKey) - .build(); - } - - private PublicKey deserializeTrailingKeyFromEc(CryptoAlgorithm algo, String pubKey) { - return TrailingSignatureAlgorithm.forCryptoAlgorithm(algo).deserializePublicKey(pubKey); + return null; } private static String serializeTrailingKeyForEc(CryptoAlgorithm algo, KeyPair trailingKeys) { diff --git a/src/main/java/com/amazonaws/encryptionsdk/EncryptRequest.java b/src/main/java/com/amazonaws/encryptionsdk/EncryptRequest.java new file mode 100644 index 000000000..60890d802 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/EncryptRequest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.keyrings.Keyring; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public class EncryptRequest extends AwsCryptoRequest { + + private final byte[] plaintext; + private final Map encryptionContext; + + private EncryptRequest(Builder builder) { + super(builder); + + requireNonNull(builder.plaintext, "plaintext is required"); + requireNonNull(builder.encryptionContext, "encryptionContext is required"); + this.plaintext = builder.plaintext; + this.encryptionContext = builder.encryptionContext; + } + + public byte[] plaintext() { + return this.plaintext; + } + + public Map encryptionContext() { + return this.encryptionContext; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private byte[] plaintext; + private Map encryptionContext = Collections.emptyMap(); + + /** + * Sets the plaintext byte array to encrypt. Note that this does not make a defensive copy of the + * plaintext and so any modifications made to the backing array will be reflected in this Builder. + * + * @param plaintext The {@link Keyring} + * @return The Builder, for method chaining + */ + public Builder plaintext(byte[] plaintext) { + requireNonNull(plaintext, "plaintext is required"); + this.plaintext = plaintext; + return this; + } + + /** + * Sets the (optional) encryption context map. + * + * @param encryptionContext The encryption context + * @return The Builder, for method chaining + */ + public Builder encryptionContext(Map encryptionContext) { + requireNonNull(encryptionContext, "encryptionContext is required"); + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + /** + * Constructs the EncryptRequest instance. + * + * @return The EncryptRequest instance + */ + public EncryptRequest build() { + return new EncryptRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/EstimateCiphertextSizeRequest.java b/src/main/java/com/amazonaws/encryptionsdk/EstimateCiphertextSizeRequest.java new file mode 100644 index 000000000..56b67380d --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/EstimateCiphertextSizeRequest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Objects.requireNonNull; + +public class EstimateCiphertextSizeRequest extends AwsCryptoRequest { + + private final int plaintextSize; + private final Map encryptionContext; + + private EstimateCiphertextSizeRequest(Builder builder) { + super(builder); + requireNonNull(builder.encryptionContext, "encryptionContext is required"); + + this.plaintextSize = builder.plaintextSize; + this.encryptionContext = builder.encryptionContext; + } + + public int plaintextSize() { + return plaintextSize; + } + + public Map encryptionContext() { + return encryptionContext; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AwsCryptoRequest.Builder { + + private int plaintextSize; + private Map encryptionContext = Collections.emptyMap(); + + /** + * Sets the plaintextSize. + * + * @param plaintextSize The plaintext size + * @return The Builder, for method chaining + */ + public Builder plaintextSize(int plaintextSize) { + this.plaintextSize = plaintextSize; + return this; + } + + /** + * Sets the (optional) encryption context map. + * + * @param encryptionContext The encryption context + * @return The Builder, for method chaining + */ + public Builder encryptionContext(Map encryptionContext) { + requireNonNull(encryptionContext, "encryptionContext is required"); + this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + return this; + } + + /** + * Constructs the EstimateCiphertextSizeRequest instance. + * + * @return The EstimateCiphertextSizeRequest instance + */ + public EstimateCiphertextSizeRequest build() { + return new EstimateCiphertextSizeRequest(this); + } + + @Override + Builder getThis() { + return this; + } + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/MasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/MasterKey.java index ae64e752e..9a247a463 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/MasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/MasterKey.java @@ -20,6 +20,7 @@ import com.amazonaws.encryptionsdk.exception.NoSuchMasterKeyException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.keyrings.Keyring; /** * Represents the cryptographic key used to protect the {@link DataKey} (which, in turn, protects @@ -31,7 +32,10 @@ * * @param * the concrete type of the {@link MasterKey} + * + * @deprecated Replaced by {@link Keyring} */ +@Deprecated public abstract class MasterKey> extends MasterKeyProvider { public abstract String getProviderId(); diff --git a/src/main/java/com/amazonaws/encryptionsdk/MasterKeyProvider.java b/src/main/java/com/amazonaws/encryptionsdk/MasterKeyProvider.java index 978586f2e..216841d4e 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/MasterKeyProvider.java +++ b/src/main/java/com/amazonaws/encryptionsdk/MasterKeyProvider.java @@ -22,6 +22,7 @@ import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; import com.amazonaws.encryptionsdk.exception.NoSuchMasterKeyException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.keyrings.Keyring; /** * Represents the logic necessary to select and construct {@link MasterKey}s for encrypting and @@ -29,7 +30,10 @@ * * @param * the type of {@link MasterKey} returned by this provider + * + * @deprecated Replaced by {@link Keyring} */ +@Deprecated public abstract class MasterKeyProvider> { /** * ProviderId used by this instance when no other is specified. diff --git a/src/main/java/com/amazonaws/encryptionsdk/MasterKeyRequest.java b/src/main/java/com/amazonaws/encryptionsdk/MasterKeyRequest.java index 650fd2ee4..6dffe40b6 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/MasterKeyRequest.java +++ b/src/main/java/com/amazonaws/encryptionsdk/MasterKeyRequest.java @@ -17,12 +17,16 @@ import java.util.HashMap; import java.util.Map; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Contains information which {@link MasterKeyProvider}s can use to select which {@link MasterKey}s * to use to protect a given plaintext. This class is immutable. + * + * @deprecated MasterKeys and MasterKeyProviders have been deprecated in favor of {@link Keyring}s */ +@Deprecated public final class MasterKeyRequest { private final Map encryptionContext_; private final boolean isStreaming_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/caching/CachingCryptoMaterialsManager.java b/src/main/java/com/amazonaws/encryptionsdk/caching/CachingCryptoMaterialsManager.java index daf5c549e..28daf9e50 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/caching/CachingCryptoMaterialsManager.java +++ b/src/main/java/com/amazonaws/encryptionsdk/caching/CachingCryptoMaterialsManager.java @@ -14,6 +14,7 @@ import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.internal.EncryptionContextSerializer; import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; @@ -22,16 +23,16 @@ /** * The CachingCryptoMaterialsManager wraps another {@link CryptoMaterialsManager}, and caches its results. This helps reduce - * the number of calls made to the underlying {@link CryptoMaterialsManager} and/or {@link MasterKeyProvider}, which may - * help reduce cost and/or improve performance. + * the number of calls made to the underlying {@link CryptoMaterialsManager}, {@link MasterKeyProvider} and/or + * {@link Keyring}, which may help reduce cost and/or improve performance. * * The CachingCryptoMaterialsManager helps enforce a number of usage limits on encrypt. Specifically, it limits the number of * individual messages encrypted with a particular data key, and the number of plaintext bytes encrypted with the same * data key. It also allows you to configure a maximum time-to-live for cache entries. * * Note that when performing streaming encryption operations, unless you set the stream size before writing any data - * using {@link com.amazonaws.encryptionsdk.CryptoOutputStream#setMaxInputLength(long)} or - * {@link com.amazonaws.encryptionsdk.CryptoInputStream#setMaxInputLength(long)}, the size of the message will not be + * using {@link com.amazonaws.encryptionsdk.AwsCryptoOutputStream#setMaxInputLength(long)} or + * {@link com.amazonaws.encryptionsdk.AwsCryptoInputStream#setMaxInputLength(long)}, the size of the message will not be * known, and to avoid exceeding byte use limits, caching will not be performed. * * By default, two different {@link CachingCryptoMaterialsManager}s will not share cached entries, even when using the same @@ -79,8 +80,8 @@ private Builder() {} * Sets the {@link CryptoMaterialsManager} that should be queried when the {@link CachingCryptoMaterialsManager} * incurs a cache miss. * - * You can set either a MasterKeyProvider or a CryptoMaterialsManager to back the CCMM - the last value set will - * be used. + * You can set either a MasterKeyProvider, a Keyring, or a CryptoMaterialsManager to back the CCMM - the last + * value set will be used. * * @param backingCMM The CryptoMaterialsManager to invoke on cache misses * @return this builder @@ -94,19 +95,39 @@ public Builder withBackingMaterialsManager(CryptoMaterialsManager backingCMM) { * Sets the {@link MasterKeyProvider} that should be queried when the {@link CachingCryptoMaterialsManager} * incurs a cache miss. * - * You can set either a MasterKeyProvider or a CryptoMaterialsManager to back the CCMM - the last value set will - * be used. + * You can set either a MasterKeyProvider, a Keyring, or a CryptoMaterialsManager to back the CCMM - the last + * value set will be used. * * This method is equivalent to calling {@link #withBackingMaterialsManager(CryptoMaterialsManager)} passing a * {@link DefaultCryptoMaterialsManager} constructed using your {@link MasterKeyProvider}. * + * @deprecated {@link MasterKeyProvider}s have been deprecated in favor of {@link Keyring}s. + * * @param mkp The MasterKeyProvider to invoke on cache misses * @return this builder */ + @Deprecated public Builder withMasterKeyProvider(MasterKeyProvider mkp) { return withBackingMaterialsManager(new DefaultCryptoMaterialsManager(mkp)); } + /** + * Sets the {@link Keyring} that should be queried when the {@link CachingCryptoMaterialsManager} + * incurs a cache miss. + * + * You can set either a MasterKeyProvider, a Keyring, or a CryptoMaterialsManager to back the CCMM - the last + * value set will be used. + * + * This method is equivalent to calling {@link #withBackingMaterialsManager(CryptoMaterialsManager)} passing a + * {@link DefaultCryptoMaterialsManager} constructed using your {@link Keyring}. + * + * @param keyring The Keyring to invoke on cache misses + * @return this builder + */ + public Builder withKeyring(Keyring keyring) { + return withBackingMaterialsManager(new DefaultCryptoMaterialsManager(keyring)); + } + /** * Sets the cache to which this {@link CryptoMaterialsManager} will be bound. * @param cache The cache to associate with the CMM diff --git a/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java b/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java index 2c06a602c..1a3be9350 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java +++ b/src/main/java/com/amazonaws/encryptionsdk/exception/UnsupportedRegionException.java @@ -15,7 +15,7 @@ /** * This exception is thrown when a region that is not allowed to be used by - * a given KmsClientSupplier is specified. + * a given AwsKmsClientSupplier is specified. */ public class UnsupportedRegionException extends AwsCryptoException { diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/Constants.java b/src/main/java/com/amazonaws/encryptionsdk/internal/Constants.java index e580f7c79..684515d37 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/Constants.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/Constants.java @@ -62,4 +62,9 @@ private Constants() { public static final long MAX_FRAME_NUMBER = (1L << 32) - 1; public static final String EC_PUBLIC_KEY_FIELD = "aws-crypto-public-key"; + + /** + * The provider ID used for the AwsKmsKeyring + */ + public static final String AWS_KMS_PROVIDER_ID = "aws-kms"; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java index 386a4d867..38628d010 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/DecryptionHandler.java @@ -34,6 +34,8 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.BadCiphertextException; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; import com.amazonaws.encryptionsdk.model.CiphertextFooters; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; import com.amazonaws.encryptionsdk.model.CiphertextType; @@ -62,6 +64,7 @@ public class DecryptionHandler> implements MessageCryptoH private CryptoHandler contentCryptoHandler_; private DataKey dataKey_; + private KeyringTrace keyringTrace_; private SecretKey decryptionKey_; private CryptoAlgorithm cryptoAlgo_; private Signature trailingSig_; @@ -109,8 +112,11 @@ private DecryptionHandler(final CryptoMaterialsManager materialsManager, final C * the key blobs encoded in the provided ciphertext. * @throws AwsCryptoException * if the master key is null. + * @deprecated MasterKeyProviders have been deprecated in favor of {@link Keyring}s. + * Use {@link #create(CryptoMaterialsManager)} instead. */ @SuppressWarnings("unchecked") + @Deprecated public static > DecryptionHandler create( final MasterKeyProvider customerMasterKeyProvider ) throws AwsCryptoException { @@ -134,8 +140,11 @@ public static > DecryptionHandler create( * {@link #processBytes(byte[], int, int, byte[], int)} * @throws AwsCryptoException * if the master key is null. + * @deprecated MasterKeyProviders have been deprecated in favor of {@link Keyring}s. + * Use {@link #create(CryptoMaterialsManager, CiphertextHeaders)} instead. */ @SuppressWarnings("unchecked") + @Deprecated public static > DecryptionHandler create( final MasterKeyProvider customerMasterKeyProvider, final CiphertextHeaders headers ) throws AwsCryptoException { @@ -456,6 +465,7 @@ private void readHeaderFields(final CiphertextHeaders ciphertextHeaders) { //noinspection unchecked dataKey_ = (DataKey)result.getDataKey(); + keyringTrace_ = result.getKeyringTrace(); PublicKey trailingPublicKey = result.getTrailingSignatureKey(); try { @@ -534,11 +544,28 @@ public CiphertextHeaders getHeaders() { return ciphertextHeaders_; } + /** + * The master key that was used to decrypt the encrypted data key. This returns an + * empty list if Keyrings are in use. + * + * @deprecated MasterKeys have been deprecated in favor of {@link Keyring}s. + * Use {@link #getKeyringTrace()} to view which key was used in decryption. + */ @Override + @Deprecated public List getMasterKeys() { + if(dataKey_.getMasterKey() == null) { + return Collections.emptyList(); + } + return Collections.singletonList(dataKey_.getMasterKey()); } + @Override + public KeyringTrace getKeyringTrace() { + return keyringTrace_; + } + @Override public boolean isComplete() { return complete_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java index 843704a87..b2f18021a 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/EncryptionHandler.java @@ -21,12 +21,15 @@ import java.security.Signature; import java.security.SignatureException; import java.security.interfaces.ECPrivateKey; +import java.util.Collections; import java.util.List; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.SecretKey; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; @@ -58,6 +61,7 @@ public class EncryptionHandler implements MessageCryptoHandler { private final Map encryptionContext_; private final CryptoAlgorithm cryptoAlgo_; private final List masterKeys_; + private final KeyringTrace keyringTrace_; private final List keyBlobs_; private final SecretKey encryptionKey_; private final byte version_; @@ -90,6 +94,7 @@ public EncryptionHandler(int frameSize, EncryptionMaterials result) throws AwsCr this.encryptionContext_ = result.getEncryptionContext(); this.cryptoAlgo_ = result.getAlgorithm(); this.masterKeys_ = result.getMasterKeys(); + this.keyringTrace_ = result.getKeyringTrace(); this.keyBlobs_ = result.getEncryptedDataKeys(); this.trailingSignaturePrivateKey_ = result.getTrailingSignatureKey(); @@ -405,12 +410,28 @@ private CiphertextHeaders signCiphertextHeaders(final CiphertextHeaders unsigned return unsignedHeaders; } + /** + * The master keys that were used to encrypt the data key. This returns an + * empty list if Keyrings are in use. + * + * @deprecated MasterKeys have been deprecated in favor of {@link Keyring}s. + * Use {@link #getKeyringTrace()} to view which keys were used in encryption. + */ @Override + @Deprecated public List> getMasterKeys() { + if(masterKeys_ == null) { + return Collections.emptyList(); + } //noinspection unchecked return (List)masterKeys_; // This is unmodifiable } + @Override + public KeyringTrace getKeyringTrace() { + return keyringTrace_; + } + private void updateTrailingSignature(byte[] input, int offset, int len) { if (trailingDigest_ != null) { trailingDigest_.update(input, offset, len); diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/LazyMessageCryptoHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/LazyMessageCryptoHandler.java index bc50183a4..1d6dab5af 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/LazyMessageCryptoHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/LazyMessageCryptoHandler.java @@ -6,6 +6,7 @@ import java.util.function.Function; import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; /** @@ -93,6 +94,11 @@ public List> getMasterKeys() { return getDelegate().getMasterKeys(); } + @Override + public KeyringTrace getKeyringTrace() { + return getDelegate().getKeyringTrace(); + } + @Override public int doFinal(byte[] out, int outOff) { return getDelegate().doFinal(out, outOff); diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/MessageCryptoHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/MessageCryptoHandler.java index 34445f867..1d2c7c93d 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/MessageCryptoHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/MessageCryptoHandler.java @@ -17,6 +17,8 @@ import java.util.Map; import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.KeyringTrace; import com.amazonaws.encryptionsdk.model.CiphertextHeaders; public interface MessageCryptoHandler extends CryptoHandler { @@ -48,6 +50,17 @@ public interface MessageCryptoHandler extends CryptoHandler { * All used {@link MasterKey}s. For encryption flows, these are all the * {@link MasterKey}s used to protect the data. In the decryption flow, it is the single * {@link MasterKey} actually used to decrypt the data. + * + * @deprecated MasterKeys and MasterKeyProviders have been deprecated in favor of {@link Keyring}s */ + @Deprecated List> getMasterKeys(); + + /** + * Gets the KeyringTrace containing all actions {@link Keyring}s have taken as part of + * encryption or decryption. + * + * @return the KeyringTrace + */ + KeyringTrace getKeyringTrace(); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java index 6c6e03e34..bf632356c 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/jce/JceMasterKey.java @@ -21,6 +21,7 @@ import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; import com.amazonaws.encryptionsdk.internal.JceKeyCipher; import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -37,7 +38,10 @@ * Represents a {@link MasterKey} backed by one (or more) JCE {@link Key}s. Instances of this should * only be acquired using {@link #getInstance(SecretKey, String, String, String)} or * {@link #getInstance(PublicKey, PrivateKey, String, String, String)}. + * + * @deprecated Replaced by {@code RawAesKeyring} and {@code RawRsaKeyring}. See {@link StandardKeyrings}. */ +@Deprecated public class JceMasterKey extends MasterKey { private final String providerName_; private final String keyId_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/jce/KeyStoreProvider.java b/src/main/java/com/amazonaws/encryptionsdk/jce/KeyStoreProvider.java index 1e92cd2bd..f220aea8b 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/jce/KeyStoreProvider.java +++ b/src/main/java/com/amazonaws/encryptionsdk/jce/KeyStoreProvider.java @@ -38,13 +38,17 @@ import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.NoSuchMasterKeyException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; /** * This {@link MasterKeyProvider} provides keys backed by a JCE {@link KeyStore}. Please see * {@link #decryptDataKey(CryptoAlgorithm, Collection, Map)} for an of how decryption is managed and * see {@link #getMasterKeysForEncryption(MasterKeyRequest)} for an explanation of how encryption is * managed. + * + * @deprecated Replaced by {@code RawAesKeyring} and {@code RawRsaKeyring}. See {@link StandardKeyrings}. */ +@Deprecated public class KeyStoreProvider extends MasterKeyProvider { private final String providerName_; private final KeyStore keystore_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java similarity index 60% rename from src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java rename to src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java index 528b4a1dc..76cd8a9b7 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyring.java @@ -16,14 +16,14 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; -import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.GenerateDataKeyResult; -import com.amazonaws.encryptionsdk.kms.KmsUtils; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; +import org.apache.commons.lang3.Validate; import java.util.ArrayList; import java.util.HashSet; @@ -31,65 +31,66 @@ import java.util.Set; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.isArnWellFormed; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.kms.AwsKmsCmkId.isKeyIdWellFormed; import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; /** * A keyring which interacts with AWS Key Management Service (KMS) to create, - * encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). + * encrypt, and decrypt data keys using AWS KMS defined Customer Master Keys (CMKs). */ -class KmsKeyring implements Keyring { +class AwsKmsKeyring implements Keyring { private final DataKeyEncryptionDao dataKeyEncryptionDao; - private final List keyIds; - private final String generatorKeyId; + private final List keyIds; + private final AwsKmsCmkId generatorKeyId; private final boolean isDiscovery; - KmsKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, List keyIds, String generatorKeyId) { + AwsKmsKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, List keyIds, AwsKmsCmkId generatorKeyId, boolean isDiscovery) { requireNonNull(dataKeyEncryptionDao, "dataKeyEncryptionDao is required"); this.dataKeyEncryptionDao = dataKeyEncryptionDao; this.keyIds = keyIds == null ? emptyList() : unmodifiableList(new ArrayList<>(keyIds)); this.generatorKeyId = generatorKeyId; - this.isDiscovery = this.generatorKeyId == null && this.keyIds.isEmpty(); - - if (!this.keyIds.stream().allMatch(KmsUtils::isArnWellFormed)) { - throw new MalformedArnException("keyIds must contain only CMK aliases and well formed ARNs"); + this.isDiscovery = isDiscovery; + + if(isDiscovery) { + Validate.isTrue(generatorKeyId == null && this.keyIds.isEmpty(), + "AWS KMS Discovery keyrings cannot specify any key IDs"); + } else { + Validate.isTrue(generatorKeyId != null || !this.keyIds.isEmpty(), + "GeneratorKeyId or KeyIds are required for non-discovery AWS KMS Keyrings."); } - if (generatorKeyId != null) { - if (!isArnWellFormed(generatorKeyId)) { - throw new MalformedArnException("generatorKeyId must be either a CMK alias or a well formed ARN"); - } - if (this.keyIds.contains(generatorKeyId)) { - throw new IllegalArgumentException("KeyIds should not contain the generatorKeyId"); - } + if (this.keyIds.contains(generatorKeyId)) { + throw new IllegalArgumentException("KeyIds should not contain the generatorKeyId"); } } @Override - public void onEncrypt(EncryptionMaterials encryptionMaterials) { + public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { requireNonNull(encryptionMaterials, "encryptionMaterials are required"); // If this keyring is a discovery keyring, OnEncrypt MUST return the input encryption materials unmodified. if (isDiscovery) { - return; + return encryptionMaterials; } + EncryptionMaterials resultMaterials = encryptionMaterials; + // If the input encryption materials do not contain a plaintext data key and this keyring does not // have a generator defined, OnEncrypt MUST not modify the encryption materials and MUST fail. if (!encryptionMaterials.hasCleartextDataKey() && generatorKeyId == null) { throw new AwsCryptoException("Encryption materials must contain either a plaintext data key or a generator"); } - final List keyIdsToEncrypt = new ArrayList<>(keyIds); + final List keyIdsToEncrypt = new ArrayList<>(keyIds); // If the input encryption materials do not contain a plaintext data key and a generator is defined onEncrypt // MUST attempt to generate a new plaintext data key and encrypt that data key by calling KMS GenerateDataKey. if (!encryptionMaterials.hasCleartextDataKey()) { - generateDataKey(encryptionMaterials); + resultMaterials = generateDataKey(encryptionMaterials); } else if (generatorKeyId != null) { // If this keyring's generator is defined and was not used to generate a data key, OnEncrypt // MUST also attempt to encrypt the plaintext data key using the CMK specified by the generator. @@ -98,39 +99,45 @@ public void onEncrypt(EncryptionMaterials encryptionMaterials) { // Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt // to encrypt the plaintext data key using each CMK specified in it's key IDs list. - for (String keyId : keyIdsToEncrypt) { - encryptDataKey(keyId, encryptionMaterials); + for (AwsKmsCmkId keyId : keyIdsToEncrypt) { + resultMaterials = encryptDataKey(keyId, resultMaterials); } + + return resultMaterials; } - private void generateDataKey(final EncryptionMaterials encryptionMaterials) { + private EncryptionMaterials generateDataKey(final EncryptionMaterials encryptionMaterials) { final GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey(generatorKeyId, encryptionMaterials.getAlgorithm(), encryptionMaterials.getEncryptionContext()); - encryptionMaterials.setCleartextDataKey(result.getPlaintextDataKey(), - new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.GENERATED_DATA_KEY)); - encryptionMaterials.addEncryptedDataKey(new KeyBlob(result.getEncryptedDataKey()), - new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + return encryptionMaterials + .withCleartextDataKey(result.getPlaintextDataKey(), + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, generatorKeyId.toString(), + KeyringTraceFlag.GENERATED_DATA_KEY)) + .withEncryptedDataKey(new KeyBlob(result.getEncryptedDataKey()), + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, generatorKeyId.toString(), + KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); } - private void encryptDataKey(final String keyId, final EncryptionMaterials encryptionMaterials) { + private EncryptionMaterials encryptDataKey(final AwsKmsCmkId keyId, final EncryptionMaterials encryptionMaterials) { final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey(keyId, encryptionMaterials.getCleartextDataKey(), encryptionMaterials.getEncryptionContext()); - encryptionMaterials.addEncryptedDataKey(new KeyBlob(encryptedDataKey), - new KeyringTraceEntry(KMS_PROVIDER_ID, keyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); + return encryptionMaterials.withEncryptedDataKey(new KeyBlob(encryptedDataKey), + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, keyId.toString(), + KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); } @Override - public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { requireNonNull(decryptionMaterials, "decryptionMaterials are required"); requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); if (decryptionMaterials.hasCleartextDataKey() || encryptedDataKeys.isEmpty()) { - return; + return decryptionMaterials; } - final Set configuredKeyIds = new HashSet<>(keyIds); + final Set configuredKeyIds = new HashSet<>(keyIds); if (generatorKeyId != null) { configuredKeyIds.add(generatorKeyId); @@ -142,25 +149,26 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List configuredKeyIds) { + private boolean okToDecrypt(EncryptedDataKey encryptedDataKey, Set configuredKeyIds) { // Only attempt to decrypt keys provided by KMS - if (!encryptedDataKey.getProviderId().equals(KMS_PROVIDER_ID)) { + if (!encryptedDataKey.getProviderId().equals(AWS_KMS_PROVIDER_ID)) { return false; } - // If the key ARN cannot be parsed, skip it - if(!isArnWellFormed(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING))) + // If the key ID cannot be parsed, skip it + if(!isKeyIdWellFormed(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING))) { return false; } @@ -174,6 +182,7 @@ private boolean okToDecrypt(EncryptedDataKey encryptedDataKey, Set confi // OnDecrypt MUST attempt to decrypt each input encrypted data key in the input // encrypted data key list where the key provider info has a value equal to one // of the ARNs in this keyring's key IDs or the generator - return configuredKeyIds.contains(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING)); + return configuredKeyIds.contains( + AwsKmsCmkId.fromString(new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING))); } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java new file mode 100644 index 000000000..483a13506 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; + +import java.util.List; + +public class AwsKmsKeyringBuilder { + private AwsKmsClientSupplier awsKmsClientSupplier; + private List grantTokens; + private List keyIds; + private AwsKmsCmkId generatorKeyId; + private final boolean isDiscovery; + + private AwsKmsKeyringBuilder(boolean isDiscovery) { + // Use AwsKmsKeyringBuilder.standard() or StandardKeyrings.awsKmsBuilder() to instantiate + // a standard Aws Kms Keyring builder. If an Aws Kms Discovery Keyring builder is needed use + // AwsKmsKeyringBuilder.discovery() or StandardKeyrings.awsKmsDiscoveryBuilder(). + this.isDiscovery = isDiscovery; + } + + /** + * Constructs a new instance of {@code AwsKmsKeyringBuilder} + * + * @return The {@code AwsKmsKeyringBuilder} + */ + public static AwsKmsKeyringBuilder standard() { + return new AwsKmsKeyringBuilder(false); + } + + /** + * Constructs a new instance of {@code AwsKmsKeyringBuilder} that produces an AWS KMS Discovery keyring. + * AWS KMS Discovery keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt + * using any encrypted data key in an encrypted message. AWS KMS Discovery keyrings do not perform encryption. + * + * @return The {@code AwsKmsKeyringBuilder} + */ + public static AwsKmsKeyringBuilder discovery() { + return new AwsKmsKeyringBuilder(true); + } + + /** + * A function that returns an AWS KMS client that can make GenerateDataKey, Encrypt, and Decrypt calls in + * a particular AWS region. If this is not supplied, the default AwsKmsClientSupplier will + * be used. AwsKmsClientSupplier.builder() can be used to construct this type. + * + * @param awsKmsClientSupplier The AWS KMS client supplier + * @return The AwsKmsKeyringBuilder, for method chaining + */ + public AwsKmsKeyringBuilder awsKmsClientSupplier(AwsKmsClientSupplier awsKmsClientSupplier) { + this.awsKmsClientSupplier = awsKmsClientSupplier; + return this; + } + + /** + * A list of string grant tokens to be included in all KMS calls. + * + * @param grantTokens The list of grant tokens + * @return The AwsKmsKeyringBuilder, for method chaining + */ + public AwsKmsKeyringBuilder grantTokens(List grantTokens) { + this.grantTokens = grantTokens; + return this; + } + + /** + * A list of {@link AwsKmsCmkId}s in ARN, CMK Alias, or ARN Alias format identifying AWS KMS CMKs + * used for encrypting and decrypting data keys. + * + * @param keyIds The list of AWS KMS CMKs + * @return The AwsKmsKeyringBuilder, for method chaining + */ + public AwsKmsKeyringBuilder keyIds(List keyIds) { + this.keyIds = keyIds; + return this; + } + + /** + * An {@link AwsKmsCmkId} in ARN, CMK Alias, or ARN Alias format that identifies a + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys . + * + * @param generatorKeyId An {@link AwsKmsCmkId} in ARN, CMK Alias, or ARN Alias format that identifies a + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys. + * @return The AwsKmsKeyringBuilder, for method chaining + */ + public AwsKmsKeyringBuilder generatorKeyId(AwsKmsCmkId generatorKeyId) { + this.generatorKeyId = generatorKeyId; + return this; + } + + /** + * Constructs the {@link Keyring} instance. + * + * @return The {@link Keyring} instance + */ + public Keyring build() { + if (awsKmsClientSupplier == null) { + awsKmsClientSupplier = AwsKmsClientSupplier.builder().build(); + } + + return new AwsKmsKeyring(DataKeyEncryptionDao.awsKms(awsKmsClientSupplier, grantTokens), + keyIds, generatorKeyId, isDiscovery); + } + +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java index 93002270d..0930b8406 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/Keyring.java @@ -27,16 +27,18 @@ public interface Keyring { /** * Attempt to encrypt either the given data key (if present) or one that may be generated * - * @param encryptionMaterials Materials needed for encryption that the keyring may modify. + * @param encryptionMaterials Materials needed for encryption. + * @return Encryption materials with added information provided by this keyring. */ - void onEncrypt(EncryptionMaterials encryptionMaterials); + EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials); /** * Attempt to decrypt the encrypted data keys * - * @param decryptionMaterials Materials needed for decryption that the keyring may modify. + * @param decryptionMaterials Materials needed for decryption. * @param encryptedDataKeys List of encrypted data keys. + * @return Decryption materials with added information provided by this keyring. */ - void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys); + DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java index c9528c08f..9298194e6 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/KeyringTrace.java @@ -17,35 +17,34 @@ import org.apache.commons.lang3.builder.ToStringStyle; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; /** * A keyring trace containing all of the actions that keyrings have taken on a set of encryption materials. */ -public class KeyringTrace { +public final class KeyringTrace { - private final List entries = new ArrayList<>(); + private final List entries; + public static final KeyringTrace EMPTY_TRACE = new KeyringTrace(emptyList()); - /** - * Add a new entry to the keyring trace. - * - * @param keyNamespace The namespace for the key. - * @param keyName The name of the key. - * @param flags A set of one or more KeyringTraceFlag enums - * indicating what actions were taken by a keyring. - */ - public void add(String keyNamespace, String keyName, KeyringTraceFlag... flags) { - add(new KeyringTraceEntry(keyNamespace, keyName, flags)); + public KeyringTrace(final List entries) { + this.entries = unmodifiableList(new ArrayList<>(entries)); } /** - * Add a new entry to the keyring trace. + * Creates a new instance of {@code KeyringTrace} with the provided {@link KeyringTraceEntry}. * - * @param entry The entry to add. + * @param entry The entry to include in the new {@code KeyringTrace}. + * @return The new {@code KeyringTrace} instance. */ - public void add(KeyringTraceEntry entry) { - entries.add(entry); + public KeyringTrace with(KeyringTraceEntry entry) { + final List newEntries = new ArrayList<>(entries); + newEntries.add(entry); + return new KeyringTrace(newEntries); } /** @@ -56,7 +55,7 @@ public void add(KeyringTraceEntry entry) { * @return An unmodifiable list of `KeyringTraceEntry`s */ public List getEntries() { - return Collections.unmodifiableList(entries); + return entries; } @Override @@ -65,4 +64,17 @@ public String toString() { .append("entries", entries) .toString(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + KeyringTrace that = (KeyringTrace) o; + return Objects.equals(entries, that.entries); + } + + @Override + public int hashCode() { + return Objects.hash(entries); + } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java index ce6d9600c..de00c7c85 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyring.java @@ -45,30 +45,34 @@ class MultiKeyring implements Keyring { } @Override - public void onEncrypt(EncryptionMaterials encryptionMaterials) { + public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + EncryptionMaterials resultMaterials = encryptionMaterials; + if (generatorKeyring != null) { - generatorKeyring.onEncrypt(encryptionMaterials); + resultMaterials = generatorKeyring.onEncrypt(encryptionMaterials); } - if (!encryptionMaterials.hasCleartextDataKey()) { + if (!resultMaterials.hasCleartextDataKey()) { throw new AwsCryptoException("Either a generator keyring must be supplied that produces a cleartext " + "data key or a cleartext data key must already be present in the encryption materials."); } for (Keyring keyring : childrenKeyrings) { - keyring.onEncrypt(encryptionMaterials); + resultMaterials = keyring.onEncrypt(resultMaterials); } + + return resultMaterials; } @Override - public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { requireNonNull(decryptionMaterials, "decryptionMaterials are required"); requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); if (decryptionMaterials.hasCleartextDataKey()) { - return; + return decryptionMaterials; } final List keyringsToDecryptWith = new ArrayList<>(); @@ -83,11 +87,11 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List - * Instantiate by using the {@code StandardKeyrings.rawAes(...)} factory method. + * Instantiate by using the {@code StandardKeyrings.rawAesBuilder(...)} factory method. */ class RawAesKeyring extends RawKeyring { diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringBuilder.java new file mode 100644 index 000000000..8f8929419 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import javax.crypto.SecretKey; + +public class RawAesKeyringBuilder { + private String keyNamespace; + private String keyName; + private SecretKey wrappingKey; + + private RawAesKeyringBuilder() { + // Use RawAesKeyringBuilder.standard() or StandardKeyrings.rawAes() to instantiate + } + + /** + * Constructs a new instance of {@code RawAesKeyringBuilder} + * + * @return The {@code RawAesKeyringBuilder} + */ + public static RawAesKeyringBuilder standard() { + return new RawAesKeyringBuilder(); + } + + /** + * A value that, together with the key name, identifies the wrapping key (required). + * + * @param keyNamespace The key namespace + * @return The RawAesKeyringBuilder, for method chaining + */ + public RawAesKeyringBuilder keyNamespace(String keyNamespace) { + this.keyNamespace = keyNamespace; + return this; + } + + /** + * A value that, together with the key namespace, identifies the wrapping key (required). + * + * @param keyName The key name + * @return The RawAesKeyringBuilder, for method chaining + */ + public RawAesKeyringBuilder keyName(String keyName) { + this.keyName = keyName; + return this; + } + + /** + * The AES key input to AES-GCM to encrypt plaintext data keys (required). + * + * @param wrappingKey The wrapping key + * @return The RawAesKeyringBuilder, for method chaining + */ + public RawAesKeyringBuilder wrappingKey(SecretKey wrappingKey) { + this.wrappingKey = wrappingKey; + return this; + } + + /** + * Constructs the {@link Keyring} instance. + * + * @return The {@link Keyring} instance + */ + public Keyring build() { + return new RawAesKeyring(keyNamespace, keyName, wrappingKey); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java index 18600a4d7..f26604840 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawKeyring.java @@ -65,27 +65,29 @@ abstract class RawKeyring implements Keyring { abstract boolean validToDecrypt(EncryptedDataKey encryptedDataKey); @Override - public void onEncrypt(EncryptionMaterials encryptionMaterials) { + public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { requireNonNull(encryptionMaterials, "encryptionMaterials are required"); + EncryptionMaterials resultMaterials = encryptionMaterials; + if (!encryptionMaterials.hasCleartextDataKey()) { - generateDataKey(encryptionMaterials); + resultMaterials = generateDataKey(encryptionMaterials); } final EncryptedDataKey encryptedDataKey = jceKeyCipher.encryptKey( - encryptionMaterials.getCleartextDataKey().getEncoded(), - keyName, keyNamespace, encryptionMaterials.getEncryptionContext()); - encryptionMaterials.addEncryptedDataKey(new KeyBlob(encryptedDataKey), + resultMaterials.getCleartextDataKey().getEncoded(), + keyName, keyNamespace, resultMaterials.getEncryptionContext()); + return resultMaterials.withEncryptedDataKey(new KeyBlob(encryptedDataKey), new KeyringTraceEntry(keyNamespace, keyName, encryptTraceFlags())); } @Override - public void onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { requireNonNull(decryptionMaterials, "decryptionMaterials are required"); requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); if (decryptionMaterials.hasCleartextDataKey() || encryptedDataKeys.isEmpty()) { - return; + return decryptionMaterials; } for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { @@ -93,25 +95,24 @@ public void onDecrypt(DecryptionMaterials decryptionMaterials, List - * Instantiate by using the {@code StandardKeyrings.rawRsa(...)} factory method. + * Instantiate by using the {@code StandardKeyrings.rawRsaBuilder(...)} factory method. */ class RawRsaKeyring extends RawKeyring { diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java new file mode 100644 index 000000000..190143954 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import java.security.PrivateKey; +import java.security.PublicKey; + +public class RawRsaKeyringBuilder { + private String keyNamespace; + private String keyName; + private PublicKey publicKey; + private PrivateKey privateKey; + private String wrappingAlgorithm; + + private RawRsaKeyringBuilder() { + // Use RawRsaKeyringBuilder.standard() or StandardKeyrings.rawRsa() to instantiate + } + + /** + * Constructs a new instance of {@code RawRsaKeyringBuilder} + * + * @return The {@code RawRsaKeyringBuilder} + */ + public static RawRsaKeyringBuilder standard() { + return new RawRsaKeyringBuilder(); + } + + /** + * A value that, together with the key name, identifies the wrapping key (required). + * + * @param keyNamespace The key namespace + * @return The RawAesKeyringBuilder, for method chaining + */ + public RawRsaKeyringBuilder keyNamespace(String keyNamespace) { + this.keyNamespace = keyNamespace; + return this; + } + + /** + * A value that, together with the key namespace, identifies the wrapping key (required). + * + * @param keyName The key name + * @return The RawAesKeyringBuilder, for method chaining + */ + public RawRsaKeyringBuilder keyName(String keyName) { + this.keyName = keyName; + return this; + } + + /** + * The RSA public key used by this keyring to encrypt data keys. Not required when used for decryption. + * + * @param publicKey The public key + * @return The RawRsaKeyringBuilder, for method chaining + */ + public RawRsaKeyringBuilder publicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + /** + * The RSA private key used by this keyring to decrypt data keys. Not required when used for encryption. + * + * @param privateKey The public key + * @return The RawRsaKeyringBuilder, for method chaining + */ + public RawRsaKeyringBuilder privateKey(PrivateKey privateKey) { + this.privateKey = privateKey; + return this; + } + + /** + * The RSA algorithm to use with this keyring (required). + * + * @param wrappingAlgorithm The algorithm + * @return The RawRsaKeyringBuilder, for method chaining + */ + public RawRsaKeyringBuilder wrappingAlgorithm(String wrappingAlgorithm) { + this.wrappingAlgorithm = wrappingAlgorithm; + return this; + } + + /** + * Constructs the {@link Keyring} instance. + * + * @return The {@link Keyring} instance + */ + public Keyring build() { + return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index 9219cf17c..9cf37ea12 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -13,12 +13,9 @@ package com.amazonaws.encryptionsdk.keyrings; -import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; -import com.amazonaws.encryptionsdk.kms.KmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import javax.crypto.SecretKey; -import java.security.PrivateKey; -import java.security.PublicKey; import java.util.Arrays; import java.util.List; @@ -31,48 +28,74 @@ private StandardKeyrings() { } /** - * Constructs a {@code Keyring} which does local AES-GCM encryption - * decryption of data keys using the provided wrapping key. + * Returns a {@link RawAesKeyringBuilder} for use in constructing a keyring which does local AES-GCM encryption + * decryption of data keys using a provided wrapping key. * - * @param keyNamespace A value that, together with the key name, identifies the wrapping key. - * @param keyName A value that, together with the key namespace, identifies the wrapping key. - * @param wrappingKey The AES key input to AES-GCM to encrypt plaintext data keys. - * @return The {@link Keyring} + * @return The {@link RawAesKeyringBuilder} */ - public static Keyring rawAes(String keyNamespace, String keyName, SecretKey wrappingKey) { - return new RawAesKeyring(keyNamespace, keyName, wrappingKey); + public static RawAesKeyringBuilder rawAesBuilder() { + return RawAesKeyringBuilder.standard(); } /** - * Constructs a {@code Keyring} which does local RSA encryption and decryption of data keys using the + * Constructs a {@code RawRsaKeyringBuilder} which does local RSA encryption and decryption of data keys using the * provided public and private keys. If {@code privateKey} is {@code null} then the returned {@code Keyring} * can only be used for encryption. * - * @param keyNamespace A value that, together with the key name, identifies the wrapping key. - * @param keyName A value that, together with the key namespace, identifies the wrapping key. - * @param publicKey The RSA public key used by this keyring to encrypt data keys. - * @param privateKey The RSA private key used by this keyring to decrypt data keys. - * @param wrappingAlgorithm The RSA algorithm to use with this keyring. - * @return The {@link Keyring} + * @return The {@link RawRsaKeyringBuilder} */ - public static Keyring rawRsa(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String wrappingAlgorithm) { - return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); + public static RawRsaKeyringBuilder rawRsaBuilder() { + return RawRsaKeyringBuilder.standard(); } /** * Constructs a {@code Keyring} which interacts with AWS Key Management Service (KMS) to create, - * encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). + * encrypt, and decrypt data keys using the supplied AWS KMS defined Customer Master Key (CMK). + * Use {@link #awsKmsBuilder()} for more advanced configuration using an {@link AwsKmsKeyringBuilder} * - * @param clientSupplier A function that returns a KMS client that can make GenerateDataKey, - * Encrypt, and Decrypt calls in a particular AWS region. - * @param grantTokens A list of string grant tokens to be included in all KMS calls. - * @param keyIds A list of strings identifying KMS CMKs, in ARN, CMK Alias, or ARN Alias format. - * @param generator A string that identifies a KMS CMK responsible for generating a data key, - * as well as encrypting and decrypting data keys in ARN, CMK Alias, or ARN Alias format. + * @param generatorKeyId An {@link AwsKmsCmkId} in ARN, CMK Alias, ARN Alias or Key Id format that identifies a + * AWS KMS CMK responsible for generating a data key, as well as encrypting and + * decrypting data keys . * @return The {@code Keyring} */ - public static Keyring kms(KmsClientSupplier clientSupplier, List grantTokens, List keyIds, String generator) { - return new KmsKeyring(DataKeyEncryptionDao.kms(clientSupplier, grantTokens), keyIds, generator); + public static Keyring awsKms(AwsKmsCmkId generatorKeyId) { + return AwsKmsKeyringBuilder.standard() + .generatorKeyId(generatorKeyId) + .build(); + } + + /** + * Returns a {@link AwsKmsKeyringBuilder} for use in constructing a keyring which interacts with + * AWS Key Management Service (KMS) to create, encrypt, and decrypt data keys using AWS KMS defined + * Customer Master Keys (CMKs). + * + * @return The {@link AwsKmsKeyringBuilder} + */ + public static AwsKmsKeyringBuilder awsKmsBuilder() { + return AwsKmsKeyringBuilder.standard(); + } + + /** + * Returns an {@link AwsKmsKeyringBuilder} for use in constructing an AWS KMS Discovery keyring. + * AWS KMS Discovery keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt + * using any encrypted data key in an encrypted message. AWS KMS Discovery keyrings do not perform encryption. + *

+ * To create an AWS KMS Regional Discovery Keyring, construct an {@link AwsKmsClientSupplier} using + * {@link AwsKmsClientSupplier#builder()} to specify which regions to include/exclude. + *

+ * For example, to include only CMKs in the us-east-1 region: + *
+     * StandardKeyrings.awsKmsDiscovery()
+     *             .awsKmsClientSupplier(
+     *                     AwsKmsClientSupplier.builder()
+     *                     .allowedRegions(Collections.singleton("us-east-1")).build())
+     *             .build();
+     * 
+ * + * @return The {@code AwsKmsKeyringBuilder} + */ + public static AwsKmsKeyringBuilder awsKmsDiscoveryBuilder() { + return AwsKmsKeyringBuilder.discovery(); } /** diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java similarity index 75% rename from src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java rename to src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java index 6182e161c..ceccb5936 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplier.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java @@ -14,6 +14,7 @@ package com.amazonaws.encryptionsdk.kms; import com.amazonaws.ClientConfiguration; +import com.amazonaws.arn.Arn; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; import com.amazonaws.services.kms.AWSKMS; @@ -38,7 +39,7 @@ * function should be able to handle when the region is null. */ @FunctionalInterface -public interface KmsClientSupplier { +public interface AwsKmsClientSupplier { /** * Gets an {@code AWSKMS} client for the given regionId. @@ -51,7 +52,7 @@ public interface KmsClientSupplier { AWSKMS getClient(@Nullable String regionId) throws UnsupportedRegionException; /** - * Gets a Builder for constructing a KmsClientSupplier + * Gets a Builder for constructing an AwsKmsClientSupplier * * @return The builder */ @@ -60,7 +61,26 @@ static Builder builder() { } /** - * Builder to construct a KmsClientSupplier given various + * Parses region from the given key id (if possible) and passes that region to the + * given clientSupplier to produce an {@code AWSKMS} client. + * + * @param keyId The Amazon Resource Name, Key Alias, Alias ARN or KeyId + * @param clientSupplier The client supplier + * @return AWSKMS The client + */ + static AWSKMS getClientByKeyId(AwsKmsCmkId keyId, AwsKmsClientSupplier clientSupplier) { + requireNonNull(keyId, "keyId is required"); + requireNonNull(clientSupplier, "clientSupplier is required"); + + if(keyId.isArn()) { + return clientSupplier.getClient(Arn.fromString(keyId.toString()).getRegion()); + } + + return clientSupplier.getClient(null); + } + + /** + * Builder to construct an AwsKmsClientSupplier given various * optional settings. */ class Builder { @@ -69,22 +89,22 @@ class Builder { private ClientConfiguration clientConfiguration; private Set allowedRegions = Collections.emptySet(); private Set excludedRegions = Collections.emptySet(); - private boolean clientCachingEnabled = false; + private boolean clientCachingEnabled = true; private final Map clientsCache = new HashMap<>(); - private static final Set KMS_METHODS = new HashSet<>(); - private AWSKMSClientBuilder kmsClientBuilder; + private static final Set AWSKMS_METHODS = new HashSet<>(); + private AWSKMSClientBuilder awsKmsClientBuilder; static { - KMS_METHODS.add("generateDataKey"); - KMS_METHODS.add("encrypt"); - KMS_METHODS.add("decrypt"); + AWSKMS_METHODS.add("generateDataKey"); + AWSKMS_METHODS.add("encrypt"); + AWSKMS_METHODS.add("decrypt"); } - Builder(AWSKMSClientBuilder kmsClientBuilder) { - this.kmsClientBuilder = kmsClientBuilder; + Builder(AWSKMSClientBuilder awsKmsClientBuilder) { + this.awsKmsClientBuilder = awsKmsClientBuilder; } - public KmsClientSupplier build() { + public AwsKmsClientSupplier build() { isTrue(allowedRegions.isEmpty() || excludedRegions.isEmpty(), "Either allowed regions or excluded regions may be set, not both."); @@ -104,18 +124,18 @@ public KmsClientSupplier build() { } if (credentialsProvider != null) { - kmsClientBuilder = kmsClientBuilder.withCredentials(credentialsProvider); + awsKmsClientBuilder = awsKmsClientBuilder.withCredentials(credentialsProvider); } if (clientConfiguration != null) { - kmsClientBuilder = kmsClientBuilder.withClientConfiguration(clientConfiguration); + awsKmsClientBuilder = awsKmsClientBuilder.withClientConfiguration(clientConfiguration); } if (regionId != null) { - kmsClientBuilder = kmsClientBuilder.withRegion(regionId); + awsKmsClientBuilder = awsKmsClientBuilder.withRegion(regionId); } - AWSKMS client = kmsClientBuilder.build(); + AWSKMS client = awsKmsClientBuilder.build(); if (clientCachingEnabled) { client = newCachingProxy(client, regionId); @@ -168,7 +188,8 @@ public Builder excludedRegions(Set regions) { } /** - * When set to true, allows for the AWSKMS client for each region to be cached and reused. + * When set to false, disables the AWSKMS client for each region from being cached and reused. + * By default, client caching is enabled. * * @param enabled Whether or not caching is enabled. */ @@ -179,7 +200,7 @@ public Builder clientCaching(boolean enabled) { /** * Creates a proxy for the AWSKMS client that will populate the client into the client cache - * after a KMS method successfully completes or a KMS exception occurs. This is to prevent a + * after an AWS KMS method successfully completes or an AWS KMS exception occurs. This is to prevent a * a malicious user from causing a local resource DOS by sending ciphertext with a large number * of spurious regions, thereby filling the cache with regions and exhausting resources. * @@ -194,13 +215,13 @@ private AWSKMS newCachingProxy(AWSKMS client, String regionId) { (proxy, method, methodArgs) -> { try { final Object result = method.invoke(client, methodArgs); - if (KMS_METHODS.contains(method.getName())) { + if (AWSKMS_METHODS.contains(method.getName())) { clientsCache.put(regionId, client); } return result; } catch (InvocationTargetException e) { if (e.getTargetException() instanceof AWSKMSException && - KMS_METHODS.contains(method.getName())) { + AWSKMS_METHODS.contains(method.getName())) { clientsCache.put(regionId, client); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java new file mode 100644 index 000000000..200308514 --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkId.java @@ -0,0 +1,138 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.arn.Arn; +import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; + +import java.util.Objects; + +/** + * A representation of an AWS KMS Customer Master Key Identifier, which may be one either a + * key ID, key Amazon Resource Name (ARN), alias name, or alias ARN. + */ +public final class AwsKmsCmkId { + + private static final String ARN_PREFIX = "arn:"; + private String keyId; + + private AwsKmsCmkId(String keyId) throws MalformedArnException { + Validate.notBlank(keyId, "keyId must be neither null, empty nor whitespace"); + + if (keyId.startsWith(ARN_PREFIX)) { + try { + Arn.fromString(keyId); + } catch (IllegalArgumentException e) { + throw new MalformedArnException(e); + } + } + + this.keyId = keyId; + } + + /** + *

+ * Constructs an {@code AwsKmsCmkId} from the given String id. + *

+ *

+ * Valid identifiers must be either a key ID, key Amazon Resource Name (ARN), alias name, or alias ARN. When using + * an alias name, prefix it with "alias/". To specify a CMK in a different AWS account, you must use the key ARN or + * alias ARN. When using decryption operations, you must use the key ARN. + *

+ *

+ * For example: + *

+ *
    + *
  • + *

    + * Key ID: 1234abcd-12ab-34cd-56ef-1234567890ab + *

    + *
  • + *
  • + *

    + * Key ARN: arn:aws:kms:us-east-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab + *

    + *
  • + *
  • + *

    + * Alias name: alias/ExampleAlias + *

    + *
  • + *
  • + *

    + * Alias ARN: arn:aws:kms:us-east-2:111122223333:alias/ExampleAlias + *

    + *
  • + *
+ * + * @param keyId The key ID, key Amazon Resource Name (ARN), alias name, or alias ARN + * @return The {@code AwsKmsCmkId} + * @throws MalformedArnException if the given keyId is an ARN (starts with 'arn:') and cannot be parsed + */ + public static AwsKmsCmkId fromString(String keyId) throws MalformedArnException { + return new AwsKmsCmkId(keyId); + } + + /** + * Returns true if the given keyId is a well formed Amazon Resource Name or is a Key Alias or raw Key Id. + * + * @param keyId The key ID, key Amazon Resource Name (ARN), alias name, or alias ARN + * @return True if well formed, false otherwise + */ + public static boolean isKeyIdWellFormed(String keyId) { + if (StringUtils.isBlank(keyId)) { + return false; + } + + if (!keyId.startsWith(ARN_PREFIX)) { + return true; + } + + try { + Arn.fromString(keyId); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + /** + * Returns true if this AwsKmsCmkId is in the Amazon Resource Name (ARN) format. + * + * @return True if in the ARN format, false otherwise + */ + public boolean isArn() { + return keyId.startsWith(ARN_PREFIX); + } + + @Override + public String toString() { + return keyId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AwsKmsCmkId that = (AwsKmsCmkId) o; + return keyId.equals(that.keyId); + } + + @Override + public int hashCode() { + return Objects.hash(keyId); + } +} diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java similarity index 84% rename from src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java rename to src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java index a00a1f1c7..cefbf97f3 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDao.java @@ -36,8 +36,8 @@ import java.util.Map; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.getClientByArn; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier.getClientByKeyId; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.Validate.isTrue; @@ -46,12 +46,12 @@ * generation, encryption, and decryption of data keys. The KmsMethods interface is implemented * to allow usage in KmsMasterKey. */ -class KmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { +class AwsKmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { - private final KmsClientSupplier clientSupplier; + private final AwsKmsClientSupplier clientSupplier; private List grantTokens; - KmsDataKeyEncryptionDao(KmsClientSupplier clientSupplier, List grantTokens) { + AwsKmsDataKeyEncryptionDao(AwsKmsClientSupplier clientSupplier, List grantTokens) { requireNonNull(clientSupplier, "clientSupplier is required"); this.clientSupplier = clientSupplier; @@ -59,7 +59,7 @@ class KmsDataKeyEncryptionDao implements DataKeyEncryptionDao, KmsMethods { } @Override - public GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext) { + public GenerateDataKeyResult generateDataKey(AwsKmsCmkId keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext) { requireNonNull(keyId, "keyId is required"); requireNonNull(algorithmSuite, "algorithmSuite is required"); requireNonNull(encryptionContext, "encryptionContext is required"); @@ -67,10 +67,10 @@ public GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algor final com.amazonaws.services.kms.model.GenerateDataKeyResult kmsResult; try { - kmsResult = getClientByArn(keyId, clientSupplier) + kmsResult = getClientByKeyId(keyId, clientSupplier) .generateDataKey(updateUserAgent( new GenerateDataKeyRequest() - .withKeyId(keyId) + .withKeyId(keyId.toString()) .withNumberOfBytes(algorithmSuite.getDataKeyLength()) .withEncryptionContext(encryptionContext) .withGrantTokens(grantTokens))); @@ -87,11 +87,11 @@ public GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algor kmsResult.getCiphertextBlob().get(encryptedKey); return new GenerateDataKeyResult(new SecretKeySpec(rawKey, algorithmSuite.getDataKeyAlgo()), - new KeyBlob(KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedKey)); + new KeyBlob(AWS_KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedKey)); } @Override - public EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDataKey, Map encryptionContext) { + public EncryptedDataKey encryptDataKey(final AwsKmsCmkId keyId, SecretKey plaintextDataKey, Map encryptionContext) { requireNonNull(keyId, "keyId is required"); requireNonNull(plaintextDataKey, "plaintextDataKey is required"); requireNonNull(encryptionContext, "encryptionContext is required"); @@ -100,9 +100,9 @@ public EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDa final com.amazonaws.services.kms.model.EncryptResult kmsResult; try { - kmsResult = getClientByArn(keyId, clientSupplier) + kmsResult = getClientByKeyId(keyId, clientSupplier) .encrypt(updateUserAgent(new EncryptRequest() - .withKeyId(keyId) + .withKeyId(keyId.toString()) .withPlaintext(ByteBuffer.wrap(plaintextDataKey.getEncoded())) .withEncryptionContext(encryptionContext) .withGrantTokens(grantTokens))); @@ -112,7 +112,7 @@ public EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDa final byte[] encryptedDataKey = new byte[kmsResult.getCiphertextBlob().remaining()]; kmsResult.getCiphertextBlob().get(encryptedDataKey); - return new KeyBlob(KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedDataKey); + return new KeyBlob(AWS_KMS_PROVIDER_ID, kmsResult.getKeyId().getBytes(PROVIDER_ENCODING), encryptedDataKey); } @@ -126,7 +126,7 @@ public DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, Cr final com.amazonaws.services.kms.model.DecryptResult kmsResult; try { - kmsResult = getClientByArn(providerInformation, clientSupplier) + kmsResult = getClientByKeyId(AwsKmsCmkId.fromString(providerInformation), clientSupplier) .decrypt(updateUserAgent(new DecryptRequest() .withCiphertextBlob(ByteBuffer.wrap(encryptedDataKey.getEncryptedDataKey())) .withEncryptionContext(encryptionContext) diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java b/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java index 4267ba6f8..a68227b2b 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java @@ -31,7 +31,7 @@ public interface DataKeyEncryptionDao { * @param encryptionContext The encryption context. * @return GenerateDataKeyResult containing the plaintext data key and the encrypted data key. */ - GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext); + GenerateDataKeyResult generateDataKey(AwsKmsCmkId keyId, CryptoAlgorithm algorithmSuite, Map encryptionContext); /** * Encrypts the given plaintext data key using the customer aster key specified by the given keyId. @@ -41,7 +41,7 @@ public interface DataKeyEncryptionDao { * @param encryptionContext The encryption context. * @return The encrypted data key. */ - EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDataKey, Map encryptionContext); + EncryptedDataKey encryptDataKey(final AwsKmsCmkId keyId, SecretKey plaintextDataKey, Map encryptionContext); /** * Decrypted the given encrypted data key. @@ -61,8 +61,8 @@ public interface DataKeyEncryptionDao { * @param grantTokens A list of grant tokens to supply to KMS * @return The DataKeyEncryptionDao */ - static DataKeyEncryptionDao kms(KmsClientSupplier clientSupplier, List grantTokens) { - return new KmsDataKeyEncryptionDao(clientSupplier, grantTokens); + static DataKeyEncryptionDao awsKms(AwsKmsClientSupplier clientSupplier, List grantTokens) { + return new AwsKmsDataKeyEncryptionDao(clientSupplier, grantTokens); } class GenerateDataKeyResult { diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java index 60c69445c..7cea5cd58 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKey.java @@ -30,6 +30,7 @@ import com.amazonaws.encryptionsdk.MasterKeyProvider; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.services.kms.AWSKMS; import static java.util.Collections.emptyList; @@ -37,9 +38,12 @@ /** * Represents a single Customer Master Key (CMK) and is used to encrypt/decrypt data with * {@link AwsCrypto}. + * + * @deprecated Replaced by {@code KmsKeyring}. See {@link StandardKeyrings}. */ +@Deprecated public final class KmsMasterKey extends MasterKey implements KmsMethods { - private final KmsDataKeyEncryptionDao dataKeyEncryptionDao_; + private final AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao_; private final MasterKeyProvider sourceProvider_; private final String id_; @@ -63,10 +67,10 @@ public static KmsMasterKey getInstance(final AWSCredentialsProvider creds, final static KmsMasterKey getInstance(final Supplier kms, final String id, final MasterKeyProvider provider) { - return new KmsMasterKey(new KmsDataKeyEncryptionDao(s -> kms.get(), emptyList()), id, provider); + return new KmsMasterKey(new AwsKmsDataKeyEncryptionDao(s -> kms.get(), emptyList()), id, provider); } - KmsMasterKey(final KmsDataKeyEncryptionDao dataKeyEncryptionDao, final String id, final MasterKeyProvider provider) { + KmsMasterKey(final AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao, final String id, final MasterKeyProvider provider) { dataKeyEncryptionDao_ = dataKeyEncryptionDao; id_ = id; sourceProvider_ = provider; @@ -86,7 +90,7 @@ public String getKeyId() { public DataKey generateDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext) { final DataKeyEncryptionDao.GenerateDataKeyResult gdkResult = dataKeyEncryptionDao_.generateDataKey( - getKeyId(), algorithm, encryptionContext); + AwsKmsCmkId.fromString(getKeyId()), algorithm, encryptionContext); return new DataKey<>(gdkResult.getPlaintextDataKey(), gdkResult.getEncryptedDataKey().getEncryptedDataKey(), gdkResult.getEncryptedDataKey().getProviderInformation(), @@ -113,7 +117,8 @@ public DataKey encryptDataKey(final CryptoAlgorithm algorithm, final Map encryptionContext, final DataKey dataKey) { final SecretKey key = dataKey.getKey(); - final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao_.encryptDataKey(id_, key, encryptionContext); + final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao_.encryptDataKey( + AwsKmsCmkId.fromString(id_), key, encryptionContext); return new DataKey<>(dataKey.getKey(), encryptedDataKey.getEncryptedDataKey(), diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java index 9f0d91b42..36e48a635 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyProvider.java @@ -43,6 +43,7 @@ import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.NoSuchMasterKeyException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.handlers.RequestHandler2; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; @@ -54,7 +55,10 @@ /** * Provides {@link MasterKey}s backed by the AWS Key Management Service. This object is regional and * if you want to use keys from multiple regions, you'll need multiple copies of this object. + * + * @deprecated Replaced by {@code KmsKeyring}. See {@link StandardKeyrings}. */ +@Deprecated public class KmsMasterKeyProvider extends MasterKeyProvider implements KmsMethods { private static final String PROVIDER_NAME = "aws-kms"; private final List keyIds_; diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java b/src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java deleted file mode 100644 index f6aab16eb..000000000 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/KmsUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.kms; - -import com.amazonaws.arn.Arn; -import com.amazonaws.encryptionsdk.exception.MalformedArnException; -import com.amazonaws.services.kms.AWSKMS; - -public class KmsUtils { - - private static final String ALIAS_PREFIX = "alias/"; - private static final String ARN_PREFIX = "arn:"; - /** - * The provider ID used for the KmsKeyring - */ - public static final String KMS_PROVIDER_ID = "aws-kms"; - - /** - * Parses region from the given arn (if possible) and passes that region to the - * given clientSupplier to produce an {@code AWSKMS} client. - * - * @param arn The Amazon Resource Name or Key Alias - * @param clientSupplier The client supplier - * @return AWSKMS The client - * @throws MalformedArnException if the arn is malformed - */ - public static AWSKMS getClientByArn(String arn, KmsClientSupplier clientSupplier) throws MalformedArnException { - if (isKeyAlias(arn)) { - return clientSupplier.getClient(null); - } - - if(isArn(arn)) { - try { - return clientSupplier.getClient(Arn.fromString(arn).getRegion()); - } catch (IllegalArgumentException e) { - throw new MalformedArnException(e); - } - } - - // Not an alias or an ARN, must be a raw Key ID - return clientSupplier.getClient(null); - } - - /** - * Returns true if the given arn is a well formed Amazon Resource Name or Key Alias. Does - * not return true for raw key IDs. - * - * @param arn The Amazon Resource Name or Key Alias - * @return True if well formed, false otherwise - */ - public static boolean isArnWellFormed(String arn) { - if (isKeyAlias(arn)) { - return true; - } - - try { - Arn.fromString(arn); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - private static boolean isKeyAlias(String arn) { - return arn.startsWith(ALIAS_PREFIX); - } - - private static boolean isArn(String arn) { - return arn.startsWith(ARN_PREFIX); - } -} diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java index 722246237..0ad194cc2 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/DecryptionMaterials.java @@ -19,7 +19,7 @@ public final class DecryptionMaterials { private final CryptoAlgorithm algorithm; private final Map encryptionContext; - private DataKey dataKey; + private final DataKey dataKey; private final PublicKey trailingSignatureKey; private final KeyringTrace keyringTrace; @@ -54,20 +54,25 @@ public DataKey getDataKey() { } /** - * Sets the cleartext data key. The cleartext data key must not already be populated. + * Creates a new {@code DecryptionMaterials} instance based on this instance with the addition of the + * provided cleartext data key and keyring trace entry. The cleartext data key must not already be populated. * * @param cleartextDataKey The cleartext data key. * @param keyringTraceEntry The keyring trace entry recording this action. + * @return The new {@code DecryptionMaterials} instance. */ - public void setCleartextDataKey(SecretKey cleartextDataKey, KeyringTraceEntry keyringTraceEntry) { + public DecryptionMaterials withCleartextDataKey(SecretKey cleartextDataKey, KeyringTraceEntry keyringTraceEntry) { if (hasCleartextDataKey()) { throw new IllegalStateException("cleartextDataKey was already populated"); } requireNonNull(cleartextDataKey, "cleartextDataKey is required"); requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); validateCleartextDataKey(algorithm, cleartextDataKey); - this.dataKey = new DataKey<>(cleartextDataKey, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, null); - keyringTrace.add(keyringTraceEntry); + + return toBuilder() + .setCleartextDataKey(cleartextDataKey) + .setKeyringTrace(keyringTrace.with(keyringTraceEntry)) + .build(); } public SecretKey getCleartextDataKey() { @@ -133,14 +138,14 @@ public static final class Builder { private Map encryptionContext = Collections.emptyMap(); private DataKey dataKey; private PublicKey trailingSignatureKey; - private KeyringTrace keyringTrace = new KeyringTrace(); + private KeyringTrace keyringTrace = KeyringTrace.EMPTY_TRACE; private Builder(DecryptionMaterials result) { this.algorithm = result.getAlgorithm(); this.encryptionContext = result.getEncryptionContext(); this.dataKey = result.getDataKey(); this.trailingSignatureKey = result.getTrailingSignatureKey(); - this.keyringTrace = result.keyringTrace; + this.keyringTrace = result.getKeyringTrace(); } private Builder() {} diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java index ed46dabf8..215ed4012 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/EncryptionMaterials.java @@ -16,6 +16,7 @@ import java.util.Objects; import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; import static org.apache.commons.lang3.Validate.isTrue; @@ -28,7 +29,7 @@ public final class EncryptionMaterials { private final CryptoAlgorithm algorithm; private final Map encryptionContext; private final List encryptedDataKeys; - private SecretKey cleartextDataKey; + private final SecretKey cleartextDataKey; private final PrivateKey trailingSignatureKey; private final List masterKeys; private final KeyringTrace keyringTrace; @@ -70,20 +71,28 @@ public Map getEncryptionContext() { * The KeyBlobs to serialize (in cleartext) into the encrypted message. */ public List getEncryptedDataKeys() { - return unmodifiableList(encryptedDataKeys); + return encryptedDataKeys; } /** - * Add an encrypted data key to the list of encrypted data keys. + * Creates a new {@code EncryptionMaterials} instance based on this instance with the addition of the + * provided encrypted data key and keyring trace entry. * * @param encryptedDataKey The encrypted data key to add. * @param keyringTraceEntry The keyring trace entry recording this action. + * @return The new {@code EncryptionMaterials} instance. */ - public void addEncryptedDataKey(KeyBlob encryptedDataKey, KeyringTraceEntry keyringTraceEntry) { + public EncryptionMaterials withEncryptedDataKey(KeyBlob encryptedDataKey, KeyringTraceEntry keyringTraceEntry) { requireNonNull(encryptedDataKey, "encryptedDataKey is required"); requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); + + final List encryptedDataKeys = new ArrayList<>(getEncryptedDataKeys()); encryptedDataKeys.add(encryptedDataKey); - keyringTrace.add(keyringTraceEntry); + + return toBuilder() + .setEncryptedDataKeys(encryptedDataKeys) + .setKeyringTrace(keyringTrace.with(keyringTraceEntry)) + .build(); } /** @@ -95,20 +104,25 @@ public SecretKey getCleartextDataKey() { } /** - * Sets the cleartext data key. The cleartext data key must not already be populated. + * Creates a new {@code EncryptionMaterials} instance based on this instance with the addition of the + * provided cleartext data key and keyring trace entry. The cleartext data key must not already be populated. * * @param cleartextDataKey The cleartext data key. * @param keyringTraceEntry The keyring trace entry recording this action. + * @return The new {@code EncryptionMaterials} instance. */ - public void setCleartextDataKey(SecretKey cleartextDataKey, KeyringTraceEntry keyringTraceEntry) { + public EncryptionMaterials withCleartextDataKey(SecretKey cleartextDataKey, KeyringTraceEntry keyringTraceEntry) { if (hasCleartextDataKey()) { throw new IllegalStateException("cleartextDataKey was already populated"); } requireNonNull(cleartextDataKey, "cleartextDataKey is required"); requireNonNull(keyringTraceEntry, "keyringTraceEntry is required"); validateCleartextDataKey(algorithm, cleartextDataKey); - this.cleartextDataKey = cleartextDataKey; - keyringTrace.add(keyringTraceEntry); + + return toBuilder() + .setCleartextDataKey(cleartextDataKey) + .setKeyringTrace(keyringTrace.with(keyringTraceEntry)) + .build(); } /** @@ -185,11 +199,11 @@ private void validateCleartextDataKey(CryptoAlgorithm algorithmSuite, SecretKey public static class Builder { private CryptoAlgorithm algorithm; private Map encryptionContext = Collections.emptyMap(); - private List encryptedDataKeys = new ArrayList<>(); + private List encryptedDataKeys = Collections.emptyList(); private SecretKey cleartextDataKey; private PrivateKey trailingSignatureKey; private List masterKeys = Collections.emptyList(); - private KeyringTrace keyringTrace = new KeyringTrace(); + private KeyringTrace keyringTrace = KeyringTrace.EMPTY_TRACE; private Builder() {} @@ -221,7 +235,7 @@ public Map getEncryptionContext() { } public Builder setEncryptionContext(Map encryptionContext) { - this.encryptionContext = Collections.unmodifiableMap(new HashMap<>(encryptionContext)); + this.encryptionContext = unmodifiableMap(new HashMap<>(encryptionContext)); return this; } @@ -230,7 +244,7 @@ public List getEncryptedDataKeys() { } public Builder setEncryptedDataKeys(List encryptedDataKeys) { - this.encryptedDataKeys = new ArrayList<>(encryptedDataKeys); + this.encryptedDataKeys = unmodifiableList(new ArrayList<>(encryptedDataKeys)); return this; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/multi/MultipleProviderFactory.java b/src/main/java/com/amazonaws/encryptionsdk/multi/MultipleProviderFactory.java index 00b723d47..b1a65f803 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/multi/MultipleProviderFactory.java +++ b/src/main/java/com/amazonaws/encryptionsdk/multi/MultipleProviderFactory.java @@ -29,6 +29,7 @@ import com.amazonaws.encryptionsdk.exception.NoSuchMasterKeyException; import com.amazonaws.encryptionsdk.exception.UnsupportedProviderException; import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; /** * Constructs {@link MasterKeyProvider}s which are backed by any number of other @@ -46,7 +47,10 @@ * * All methods in this factory return identical results and exist only for different degrees of * type-safety. + * + * @deprecated Replaced by {@code MultiKeyring}. See {@link StandardKeyrings}. */ +@Deprecated public class MultipleProviderFactory { private MultipleProviderFactory() { // Prevent instantiation diff --git a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java index 5d162679d..95405fa2b 100644 --- a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java @@ -13,13 +13,17 @@ package com.amazonaws.crypto.examples; +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.Test; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; -public class BasicEncryptionExampleTest { +@Tag(TestUtils.TAG_INTEGRATION) +class BasicEncryptionExampleTest { @Test - public void testEncryptAndDecrypt() { - BasicEncryptionExample.encryptAndDecrypt(KMSTestFixtures.TEST_KEY_IDS[0]); + void testEncryptAndDecrypt() { + BasicEncryptionExample.encryptAndDecrypt(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); } } diff --git a/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java new file mode 100644 index 000000000..6e3cf5137 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import java.security.GeneralSecurityException; + +@Tag(TestUtils.TAG_INTEGRATION) +class EscrowedEncryptExampleTest { + + @Test + void testEncryptAndDecrypt() throws GeneralSecurityException { + EscrowedEncryptExample.escrowEncryptAndDecrypt(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java new file mode 100644 index 000000000..28b3f8f66 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +class FileStreamingExampleTest { + + @Test + void testEncryptAndDecrypt() throws IOException { + final File tempFile = File.createTempFile("FileStreamingExampleTest-TempTestData", ".tmp"); + final File encryptedFile = new File(tempFile.getPath() + ".encrypted"); + final File decryptedFile = new File(tempFile.getPath() + ".decrypted"); + tempFile.deleteOnExit(); + encryptedFile.deleteOnExit(); + decryptedFile.deleteOnExit(); + + try (BufferedWriter writer = Files.newBufferedWriter(tempFile.toPath())) { + for (int i = 0; i < 1000 ; i++) { + writer.write(RandomStringUtils.randomAlphanumeric(100)); + writer.newLine(); + } + } + + FileStreamingExample.encryptAndDecrypt(tempFile, encryptedFile, decryptedFile); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java new file mode 100644 index 000000000..fa4d2906b --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import org.junit.jupiter.api.Test; + +class RawAesKeyringExampleTest { + + @Test + void testEncryptAndDecrypt() { + RawAesKeyringExample.encryptAndDecrypt(); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java new file mode 100644 index 000000000..ca4c165f1 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import org.junit.jupiter.api.Test; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class RawRsaKeyringDecryptExampleTest { + + @Test + void testDecrypt() throws Exception { + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + byte[] ciphertext = RawRsaKeyringEncryptExample.encrypt(keyPair.getPublic()); + byte[] decryptedResult = RawRsaKeyringDecryptExample.decrypt(ciphertext, keyPair); + + assertArrayEquals(RawRsaKeyringEncryptExample.EXAMPLE_DATA, decryptedResult); + } + +} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java new file mode 100644 index 000000000..25a8bddf2 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import org.junit.jupiter.api.Test; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class RawRsaKeyringEncryptExampleTest { + + @Test + void testEncrypt() throws Exception { + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + byte[] ciphertext = RawRsaKeyringEncryptExample.encrypt(keyPair.getPublic()); + + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + .keyNamespace("ExampleKeyNamespace") + .keyName("ExampleKeyName") + .privateKey(keyPair.getPrivate()) + .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .build(); + + + final AwsCrypto awsCrypto = new AwsCrypto(); + byte[] decryptedResult = awsCrypto.decrypt(DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext) + .build()).getResult(); + + assertArrayEquals(RawRsaKeyringEncryptExample.EXAMPLE_DATA, decryptedResult); + } + +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java b/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java deleted file mode 100644 index f16681a60..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/AllTestsSuite.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.amazonaws.encryptionsdk; - -import com.amazonaws.encryptionsdk.jce.JceMasterKeyTest; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -import com.amazonaws.encryptionsdk.caching.CacheIdentifierTests; -import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManagerTest; -import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCacheTest; -import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCacheThreadStormTest; -import com.amazonaws.encryptionsdk.caching.NullCryptoMaterialsCacheTest; -import com.amazonaws.encryptionsdk.internal.BlockDecryptionHandlerTest; -import com.amazonaws.encryptionsdk.internal.BlockEncryptionHandlerTest; -import com.amazonaws.encryptionsdk.internal.CipherHandlerTest; -import com.amazonaws.encryptionsdk.internal.DecryptionHandlerTest; -import com.amazonaws.encryptionsdk.internal.EncContextSerializerTest; -import com.amazonaws.encryptionsdk.internal.EncryptionHandlerTest; -import com.amazonaws.encryptionsdk.internal.FrameDecryptionHandlerTest; -import com.amazonaws.encryptionsdk.internal.FrameEncryptionHandlerTest; -import com.amazonaws.encryptionsdk.internal.PrimitivesParserTest; -import com.amazonaws.encryptionsdk.jce.KeyStoreProviderTest; -import com.amazonaws.encryptionsdk.model.CipherBlockHeadersTest; -import com.amazonaws.encryptionsdk.model.CipherFrameHeadersTest; -import com.amazonaws.encryptionsdk.model.KeyBlobTest; -import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequestTest; -import com.amazonaws.encryptionsdk.multi.MultipleMasterKeyTest; -import com.amazonaws.encryptionsdk.kms.KMSProviderBuilderMockTests; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ - BlockDecryptionHandlerTest.class, - BlockEncryptionHandlerTest.class, - CipherHandlerTest.class, - DecryptionHandlerTest.class, - EncContextSerializerTest.class, - EncryptionHandlerTest.class, - FrameDecryptionHandlerTest.class, - FrameEncryptionHandlerTest.class, - PrimitivesParserTest.class, - KeyStoreProviderTest.class, - CipherBlockHeadersTest.class, - CipherFrameHeadersTest.class, - KeyBlobTest.class, - DecryptionMaterialsRequestTest.class, - MultipleMasterKeyTest.class, - AwsCryptoTest.class, - CryptoInputStreamTest.class, - CryptoOutputStreamTest.class, - TestVectorRunner.class, - XCompatDecryptTest.class, - DefaultCryptoMaterialsManagerTest.class, - NullCryptoMaterialsCacheTest.class, - CacheIdentifierTests.class, - CachingCryptoMaterialsManagerTest.class, - LocalCryptoMaterialsCacheTest.class, - LocalCryptoMaterialsCacheThreadStormTest.class, - UtilsTest.class, - MultipleMasterKeyTest.class, - KMSProviderBuilderMockTests.class, - JceMasterKeyTest.class -}) -public class AllTestsSuite { -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java b/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java index 70fe91baf..6e404827d 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/AwsCryptoTest.java @@ -13,8 +13,9 @@ package com.amazonaws.encryptionsdk; -import static com.amazonaws.encryptionsdk.FastTestsOnlySuite.isFastTestSuiteActive; +import static com.amazonaws.encryptionsdk.TestUtils.assertNullChecks; import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; +import static com.amazonaws.encryptionsdk.TestUtils.isFastTestsOnly; import static java.util.Collections.singletonMap; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -37,7 +38,8 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import org.junit.Assert; +import com.amazonaws.encryptionsdk.internal.TestKeyring; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import org.junit.Before; import org.junit.Test; @@ -56,11 +58,13 @@ public class AwsCryptoTest { private StaticMasterKey masterKeyProvider; + private Keyring keyring; private AwsCrypto encryptionClient_; @Before public void init() { masterKeyProvider = spy(new StaticMasterKey("testmaterial")); + keyring = spy(new TestKeyring("testmaterial")); encryptionClient_ = new AwsCrypto(); encryptionClient_.setEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256); @@ -87,6 +91,26 @@ private void doEncryptDecrypt(final CryptoAlgorithm cryptoAlg, final int byteSiz assertArrayEquals("Bad encrypt/decrypt for " + cryptoAlg, plaintextBytes, decryptedText); } + private void doEncryptDecryptWithKeyring(final CryptoAlgorithm cryptoAlg, final int byteSize, final int frameSize) { + final byte[] plaintextBytes = new byte[byteSize]; + + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC1", "Encrypt-decrypt-keyring test with %d" + byteSize); + + encryptionClient_.setEncryptionAlgorithm(cryptoAlg); + encryptionClient_.setEncryptionFrameSize(frameSize); + + final byte[] cipherText = encryptionClient_.encrypt(request -> request + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(plaintextBytes)).getResult(); + final byte[] decryptedText = encryptionClient_.decrypt(request -> request + .keyring(keyring) + .ciphertext(cipherText)).getResult(); + + assertArrayEquals("Bad encrypt/decrypt for " + cryptoAlg, plaintextBytes, decryptedText); + } + private void doTamperedEncryptDecrypt(final CryptoAlgorithm cryptoAlg, final int byteSize, final int frameSize) { final byte[] plaintextBytes = new byte[byteSize]; @@ -101,15 +125,29 @@ private void doTamperedEncryptDecrypt(final CryptoAlgorithm cryptoAlg, final int plaintextBytes, encryptionContext).getResult(); cipherText[cipherText.length - 2] ^= (byte) 0xff; - try { - encryptionClient_.decryptData( - masterKeyProvider, - cipherText - ).getResult(); - Assert.fail("Expected BadCiphertextException"); - } catch (final BadCiphertextException ex) { - // Expected exception - } + + assertThrows(BadCiphertextException.class, () -> encryptionClient_.decryptData( + masterKeyProvider, + cipherText)); + } + + private void doTamperedEncryptDecryptWithKeyring(final CryptoAlgorithm cryptoAlg, final int byteSize, final int frameSize) { + final byte[] plaintextBytes = new byte[byteSize]; + + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC1", "Encrypt-decrypt-keyring test with %d" + byteSize); + + encryptionClient_.setEncryptionAlgorithm(cryptoAlg); + encryptionClient_.setEncryptionFrameSize(frameSize); + + final byte[] cipherText = encryptionClient_.encrypt(EncryptRequest.builder() + .keyring(keyring) + .plaintext(plaintextBytes).build()).getResult(); + cipherText[cipherText.length - 2] ^= (byte) 0xff; + + assertThrows(BadCiphertextException.class, () -> encryptionClient_.decrypt(DecryptRequest.builder() + .keyring(keyring) + .ciphertext(cipherText).build())); } private void doEncryptDecryptWithParsedCiphertext(final int byteSize, final int frameSize) { @@ -155,12 +193,13 @@ public void encryptDecrypt() { for (int j = 0; j < bytesToTest.length; j++) { final int byteSize = bytesToTest[j]; - if (byteSize > 500_000 && isFastTestSuiteActive()) { + if (byteSize > 500_000 && isFastTestsOnly()) { continue; } if (byteSize >= 0) { doEncryptDecrypt(cryptoAlg, byteSize, frameSize); + doEncryptDecryptWithKeyring(cryptoAlg, byteSize, frameSize); } } } @@ -183,12 +222,13 @@ public void encryptDecryptWithBadSignature() { for (int j = 0; j < bytesToTest.length; j++) { final int byteSize = bytesToTest[j]; - if (byteSize > 500_000 && isFastTestSuiteActive()) { + if (byteSize > 500_000 && isFastTestsOnly()) { continue; } if (byteSize >= 0) { doTamperedEncryptDecrypt(cryptoAlg, byteSize, frameSize); + doTamperedEncryptDecryptWithKeyring(cryptoAlg, byteSize, frameSize); } } } @@ -208,7 +248,7 @@ public void encryptDecryptWithParsedCiphertext() { for (int j = 0; j < bytesToTest.length; j++) { final int byteSize = bytesToTest[j]; - if (byteSize > 500_000 && isFastTestSuiteActive()) { + if (byteSize > 500_000 && isFastTestsOnly()) { continue; } @@ -255,6 +295,42 @@ public void encryptDecryptWithCustomManager() throws Exception { assertTrue(didDecrypt[0]); } + @Test + public void encryptDecryptWithCustomManagerWithKeyring() { + boolean[] didDecrypt = new boolean[] { false }; + + CryptoMaterialsManager manager = new CryptoMaterialsManager() { + @Override public EncryptionMaterials getMaterialsForEncrypt( + EncryptionMaterialsRequest request + ) { + request = request.toBuilder().setContext(singletonMap("foo", "bar")).build(); + + return new DefaultCryptoMaterialsManager(keyring).getMaterialsForEncrypt(request); + } + + @Override public DecryptionMaterials decryptMaterials( + DecryptionMaterialsRequest request + ) { + didDecrypt[0] = true; + return new DefaultCryptoMaterialsManager(keyring).decryptMaterials(request); + } + }; + + byte[] plaintext = new byte[100]; + + AwsCryptoResult ciphertext = encryptionClient_.encrypt(EncryptRequest.builder() + .cryptoMaterialsManager(manager) + .plaintext(plaintext).build()); + assertEquals("bar", ciphertext.getEncryptionContext().get("foo")); + + assertFalse(didDecrypt[0]); + AwsCryptoResult plaintextResult = encryptionClient_.decrypt(DecryptRequest.builder() + .cryptoMaterialsManager(manager) + .ciphertext(ciphertext.getResult()).build()); + assertArrayEquals(plaintext, plaintextResult.getResult()); + assertTrue(didDecrypt[0]); + } + @Test public void whenCustomCMMIgnoresAlgorithm_throws() throws Exception { boolean[] didDecrypt = new boolean[] { false }; @@ -304,6 +380,21 @@ public void whenDecrypting_invokesMKPOnce() throws Exception { verify(masterKeyProvider, times(1)).decryptDataKey(any(), any(), any()); } + @Test + public void whenDecrypting_invokesOnDecryptOnce() throws Exception { + byte[] data = encryptionClient_.encrypt(EncryptRequest.builder() + .keyring(keyring) + .plaintext(new byte[1]).build()).getResult(); + + reset(keyring); + + encryptionClient_.decrypt(DecryptRequest.builder() + .keyring(keyring) + .ciphertext(data).build()); + + verify(keyring, times(1)).onDecrypt(any(), any()); + } + private void doEstimateCiphertextSize(final CryptoAlgorithm cryptoAlg, final int inLen, final int frameSize) { final byte[] plaintext = TestIOUtils.generateRandomPlaintext(inLen); @@ -327,6 +418,32 @@ private void doEstimateCiphertextSize(final CryptoAlgorithm cryptoAlg, final int assertTrue(errMsg, estimatedCiphertextSize - cipherText.length <= 16); } + private void doEstimateCiphertextSizeWithKeyring(final CryptoAlgorithm cryptoAlg, final int inLen, final int frameSize) { + final byte[] plaintext = TestIOUtils.generateRandomPlaintext(inLen); + + final Map encryptionContext = new HashMap<>(1); + encryptionContext.put("ENC1", "Ciphertext size estimation test with " + inLen); + + encryptionClient_.setEncryptionAlgorithm(cryptoAlg); + encryptionClient_.setEncryptionFrameSize(frameSize); + + final long estimatedCiphertextSize = encryptionClient_.estimateCiphertextSize(EstimateCiphertextSizeRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintextSize(inLen) + .build()); + final byte[] cipherText = encryptionClient_.encrypt(EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(plaintext).build()).getResult(); + + // The estimate should be close (within 16 bytes) and never less than reality + final String errMsg = "Bad estimation for " + cryptoAlg + " expected: <" + estimatedCiphertextSize + + "> but was: <" + cipherText.length + ">"; + assertTrue(errMsg, estimatedCiphertextSize - cipherText.length >= 0); + assertTrue(errMsg, estimatedCiphertextSize - cipherText.length <= 16); + } + @Test public void estimateCiphertextSize() { for (final CryptoAlgorithm cryptoAlg : EnumSet.allOf(CryptoAlgorithm.class)) { @@ -340,12 +457,13 @@ public void estimateCiphertextSize() { for (int j = 0; j < bytesToTest.length; j++) { final int byteSize = bytesToTest[j]; - if (byteSize > 500_000 && isFastTestSuiteActive()) { + if (byteSize > 500_000 && isFastTestsOnly()) { continue; } if (byteSize >= 0) { doEstimateCiphertextSize(cryptoAlg, byteSize, frameSize); + doEstimateCiphertextSizeWithKeyring(cryptoAlg, byteSize, frameSize); } } } @@ -495,7 +613,7 @@ public void emptyEncryptionContext() { // Test that all the parameters that aren't allowed to be null (i.e. all of them) result in immediate NPEs if // invoked with null args @Test - public void assertNullChecks() throws Exception { + public void assertNullValidation() throws Exception { byte[] buf = new byte[1]; HashMap context = new HashMap<>(); MasterKeyProvider provider = masterKeyProvider; @@ -506,144 +624,177 @@ public void assertNullChecks() throws Exception { byte[] ciphertext = encryptionClient_.encryptData(cmm, buf).getResult(); String stringCiphertext = encryptionClient_.encryptString(cmm, "hello, world").getResult(); - TestUtils.assertNullChecks(encryptionClient_, "estimateCiphertextSize", + assertNullChecks(encryptionClient_, "estimateCiphertextSize", MasterKeyProvider.class, provider, Integer.TYPE, 42, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "estimateCiphertextSize", + assertNullChecks(encryptionClient_, "estimateCiphertextSize", CryptoMaterialsManager.class, cmm, Integer.TYPE, 42, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "estimateCiphertextSize", + assertNullChecks(encryptionClient_, "estimateCiphertextSize", MasterKeyProvider.class, provider, Integer.TYPE, 42 ); - TestUtils.assertNullChecks(encryptionClient_, "estimateCiphertextSize", + assertNullChecks(encryptionClient_, "estimateCiphertextSize", CryptoMaterialsManager.class, cmm, Integer.TYPE, 42 ); - - TestUtils.assertNullChecks(encryptionClient_, "encryptData", + assertNullChecks(encryptionClient_, "estimateCiphertextSize", + EstimateCiphertextSizeRequest.class, EstimateCiphertextSizeRequest.builder() + .cryptoMaterialsManager(cmm) + .plaintextSize(42).build() + ); + assertNullChecks(encryptionClient_, "encryptData", MasterKeyProvider.class, provider, byte[].class, buf, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "encryptData", + assertNullChecks(encryptionClient_, "encryptData", CryptoMaterialsManager.class, cmm, byte[].class, buf, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "encryptData", + assertNullChecks(encryptionClient_, "encryptData", MasterKeyProvider.class, provider, byte[].class, buf ); - TestUtils.assertNullChecks(encryptionClient_, "encryptData", + assertNullChecks(encryptionClient_, "encryptData", CryptoMaterialsManager.class, cmm, byte[].class, buf ); - TestUtils.assertNullChecks(encryptionClient_, "encryptString", + assertNullChecks(encryptionClient_, "encrypt", + EncryptRequest.class, EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .plaintext(buf).build() + ); + assertNullChecks(encryptionClient_, "encryptString", MasterKeyProvider.class, provider, String.class, "", Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "encryptString", + assertNullChecks(encryptionClient_, "encryptString", CryptoMaterialsManager.class, cmm, String.class, "", Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "encryptString", + assertNullChecks(encryptionClient_, "encryptString", MasterKeyProvider.class, provider, String.class, "" ); - TestUtils.assertNullChecks(encryptionClient_, "encryptString", + assertNullChecks(encryptionClient_, "encryptString", CryptoMaterialsManager.class, cmm, String.class, "" ); - TestUtils.assertNullChecks(encryptionClient_, "decryptData", + assertNullChecks(encryptionClient_, "decryptData", MasterKeyProvider.class, provider, byte[].class, ciphertext ); - TestUtils.assertNullChecks(encryptionClient_, "decryptData", + assertNullChecks(encryptionClient_, "decryptData", CryptoMaterialsManager.class, cmm, byte[].class, ciphertext ); - TestUtils.assertNullChecks(encryptionClient_, "decryptData", + assertNullChecks(encryptionClient_, "decryptData", MasterKeyProvider.class, provider, ParsedCiphertext.class, new ParsedCiphertext(ciphertext) ); - TestUtils.assertNullChecks(encryptionClient_, "decryptData", + assertNullChecks(encryptionClient_, "decryptData", CryptoMaterialsManager.class, cmm, ParsedCiphertext.class, new ParsedCiphertext(ciphertext) ); - TestUtils.assertNullChecks(encryptionClient_, "decryptString", + assertNullChecks(encryptionClient_, "decrypt", + DecryptRequest.class, DecryptRequest.builder() + .cryptoMaterialsManager(cmm) + .ciphertext(ciphertext).build() + ); + assertNullChecks(encryptionClient_, "decryptString", MasterKeyProvider.class, provider, String.class, stringCiphertext ); - TestUtils.assertNullChecks(encryptionClient_, "decryptString", + assertNullChecks(encryptionClient_, "decryptString", CryptoMaterialsManager.class, cmm, String.class, stringCiphertext ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", MasterKeyProvider.class, provider, OutputStream.class, os, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", CryptoMaterialsManager.class, cmm, OutputStream.class, os, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", MasterKeyProvider.class, provider, OutputStream.class, os ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", CryptoMaterialsManager.class, cmm, OutputStream.class, os ); - - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingOutputStream", + CreateEncryptingOutputStreamRequest.class, CreateEncryptingOutputStreamRequest.builder() + .cryptoMaterialsManager(cmm) + .outputStream(os).build() + ); + assertNullChecks(encryptionClient_, "createEncryptingStream", MasterKeyProvider.class, provider, InputStream.class, is, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", CryptoMaterialsManager.class, cmm, InputStream.class, is, Map.class, context ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", MasterKeyProvider.class, provider, InputStream.class, is ); - TestUtils.assertNullChecks(encryptionClient_, "createEncryptingStream", + assertNullChecks(encryptionClient_, "createEncryptingStream", CryptoMaterialsManager.class, cmm, InputStream.class, is ); + assertNullChecks(encryptionClient_, "createEncryptingInputStream", + CreateEncryptingInputStreamRequest.class, CreateEncryptingInputStreamRequest.builder() + .cryptoMaterialsManager(cmm) + .inputStream(is).build() + ); - TestUtils.assertNullChecks(encryptionClient_, "createDecryptingStream", + assertNullChecks(encryptionClient_, "createDecryptingStream", MasterKeyProvider.class, provider, OutputStream.class, os ); - TestUtils.assertNullChecks(encryptionClient_, "createDecryptingStream", + assertNullChecks(encryptionClient_, "createDecryptingStream", CryptoMaterialsManager.class, cmm, OutputStream.class, os ); - TestUtils.assertNullChecks(encryptionClient_, "createDecryptingStream", + assertNullChecks(encryptionClient_, "createDecryptingStream", MasterKeyProvider.class, provider, InputStream.class, is ); - TestUtils.assertNullChecks(encryptionClient_, "createDecryptingStream", + assertNullChecks(encryptionClient_, "createDecryptingStream", CryptoMaterialsManager.class, cmm, InputStream.class, is ); + assertNullChecks(encryptionClient_, "createDecryptingInputStream", + CreateDecryptingInputStreamRequest.class, CreateDecryptingInputStreamRequest.builder() + .cryptoMaterialsManager(cmm) + .inputStream(is).build() + ); + assertNullChecks(encryptionClient_, "createDecryptingOutputStream", + CreateDecryptingOutputStreamRequest.class, CreateDecryptingOutputStreamRequest.builder() + .cryptoMaterialsManager(cmm) + .outputStream(os).build() + ); } @Test diff --git a/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java b/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java index 6762475e6..7bca94cd9 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/CryptoInputStreamTest.java @@ -15,6 +15,7 @@ import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; import static com.amazonaws.encryptionsdk.TestUtils.insecureRandomBytes; +import static com.amazonaws.encryptionsdk.TestUtils.isFastTestsOnly; import static com.amazonaws.encryptionsdk.internal.TestIOUtils.getSha256Hash; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -163,7 +164,7 @@ public static Collection encryptDecryptParams() { // Our bytesToTest and readLenVals arrays tend to have the bigger numbers towards the end - we'll chop off // the last few as they take the longest and don't really add that much more coverage. int skipLastNSizes; - if (!FastTestsOnlySuite.isFastTestSuiteActive()) { + if (!isFastTestsOnly()) { skipLastNSizes = 0; } else if (firstAlgorithm) { // We'll run more tests for the first algorithm in the list - but not go quite so far as running the diff --git a/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java b/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java index 558dc88b6..f31a2849a 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/CryptoOutputStreamTest.java @@ -14,9 +14,9 @@ package com.amazonaws.encryptionsdk; import static com.amazonaws.encryptionsdk.AwsCrypto.getDefaultFrameSize; -import static com.amazonaws.encryptionsdk.FastTestsOnlySuite.isFastTestSuiteActive; import static com.amazonaws.encryptionsdk.TestUtils.assertThrows; import static com.amazonaws.encryptionsdk.TestUtils.insecureRandomBytes; +import static com.amazonaws.encryptionsdk.TestUtils.isFastTestsOnly; import static com.amazonaws.encryptionsdk.TestUtils.toByteArray; import static com.amazonaws.encryptionsdk.internal.TestIOUtils.getSha256Hash; import static org.junit.Assert.assertArrayEquals; @@ -163,7 +163,7 @@ public static Collection encryptDecryptParams() { int[] bytesToTest = { 0, 1, frameSize - 1, frameSize, frameSize + 1, (int) (frameSize * 1.5), frameSize * 2, 1000000 }; - if (isFastTestSuiteActive()) { + if (isFastTestsOnly()) { // Exclude the last two sizes, as they're the slowest bytesToTest = Arrays.copyOfRange(bytesToTest, 0, bytesToTest.length - 2); } @@ -173,7 +173,7 @@ public static Collection encryptDecryptParams() { final int byteSize = bytesToTest[j]; int[] readLenVals = { byteSize - 1, byteSize, byteSize + 1, byteSize * 2, 1000000 }; - if (isFastTestSuiteActive()) { + if (isFastTestsOnly()) { // Only test one read() call buffer length in the fast tests. This greatly cuts down on // the combinatorial explosion of test cases here. readLenVals = Arrays.copyOfRange(readLenVals, 0, 1); diff --git a/src/test/java/com/amazonaws/encryptionsdk/DecryptRequestTest.java b/src/test/java/com/amazonaws/encryptionsdk/DecryptRequestTest.java new file mode 100644 index 000000000..da83deb61 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/DecryptRequestTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.internal.TestKeyring; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +class DecryptRequestTest { + + @Mock private Keyring keyring; + @Mock private ParsedCiphertext parsedCiphertext; + private static final byte[] CIPHERTEXT = new byte[]{1, 2, 3}; + + @Test + void testBothCiphertextAndParsedCiphertext() { + + assertThrows(IllegalArgumentException.class, () -> DecryptRequest.builder() + .keyring(keyring) + .ciphertext(CIPHERTEXT) + .parsedCiphertext(parsedCiphertext) + .build()); + } + + @Test + void testNeitherCiphertextOrParsedCiphertext() { + + assertThrows(IllegalArgumentException.class, () -> DecryptRequest.builder() + .keyring(keyring) + .build()); + } + + @Test + void testKeyringUsesDefaultCmm() { + + byte[] ciphertext = new AwsCrypto().encrypt(EncryptRequest.builder() + .keyring(new TestKeyring("keyId")) + .plaintext(new byte[]{4, 5, 6}) + .build()).getResult(); + + final CryptoMaterialsManager cryptoMaterialsManager = DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build() + .cryptoMaterialsManager(); + + assertTrue(cryptoMaterialsManager instanceof DefaultCryptoMaterialsManager); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManagerTest.java b/src/test/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManagerTest.java index 9f693fe28..00fcfa063 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManagerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/DefaultCryptoMaterialsManagerTest.java @@ -2,12 +2,11 @@ import static com.amazonaws.encryptionsdk.multi.MultipleProviderFactory.buildMultiProvider; import static java.util.Collections.singletonMap; -import static org.hamcrest.Matchers.hasEntry; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.same; @@ -15,6 +14,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; import java.security.Signature; @@ -26,6 +26,8 @@ import java.util.Objects; import java.util.function.Consumer; +import com.amazonaws.encryptionsdk.internal.TestKeyring; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import org.junit.Test; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; @@ -43,9 +45,10 @@ public class DefaultCryptoMaterialsManagerTest { private static final MasterKey mk1 = new StaticMasterKey("mk1"); private static final MasterKey mk2 = new StaticMasterKey("mk2"); + private static final Keyring keyring1 = new TestKeyring("keyring1"); @Test - public void encrypt_testBasicFunctionality() throws Exception { + public void encrypt_testBasicFunctionalityWithMkp() { EncryptionMaterialsRequest req = EncryptionMaterialsRequest.newBuilder().build(); EncryptionMaterials result = new DefaultCryptoMaterialsManager(mk1).getMaterialsForEncrypt(req); @@ -58,7 +61,20 @@ public void encrypt_testBasicFunctionality() throws Exception { } @Test - public void encrypt_noSignatureKeyOnUnsignedAlgo() throws Exception { + public void encrypt_testBasicFunctionalityWithKeyring() { + EncryptionMaterialsRequest req = EncryptionMaterialsRequest.newBuilder().build(); + EncryptionMaterials result = new DefaultCryptoMaterialsManager(keyring1).getMaterialsForEncrypt(req); + + assertNotNull(result.getAlgorithm()); + assertNotNull(result.getCleartextDataKey()); + assertNotNull(result.getEncryptionContext()); + assertNotNull(result.getKeyringTrace()); + assertEquals(1, result.getEncryptedDataKeys().size()); + assertEquals(0, result.getMasterKeys().size()); + } + + @Test + public void encrypt_noSignatureKeyOnUnsignedAlgoWithMkp() { CryptoAlgorithm[] algorithms = new CryptoAlgorithm[] { CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256, CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_NO_KDF, @@ -80,7 +96,29 @@ public void encrypt_noSignatureKeyOnUnsignedAlgo() throws Exception { } @Test - public void encrypt_hasSignatureKeyForSignedAlgo() throws Exception { + public void encrypt_noSignatureKeyOnUnsignedAlgoWithKeyring() { + CryptoAlgorithm[] algorithms = new CryptoAlgorithm[] { + CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256, + CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_NO_KDF, + CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256, + CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_NO_KDF, + CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256, + CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF + }; + + for (CryptoAlgorithm algo : algorithms) { + EncryptionMaterialsRequest req + = EncryptionMaterialsRequest.newBuilder().setRequestedAlgorithm(algo).build(); + EncryptionMaterials result = new DefaultCryptoMaterialsManager(keyring1).getMaterialsForEncrypt(req); + + assertNull(result.getTrailingSignatureKey()); + assertEquals(0, result.getEncryptionContext().size()); + assertEquals(algo, result.getAlgorithm()); + } + } + + @Test + public void encrypt_hasSignatureKeyForSignedAlgoWithMkp() { CryptoAlgorithm[] algorithms = new CryptoAlgorithm[] { CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256, CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, @@ -93,6 +131,28 @@ public void encrypt_hasSignatureKeyForSignedAlgo() throws Exception { = EncryptionMaterialsRequest.newBuilder().setRequestedAlgorithm(algo).build(); EncryptionMaterials result = new DefaultCryptoMaterialsManager(mk1).getMaterialsForEncrypt(req); + + assertNotNull(result.getTrailingSignatureKey()); + assertEquals(1, result.getEncryptionContext().size()); + assertNotNull(result.getEncryptionContext().get(Constants.EC_PUBLIC_KEY_FIELD)); + assertEquals(algo, result.getAlgorithm()); + } + } + + @Test + public void encrypt_hasSignatureKeyForSignedAlgoWithKeyring() { + CryptoAlgorithm[] algorithms = new CryptoAlgorithm[] { + CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256, + CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, + CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384 + }; + + for (CryptoAlgorithm algo : algorithms) { + + EncryptionMaterialsRequest req + = EncryptionMaterialsRequest.newBuilder().setRequestedAlgorithm(algo).build(); + EncryptionMaterials result = new DefaultCryptoMaterialsManager(keyring1).getMaterialsForEncrypt(req); + assertNotNull(result.getTrailingSignatureKey()); assertEquals(1, result.getEncryptionContext().size()); assertNotNull(result.getEncryptionContext().get(Constants.EC_PUBLIC_KEY_FIELD)); @@ -101,7 +161,7 @@ public void encrypt_hasSignatureKeyForSignedAlgo() throws Exception { } @Test - public void encrypt_dispatchesMultipleMasterKeys() throws Exception { + public void encrypt_dispatchesMultipleMasterKeys() { MasterKey mk1_spy = spy(mk1); MasterKey mk2_spy = spy(mk2); @@ -145,7 +205,7 @@ public void encrypt_dispatchesMultipleMasterKeys() throws Exception { } @Test - public void encrypt_forwardsPlaintextWhenAvailable() throws Exception { + public void encrypt_forwardsPlaintextWhenAvailable() { MasterKey mk1_spy = spy(mk1); EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder() @@ -161,7 +221,7 @@ public void encrypt_forwardsPlaintextWhenAvailable() throws Exception { } @Test - public void encrypt_forwardsPlaintextSizeWhenAvailable() throws Exception { + public void encrypt_forwardsPlaintextSizeWhenAvailable() { MasterKey mk1_spy = spy(mk1); EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder() @@ -177,7 +237,7 @@ public void encrypt_forwardsPlaintextSizeWhenAvailable() throws Exception { } @Test - public void encrypt_setsStreamingWhenNoSizeAvailable() throws Exception { + public void encrypt_setsStreamingWhenNoSizeAvailable() { MasterKey mk1_spy = spy(mk1); EncryptionMaterialsRequest request = EncryptionMaterialsRequest.newBuilder().build(); @@ -189,7 +249,7 @@ public void encrypt_setsStreamingWhenNoSizeAvailable() throws Exception { } @Test(expected = IllegalArgumentException.class) - public void encrypt_whenECContextKeyPresent_throws() throws Exception { + public void encrypt_whenECContextKeyPresent_throws() { EncryptionMaterialsRequest req = EncryptionMaterialsRequest.newBuilder() .setRequestedAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384) .setContext(singletonMap(Constants.EC_PUBLIC_KEY_FIELD, "some EC key")) @@ -199,7 +259,7 @@ public void encrypt_whenECContextKeyPresent_throws() throws Exception { } @Test(expected = IllegalArgumentException.class) - public void encrypt_whenNoMasterKeys_throws() throws Exception { + public void encrypt_whenNoMasterKeys_throws() { EncryptionMaterialsRequest req = EncryptionMaterialsRequest.newBuilder().build(); new DefaultCryptoMaterialsManager(new MasterKeyProvider() { @@ -218,7 +278,7 @@ public void encrypt_whenNoMasterKeys_throws() throws Exception { @Override public DataKey decryptDataKey( CryptoAlgorithm algorithm, Collection encryptedDataKeys, Map encryptionContext - ) throws UnsupportedProviderException, AwsCryptoException { + ) throws AwsCryptoException { return null; } }).getMaterialsForEncrypt(req); @@ -232,6 +292,14 @@ private EncryptionMaterials easyGenMaterials(Consumer customizer) { + EncryptionMaterialsRequest.Builder request = EncryptionMaterialsRequest.newBuilder(); + + customizer.accept(request); + + return new DefaultCryptoMaterialsManager(keyring1).getMaterialsForEncrypt(request.build()); + } + private DecryptionMaterialsRequest decryptReqFromMaterials(EncryptionMaterials result) { return DecryptionMaterialsRequest.newBuilder() .setEncryptionContext(result.getEncryptionContext()) @@ -275,15 +343,62 @@ public void decrypt_testSimpleRoundTrip() throws Exception { } } + @Test + public void decrypt_testSimpleRoundTripWithKeyring() throws Exception { + for (CryptoAlgorithm algorithm : CryptoAlgorithm.values()) { + EncryptionMaterials encryptMaterials = easyGenMaterialsForKeyring( + builder -> builder.setRequestedAlgorithm(algorithm) + ); + + DecryptionMaterials decryptMaterials + = new DefaultCryptoMaterialsManager(keyring1).decryptMaterials(decryptReqFromMaterials(encryptMaterials)); + + assertArrayEquals(decryptMaterials.getDataKey().getKey().getEncoded(), + encryptMaterials.getCleartextDataKey().getEncoded()); + + if (encryptMaterials.getTrailingSignatureKey() == null) { + assertNull(decryptMaterials.getTrailingSignatureKey()); + } else { + Signature sig = Signature.getInstance( + TrailingSignatureAlgorithm.forCryptoAlgorithm(algorithm).getHashAndSignAlgorithm() + ); + + sig.initSign(encryptMaterials.getTrailingSignatureKey()); + + byte[] data = "hello world".getBytes(StandardCharsets.UTF_8); + + sig.update(data); + byte[] signature = sig.sign(); + + sig.initVerify(decryptMaterials.getTrailingSignatureKey()); + + sig.update(data); + sig.verify(signature); + } + } + } + @Test(expected = CannotUnwrapDataKeyException.class) - public void decrypt_onDecryptFailure() throws Exception { + public void decrypt_onDecryptFailure() { new DefaultCryptoMaterialsManager(mock(MasterKeyProvider.class)).decryptMaterials( decryptReqFromMaterials(easyGenMaterials(ignored -> {})) ); } + @Test(expected = CannotUnwrapDataKeyException.class) + public void decrypt_onDecryptFailureForKeyring() { + Keyring keyring = mock(Keyring.class); + DecryptionMaterials decryptionMaterials = mock(DecryptionMaterials.class); + when(keyring.onDecrypt(any(), any())).thenReturn(decryptionMaterials); + when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false); + + new DefaultCryptoMaterialsManager(keyring).decryptMaterials( + decryptReqFromMaterials(easyGenMaterials(ignored -> {})) + ); + } + @Test - public void decrypt_whenTrailingSigMissing_throwsException() throws Exception { + public void decrypt_whenTrailingSigMissing_throwsException() { for (CryptoAlgorithm algorithm : CryptoAlgorithm.values()) { if (algorithm.getTrailingSignatureLength() == 0) { continue; @@ -299,13 +414,30 @@ public void decrypt_whenTrailingSigMissing_throwsException() throws Exception { .setEncryptionContext(Collections.emptyMap()) .build(); - try { - new DefaultCryptoMaterialsManager(mk1).decryptMaterials(request); - fail("expected exception"); - } catch (AwsCryptoException e) { - // ok + assertThrows(AwsCryptoException.class, () -> + new DefaultCryptoMaterialsManager(mk1).decryptMaterials(request)); + } + } + + @Test + public void decrypt_whenTrailingSigMissingForKeyring_throwsException() { + for (CryptoAlgorithm algorithm : CryptoAlgorithm.values()) { + if (algorithm.getTrailingSignatureLength() == 0) { continue; } + + EncryptionMaterials encryptMaterials = easyGenMaterialsForKeyring( + builder -> builder.setRequestedAlgorithm(algorithm) + ); + + DecryptionMaterialsRequest request = DecryptionMaterialsRequest.newBuilder() + .setEncryptedDataKeys(encryptMaterials.getEncryptedDataKeys()) + .setAlgorithm(algorithm) + .setEncryptionContext(Collections.emptyMap()) + .build(); + + assertThrows(AwsCryptoException.class, () -> + new DefaultCryptoMaterialsManager(keyring1).decryptMaterials(request)); } } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/EncryptRequestTest.java b/src/test/java/com/amazonaws/encryptionsdk/EncryptRequestTest.java new file mode 100644 index 000000000..564b017e1 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/EncryptRequestTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk; + +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +class EncryptRequestTest { + + @Mock private Keyring keyring; + @Mock private CryptoMaterialsManager cmm; + private static final byte[] PLAINTEXT = new byte[]{1, 2, 3}; + + @Test + void testBothCmmAndKeyring() { + + assertThrows(IllegalArgumentException.class, () -> EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .keyring(keyring) + .plaintext(PLAINTEXT) + .build()); + } + + @Test + void testNeitherCmmOrKeyring() { + + assertThrows(IllegalArgumentException.class, () -> EncryptRequest.builder() + .plaintext(PLAINTEXT) + .build()); + } + + @Test + void testKeyringUsesDefaultCmm() { + + assertTrue(EncryptRequest.builder() + .keyring(keyring) + .plaintext(PLAINTEXT).build().cryptoMaterialsManager() + instanceof DefaultCryptoMaterialsManager); + } + + @Test + void testNoEncryptionContext() { + + assertEquals(0, EncryptRequest.builder() + .plaintext(PLAINTEXT) + .keyring(keyring).build().encryptionContext().size()); + } + + @Test + void testNullPlaintext() { + assertThrows(NullPointerException.class, () -> EncryptRequest.builder() + .keyring(keyring).build()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/FastTestsOnlySuite.java b/src/test/java/com/amazonaws/encryptionsdk/FastTestsOnlySuite.java deleted file mode 100644 index 528ae273e..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/FastTestsOnlySuite.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.amazonaws.encryptionsdk; - -import java.util.concurrent.TimeUnit; - -import org.junit.ClassRule; -import org.junit.experimental.categories.Categories; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.junit.runner.Runner; -import org.junit.runners.Suite; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.RunnerBuilder; -import org.junit.runners.model.Statement; - -/** - * This test suite is intended to assist in rapid development; it filters out some of the slower, more exhaustive tests - * in the overall test suite to allow for a rapid edit-test cycle. - */ -@RunWith(FastTestsOnlySuite.CustomRunner.class) -@Suite.SuiteClasses({ - AllTestsSuite.class -}) -@Categories.ExcludeCategory(SlowTestCategory.class) -public class FastTestsOnlySuite { - private static InheritableThreadLocal IS_FAST_TEST_SUITE_ACTIVE = new InheritableThreadLocal() { - @Override protected Boolean initialValue() { - return false; - } - }; - - // This method is used to adjust DataProviders to provide a smaller subset of their test cases when the fast tests - // are selected - public static boolean isFastTestSuiteActive() { - return IS_FAST_TEST_SUITE_ACTIVE.get(); - } - - // Require that this fast suite completes relatively quickly. If you're seeing this timeout get hit, it's time to - // pare down tests some more. As a general rule of thumb, we should avoid any single test taking more than 10s, and - // try to keep the number of such slow tests to a minimum. - @ClassRule - public static Timeout timeout = new Timeout(2, TimeUnit.MINUTES); - - @ClassRule - public static EnableFastSuite enableFastSuite = new EnableFastSuite(); - - // TestRules run over the execution of tests, but not over the generation of parameterized test data... - private static class EnableFastSuite implements TestRule { - @Override public Statement apply( - Statement base, Description description - ) { - return new Statement() { - @Override public void evaluate() throws Throwable { - Boolean oldValue = IS_FAST_TEST_SUITE_ACTIVE.get(); - - try { - IS_FAST_TEST_SUITE_ACTIVE.set(true); - base.evaluate(); - } finally { - IS_FAST_TEST_SUITE_ACTIVE.set(oldValue); - } - } - }; - } - } - - // ... so we also need a custom TestRunner that will pass the flag on to the parameterized test data generators. - public static class CustomRunner extends Categories { - public CustomRunner(Class klass, RunnerBuilder builder) throws InitializationError { - super( - klass, - new RunnerBuilder() { - @Override public Runner runnerForClass(Class testClass) throws Throwable { - Boolean oldValue = IS_FAST_TEST_SUITE_ACTIVE.get(); - - try { - IS_FAST_TEST_SUITE_ACTIVE.set(true); - Runner r = builder.runnerForClass(testClass); - return r; - } finally { - IS_FAST_TEST_SUITE_ACTIVE.set(oldValue); - } - } - } - ); - } - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/IntegrationTestSuite.java b/src/test/java/com/amazonaws/encryptionsdk/IntegrationTestSuite.java deleted file mode 100644 index 44628b0db..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/IntegrationTestSuite.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.amazonaws.encryptionsdk; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -import com.amazonaws.encryptionsdk.kms.KMSProviderBuilderIntegrationTests; -import com.amazonaws.encryptionsdk.kms.XCompatKmsDecryptTest; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ - XCompatKmsDecryptTest.class, - KMSProviderBuilderIntegrationTests.class -}) -public class IntegrationTestSuite { -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/SlowTestCategory.java b/src/test/java/com/amazonaws/encryptionsdk/SlowTestCategory.java deleted file mode 100644 index 9814b0948..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/SlowTestCategory.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.amazonaws.encryptionsdk; - -/** - * JUnit category marking tests to be excluded from the FastTestsOnlySuite. Usage: - * - * @Category(SlowTestCategory.class) - * @Test - * public void mySlowTest() { - * // encrypt a couple terabytes of test data - * } - * - * - */ -public interface SlowTestCategory {} diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestUtils.java b/src/test/java/com/amazonaws/encryptionsdk/TestUtils.java index 6763798f9..047189918 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestUtils.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestUtils.java @@ -34,6 +34,16 @@ private static byte[] ensureRandomCached(int length) { }); } + /** + * JUnit tag name marking tests or test classes to be excluded from normal test profiles + * and only run in an ad hoc manner. + */ + public static final String TAG_AD_HOC = "ad_hoc"; + /** + * JUnit tag name marking integration tests + */ + public static final String TAG_INTEGRATION = "integration"; + @FunctionalInterface public interface ThrowingRunnable { void run() throws Throwable; @@ -211,4 +221,8 @@ public static int[] signedBytesToUnsignedBytes(final byte[] signedBytes) { return unsignedBytes; } + + public static boolean isFastTestsOnly() { + return Boolean.parseBoolean(System.getProperty("fastTestsOnly")); + } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java index 14019c2e2..8a8e8048e 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java @@ -1,25 +1,40 @@ -package com.amazonaws.encryptionsdk; +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ -import static java.lang.String.format; +package com.amazonaws.encryptionsdk; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; import com.amazonaws.util.IOUtils; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.bouncycastle.util.encoders.Base64; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.net.JarURLConnection; import java.net.URL; import java.security.GeneralSecurityException; @@ -38,32 +53,40 @@ import java.util.jar.JarFile; import java.util.zip.ZipEntry; -@RunWith(Parameterized.class) -public class TestVectorRunner { - // We save the files in memory to avoid repeatedly retrieving them. This won't work if the plaintexts are too - // large or numerous +import static java.lang.String.format; +import static java.util.Collections.emptyList; +import static org.apache.commons.lang3.Validate.isTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +@Tag(TestUtils.TAG_INTEGRATION) +class TestVectorRunner { + // We save the files in memory to avoid repeatedly retrieving them. + // This won't work if the plaintexts are too large or numerous private static final Map cachedData = new HashMap<>(); - - private final String testName; - private final TestCase testCase; - - public TestVectorRunner(final String testName, TestCase testCase) { - this.testName = testName; - this.testCase = testCase; - } - - @Test - public void decrypt() { + private static final AwsKmsClientSupplier awsKmsClientSupplier = AwsKmsClientSupplier.builder() + .credentialsProvider(new DefaultAWSCredentialsProviderChain()) + .build(); + private static final KmsMasterKeyProvider kmsProv = KmsMasterKeyProvider + .builder() + .withCustomClientFactory(awsKmsClientSupplier::getClient) + .build(); + + @ParameterizedTest(name = "Compatibility Test: {0}") + @MethodSource("data") + void decrypt(TestCase testCase) { AwsCrypto crypto = new AwsCrypto(); - byte[] plaintext = crypto.decryptData(testCase.mkp, cachedData.get(testCase.ciphertextPath)).getResult(); + byte[] keyringPlaintext = crypto.decrypt(DecryptRequest.builder() + .ciphertext(cachedData.get(testCase.ciphertextPath)) + .keyring(testCase.keyring).build()).getResult(); + byte[] mkpPlaintext = crypto.decryptData(testCase.mkp, cachedData.get(testCase.ciphertextPath)).getResult(); final byte[] expectedPlaintext = cachedData.get(testCase.plaintextPath); - Assert.assertArrayEquals(expectedPlaintext, plaintext); + assertArrayEquals(expectedPlaintext, keyringPlaintext); + assertArrayEquals(expectedPlaintext, mkpPlaintext); } - @Parameterized.Parameters(name="Compatibility Test: {0}") @SuppressWarnings("unchecked") - public static Collection data() throws Exception { + static Collection data() throws Exception { final String zipPath = System.getProperty("testVectorZip"); if (zipPath == null) { return Collections.emptyList(); @@ -73,43 +96,33 @@ public static Collection data() throws Exception { try (JarFile jar = jarConnection.getJarFile()) { final Map manifest = readJsonMapFromJar(jar, "manifest.json"); - final Map metaData = (Map) manifest.get("manifest"); // We only support "awses-decrypt" type manifests right now - if (!"awses-decrypt".equals(metaData.get("type"))) { - throw new IllegalArgumentException("Unsupported manifest type: " + metaData.get("type")); - } - - if (!Integer.valueOf(1).equals(metaData.get("version"))) { - throw new IllegalArgumentException("Unsupported manifest version: " + metaData.get("version")); - } + isTrue("awses-decrypt".equals(metaData.get("type")), "Unsupported manifest type: %s", metaData.get("type")); + isTrue(Integer.valueOf(1).equals(metaData.get("version")), "Unsupported manifest version: %s", metaData.get("version")); final Map keys = parseKeyManifest(readJsonMapFromJar(jar, (String) manifest.get("keys"))); - final KmsMasterKeyProvider kmsProv = KmsMasterKeyProvider - .builder() - .withCredentials(new DefaultAWSCredentialsProviderChain()) - .build(); + final List testCases = new ArrayList<>(); + + ((Map>) manifest.get("tests")).forEach( + (testName, data) -> testCases.add(parseTest(testName, data, keys, jar))); - List testCases = new ArrayList<>(); - for (Map.Entry> testEntry : - ((Map>) manifest.get("tests")).entrySet()) { - testCases.add(new Object[]{testEntry.getKey(), - parseTest(testEntry.getKey(), testEntry.getValue(), keys, jar, kmsProv)}); - } return testCases; } } - @AfterClass - public static void teardown() { + @AfterAll + static void teardown() { cachedData.clear(); } - private static byte[] readBytesFromJar(JarFile jar, String fileName) throws IOException { + private static byte[] readBytesFromJar(JarFile jar, String fileName) { try (InputStream is = readFromJar(jar, fileName)) { return IOUtils.toByteArray(is); + } catch (IOException ex) { + throw new UncheckedIOException(ex); } } @@ -126,21 +139,15 @@ private static InputStream readFromJar(JarFile jar, String name) throws IOExcept return jar.getInputStream(entry); } - private static void cacheData(JarFile jar, String url) throws IOException { - if (!cachedData.containsKey(url)) { - cachedData.put(url, readBytesFromJar(jar, url)); - } - } - @SuppressWarnings("unchecked") private static TestCase parseTest(String testName, Map data, Map keys, - JarFile jar, KmsMasterKeyProvider kmsProv) throws IOException { + JarFile jar) { final String plaintextUrl = (String) data.get("plaintext"); - cacheData(jar, plaintextUrl); final String ciphertextURL = (String) data.get("ciphertext"); - cacheData(jar, ciphertextURL); + cachedData.computeIfAbsent(plaintextUrl, k -> readBytesFromJar(jar, k)); + cachedData.computeIfAbsent(ciphertextURL, k -> readBytesFromJar(jar, k)); - @SuppressWarnings("generic") + final List keyrings = new ArrayList<>(); final List> mks = new ArrayList<>(); for (Map mkEntry : (List>) data.get("master-keys")) { @@ -149,11 +156,19 @@ private static TestCase parseTest(String testName, Map data, Map final KeyEntry key = keys.get(keyName); if ("aws-kms".equals(type)) { + keyrings.add(StandardKeyrings.awsKmsBuilder() + .awsKmsClientSupplier(awsKmsClientSupplier) + .generatorKeyId(AwsKmsCmkId.fromString(key.keyId)) + .build()); mks.add(kmsProv.getMasterKey(key.keyId)); } else if ("raw".equals(type)) { final String provId = mkEntry.get("provider-id"); final String algorithm = mkEntry.get("encryption-algorithm"); if ("aes".equals(algorithm)) { + keyrings.add(StandardKeyrings.rawAesBuilder() + .keyName(key.keyId) + .keyNamespace(provId) + .wrappingKey((SecretKey) key.key).build()); mks.add(JceMasterKey.getInstance((SecretKey) key.key, provId, key.keyId, "AES/GCM/NoPadding")); } else if ("rsa".equals(algorithm)) { String transformation = "RSA/ECB/"; @@ -162,21 +177,27 @@ private static TestCase parseTest(String testName, Map data, Map transformation += "PKCS1Padding"; } else if ("oaep-mgf1".equals(padding)) { final String hashName = mkEntry.get("padding-hash") - .replace("sha", "sha-") - .toUpperCase(); + .replace("sha", "sha-") + .toUpperCase(); transformation += "OAEPWith" + hashName + "AndMGF1Padding"; } else { throw new IllegalArgumentException("Unsupported padding:" + padding); } final PublicKey wrappingKey; final PrivateKey unwrappingKey; - if (key.key instanceof PublicKey) { + if (key.key instanceof PublicKey) { wrappingKey = (PublicKey) key.key; unwrappingKey = null; } else { wrappingKey = null; unwrappingKey = (PrivateKey) key.key; } + keyrings.add(StandardKeyrings.rawRsaBuilder() + .publicKey(wrappingKey) + .privateKey(unwrappingKey) + .keyNamespace(provId) + .keyName(key.keyId) + .wrappingAlgorithm(transformation).build()); mks.add(JceMasterKey.getInstance(wrappingKey, unwrappingKey, provId, key.keyId, transformation)); } else { throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); @@ -186,11 +207,11 @@ private static TestCase parseTest(String testName, Map data, Map } } - return new TestCase(testName, ciphertextURL, plaintextUrl, mks); + return new TestCase(testName, ciphertextURL, plaintextUrl, keyrings, mks); } @SuppressWarnings("unchecked") - private static Map parseKeyManifest(final Map keysManifest) throws GeneralSecurityException { + static Map parseKeyManifest(final Map keysManifest) throws GeneralSecurityException { // check our type final Map metaData = (Map) keysManifest.get("manifest"); if (!"keys".equals(metaData.get("type"))) { @@ -221,8 +242,7 @@ private static Map parseKeyManifest(final Map if (!"base64".equals(encoding)) { throw new IllegalArgumentException(format("Key %s is symmetric but has encoding %s", keyId, encoding)); } - keyEntry = new KeyEntry(name, keyId, keyType, - new SecretKeySpec(Base64.decode(material), algorithm.toUpperCase())); + keyEntry = new KeyEntry(keyId, new SecretKeySpec(Base64.decode(material), algorithm.toUpperCase())); break; case "private": kf = KeyFactory.getInstance(algorithm); @@ -230,8 +250,7 @@ private static Map parseKeyManifest(final Map throw new IllegalArgumentException(format("Key %s is private but has encoding %s", keyId, encoding)); } byte[] pkcs8Key = parsePem(material); - keyEntry = new KeyEntry(name, keyId, keyType, - kf.generatePrivate(new PKCS8EncodedKeySpec(pkcs8Key))); + keyEntry = new KeyEntry(keyId, kf.generatePrivate(new PKCS8EncodedKeySpec(pkcs8Key))); break; case "public": kf = KeyFactory.getInstance(algorithm); @@ -239,11 +258,10 @@ private static Map parseKeyManifest(final Map throw new IllegalArgumentException(format("Key %s is private but has encoding %s", keyId, encoding)); } byte[] x509Key = parsePem(material); - keyEntry = new KeyEntry(name, keyId, keyType, - kf.generatePublic(new X509EncodedKeySpec(x509Key))); + keyEntry = new KeyEntry(keyId, kf.generatePublic(new X509EncodedKeySpec(x509Key))); break; case "aws-kms": - keyEntry = new KeyEntry(name, keyId, keyType, null); + keyEntry = new KeyEntry(keyId, null); break; default: throw new IllegalArgumentException("Unsupported key type: " + keyType); @@ -261,15 +279,11 @@ private static byte[] parsePem(String pem) { } private static class KeyEntry { - final String name; final String keyId; - final String type; final Key key; - private KeyEntry(String name, String keyId, String type, Key key) { - this.name = name; + private KeyEntry(String keyId, Key key) { this.keyId = keyId; - this.type = type; this.key = key; } } @@ -278,17 +292,20 @@ private static class TestCase { private final String name; private final String ciphertextPath; private final String plaintextPath; + private final Keyring keyring; private final MasterKeyProvider mkp; - private TestCase(String name, String ciphertextPath, String plaintextPath, List> mks) { - this(name, ciphertextPath, plaintextPath, MultipleProviderFactory.buildMultiProvider(mks)); - } - - private TestCase(String name, String ciphertextPath, String plaintextPath, MasterKeyProvider mkp) { + private TestCase(String name, String ciphertextPath, String plaintextPath, List keyrings, List> mks) { this.name = name; this.ciphertextPath = ciphertextPath; this.plaintextPath = plaintextPath; - this.mkp = mkp; + this.keyring = StandardKeyrings.multi(keyrings.get(0), keyrings.size() > 1 ? keyrings.subList(1, keyrings.size()) : emptyList()); + this.mkp = MultipleProviderFactory.buildMultiProvider(mks); + } + + @Override + public String toString() { + return name; } } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/XCompatDecryptTest.java b/src/test/java/com/amazonaws/encryptionsdk/XCompatDecryptTest.java deleted file mode 100644 index 9e7a278a4..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/XCompatDecryptTest.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk; - -import java.io.File; -import java.io.StringReader; -import java.lang.IllegalArgumentException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyFactory; -import java.security.PrivateKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.lang3.StringUtils; - -import org.bouncycastle.util.io.pem.PemReader; - -import static org.junit.Assert.assertArrayEquals; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import com.amazonaws.encryptionsdk.internal.Utils; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.core.type.TypeReference; - -import com.amazonaws.encryptionsdk.jce.JceMasterKey; -import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; - -@RunWith(Parameterized.class) -public class XCompatDecryptTest { - private static final String STATIC_XCOMPAT_NAME = "static-aws-xcompat"; - private static final String AES_GCM = "AES/GCM/NoPadding"; - - private String plaintextFileName; - private String ciphertextFileName; - private MasterKeyProvider masterKeyProvider; - - public XCompatDecryptTest( - String plaintextFileName, - String ciphertextFileName, - MasterKeyProvider masterKeyProvider - ) throws Exception { - this.plaintextFileName = plaintextFileName; - this.ciphertextFileName = ciphertextFileName; - this.masterKeyProvider = masterKeyProvider; - } - - @Parameters(name="{index}: testDecryptFromFile({0}, {1}, {2})") - public static Collection data() throws Exception{ - String baseDirName; - baseDirName = System.getProperty("staticCompatibilityResourcesDir"); - if (baseDirName == null) { - baseDirName = - XCompatDecryptTest.class.getProtectionDomain().getCodeSource().getLocation().getPath() + - "aws_encryption_sdk_resources"; - } - - List testCases_ = new ArrayList(); - - String ciphertextManifestName = StringUtils.join( - new String[]{ - baseDirName, - "manifests", - "ciphertext.manifest" - }, - File.separator - ); - File ciphertextManifestFile = new File(ciphertextManifestName); - - if (!ciphertextManifestFile.exists()) { - return Collections.emptySet(); - } - - ObjectMapper ciphertextManifestMapper = new ObjectMapper(); - Map ciphertextManifest = ciphertextManifestMapper.readValue( - ciphertextManifestFile, - new TypeReference>(){} - ); - - HashMap> staticKeyMap = new HashMap>(); - - Map testKeys = (Map)ciphertextManifest.get("test_keys"); - - for (Map.Entry keyType : testKeys.entrySet()) { - Map keys = (Map)keyType.getValue(); - HashMap thisKeyType = new HashMap(); - for (Map.Entry key : keys.entrySet()) { - Map thisKey = (Map)key.getValue(); - String keyRaw = new String( - StringUtils.join( - (List)thisKey.get("key"), - (String)thisKey.getOrDefault("line_separator", "") - ).getBytes(), - StandardCharsets.UTF_8 - ); - byte[] keyBytes; - switch ((String)thisKey.get("encoding")) { - case "base64": - keyBytes = Utils.decodeBase64String(keyRaw); - break; - case "pem": - PemReader pemReader = new PemReader(new StringReader(keyRaw)); - keyBytes = pemReader.readPemObject().getContent(); - break; - case "raw": - default: - keyBytes = keyRaw.getBytes(); - } - thisKeyType.put((String)key.getKey(), keyBytes); - } - staticKeyMap.put((String)keyType.getKey(), thisKeyType); - } - - final KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA"); - - List> testCases = (List>)ciphertextManifest.get("test_cases"); - for (Map testCase : testCases) { - Map plaintext = (Map)testCase.get("plaintext"); - Map ciphertext = (Map)testCase.get("ciphertext"); - - short algId = (short) Integer.parseInt((String)testCase.get("algorithm"), 16); - CryptoAlgorithm encryptionAlgorithm = CryptoAlgorithm.deserialize(algId); - - List> masterKeys = (List>)testCase.get("master_keys"); - List allMasterKeys = new ArrayList(); - for (Map aMasterKey : masterKeys) { - String providerId = (String)aMasterKey.get("provider_id"); - if (providerId.equals(STATIC_XCOMPAT_NAME) && (boolean)aMasterKey.get("decryptable")) { - String paddingAlgorithm = (String)aMasterKey.getOrDefault("padding_algorithm", ""); - String paddingHash = (String)aMasterKey.getOrDefault("padding_hash", ""); - Integer keyBits = (Integer)aMasterKey.getOrDefault( - "key_bits", - encryptionAlgorithm.getDataKeyLength() * 8 - ); - String keyId = - (String)aMasterKey.get("encryption_algorithm") + "." + - keyBits.toString() + "." + - paddingAlgorithm + "." + - paddingHash; - String encAlg = (String)aMasterKey.get("encryption_algorithm"); - switch (encAlg.toUpperCase()) { - case "RSA": - String cipherBase = "RSA/ECB/"; - String cipherName; - switch (paddingAlgorithm) { - case "OAEP-MGF1": - cipherName = cipherBase + "OAEPWith" + paddingHash + "AndMGF1Padding"; - break; - case "PKCS1": - cipherName = cipherBase + paddingAlgorithm + "Padding"; - break; - default: - throw new IllegalArgumentException("Unknown padding algorithm: " + paddingAlgorithm); - } - PrivateKey privKey = rsaKeyFactory.generatePrivate(new PKCS8EncodedKeySpec(staticKeyMap.get("RSA").get(keyBits.toString()))); - allMasterKeys.add(JceMasterKey.getInstance( - null, - privKey, - STATIC_XCOMPAT_NAME, - keyId, - cipherName - )); - break; - case "AES": - SecretKeySpec spec = new SecretKeySpec( - staticKeyMap.get("AES").get(keyBits.toString()), - 0, - encryptionAlgorithm.getDataKeyLength(), - encryptionAlgorithm.getDataKeyAlgo() - ); - allMasterKeys.add(JceMasterKey.getInstance(spec, STATIC_XCOMPAT_NAME, keyId, AES_GCM)); - break; - default: - throw new IllegalArgumentException("Unknown encryption algorithm: " + encAlg.toUpperCase()); - } - } - } - - if (allMasterKeys.size() > 0) { - final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(allMasterKeys); - testCases_.add(new Object[]{ - baseDirName + File.separator + plaintext.get("filename"), - baseDirName + File.separator + ciphertext.get("filename"), - provider - }); - } - } - return testCases_; - } - - @Test - public void testDecryptFromFile() throws Exception { - AwsCrypto crypto = new AwsCrypto(); - byte ciphertextBytes[] = Files.readAllBytes(Paths.get(ciphertextFileName)); - byte plaintextBytes[] = Files.readAllBytes(Paths.get(plaintextFileName)); - final CryptoResult decryptResult = crypto.decryptData( - masterKeyProvider, - ciphertextBytes - ); - assertArrayEquals(plaintextBytes, (byte[])decryptResult.getResult()); - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java index dcb0de9cd..0393e906e 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/DecryptionHandlerTest.java @@ -16,6 +16,7 @@ import java.util.Collections; import java.util.Map; +import com.amazonaws.encryptionsdk.keyrings.Keyring; import org.junit.Before; import org.junit.Test; @@ -29,12 +30,16 @@ import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import static org.junit.Assert.assertEquals; + public class DecryptionHandlerTest { private StaticMasterKey masterKeyProvider_; + private Keyring keyring; @Before public void init() { masterKeyProvider_ = new StaticMasterKey("testmaterial"); + keyring = new TestKeyring("testmaterial"); } @Test(expected = NullPointerException.class) @@ -140,4 +145,13 @@ public void invalidOffsetProcessBytes() { final byte[] out = new byte[1]; decryptionHandler.processBytes(in, -1, in.length, out, 0); } -} \ No newline at end of file + + @Test + public void testNullMasterKey() { + final DecryptionHandler decryptionHandler = DecryptionHandler.create(new DefaultCryptoMaterialsManager(keyring)); + final byte[] out = new byte[1]; + final byte[] testHeaders = getTestHeaders(); + decryptionHandler.processBytes(getTestHeaders(), 0, testHeaders.length, out, 0); + assertEquals(0, decryptionHandler.getMasterKeys().size()); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/FrameEncryptionHandlerVeryLongTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/FrameEncryptionHandlerVeryLongTest.java index 228bcc28f..7970d441e 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/FrameEncryptionHandlerVeryLongTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/FrameEncryptionHandlerVeryLongTest.java @@ -1,28 +1,29 @@ package com.amazonaws.encryptionsdk.internal; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.fail; - import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; +import com.amazonaws.encryptionsdk.TestUtils; import org.bouncycastle.util.encoders.Hex; -import org.bouncycastle.util.encoders.HexTranslator; -import org.junit.Test; import com.amazonaws.encryptionsdk.CryptoAlgorithm; import com.amazonaws.encryptionsdk.model.CipherFrameHeaders; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; /* * This test exhaustively encrypts a 2^32 frame message, which takes approximately 2-3 hours on my hardware. Because of * this long test time, this test is not run as part of the normal suites. */ -public class FrameEncryptionHandlerVeryLongTest { +@Tag(TestUtils.TAG_AD_HOC) +class FrameEncryptionHandlerVeryLongTest { @Test - public void exhaustiveIVCheck() throws Exception { + void exhaustiveIVCheck() throws Exception { CryptoAlgorithm algorithm = CryptoAlgorithm.ALG_AES_128_GCM_IV12_TAG16_NO_KDF; FrameEncryptionHandler frameEncryptionHandler_ = new FrameEncryptionHandler( new SecretKeySpec(new byte[16], "AES"), diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/TestKeyring.java b/src/test/java/com/amazonaws/encryptionsdk/internal/TestKeyring.java new file mode 100644 index 000000000..c01812001 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/TestKeyring.java @@ -0,0 +1,189 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.internal; + +import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceEntry; +import com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.KeyBlob; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.NotThreadSafe; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; + +import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static java.util.Objects.requireNonNull; + +/** + * Implementation of the {@link Keyring} interface that should only + * used for unit-tests. + *

+ * Contains a statically defined asymmetric key-pair that can be used + * to encrypt and decrypt (randomly generated) symmetric data key. + *

+ * + * @author wesleyr + */ +@NotThreadSafe +public class TestKeyring implements Keyring { + private static final String PROVIDER_ID = "static_provider"; + + /** + * Encryption algorithm for the key-pair + */ + private static final String ENCRYPTION_ALGORITHM = "RSA/ECB/PKCS1Padding"; + + /** + * Encryption algorithm for the KeyFactory + */ + private static final String KEY_FACTORY_ALGORITHM = "RSA"; + + /** + * The ID of the key + */ + private final String keyId_; + + /** + * The {@link Cipher} object created with the public part of + * the key. It's used to encrypt data keys. + */ + private final Cipher keyEncryptionCipher_; + + /** + * The {@link Cipher} object created with the private part of + * the key. It's used to decrypt encrypted data keys. + */ + private final Cipher keyDecryptionCipher_; + + /** + * Creates a new object that encrypts the data key with a key + * whose id is {@code keyId}. + *

+ * The value of {@code keyId} does not affect how the data key will be + * generated or encrypted. The {@code keyId} forms part of the header + * of the encrypted data, and is used to ensure that the header cannot + * be tempered with. + */ + public TestKeyring(@Nonnull final String keyId) { + this.keyId_ = requireNonNull(keyId); + + try { + KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); + KeySpec publicKeySpec = new X509EncodedKeySpec(publicKey_v1); + PublicKey pubKey = keyFactory.generatePublic(publicKeySpec); + KeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKey_v1); + PrivateKey privKey = keyFactory.generatePrivate(privateKeySpec); + + keyEncryptionCipher_ = Cipher.getInstance(ENCRYPTION_ALGORITHM); + keyEncryptionCipher_.init(Cipher.ENCRYPT_MODE, pubKey); + + keyDecryptionCipher_ = Cipher.getInstance(ENCRYPTION_ALGORITHM); + keyDecryptionCipher_.init(Cipher.DECRYPT_MODE, privKey); + + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public EncryptionMaterials onEncrypt(EncryptionMaterials encryptionMaterials) { + if(!encryptionMaterials.hasCleartextDataKey()) { + return generateDataKey(encryptionMaterials); + } else { + byte[] encryptedKey; + try { + encryptedKey = keyEncryptionCipher_.doFinal(encryptionMaterials.getCleartextDataKey().getEncoded()); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + return encryptionMaterials.withEncryptedDataKey(new KeyBlob(PROVIDER_ID, keyId_.getBytes(PROVIDER_ENCODING), encryptedKey), + new KeyringTraceEntry(PROVIDER_ID, keyId_, KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + } + } + + @Override + public DecryptionMaterials onDecrypt(DecryptionMaterials decryptionMaterials, List encryptedDataKeys) { + try { + for (EncryptedDataKey edk :encryptedDataKeys) { + if (keyId_.equals(new String(edk.getProviderInformation(), StandardCharsets.UTF_8))) { + byte[] unencryptedDataKey = keyDecryptionCipher_.doFinal(edk.getEncryptedDataKey()); + SecretKey key = new SecretKeySpec(unencryptedDataKey, decryptionMaterials.getAlgorithm().getDataKeyAlgo()); + + return decryptionMaterials.withCleartextDataKey(key, + new KeyringTraceEntry(PROVIDER_ID, keyId_, KeyringTraceFlag.DECRYPTED_DATA_KEY)); + } + } + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + + return decryptionMaterials; + } + + private EncryptionMaterials generateDataKey(EncryptionMaterials encryptionMaterials) { + try { + final byte[] rawKey = new byte[encryptionMaterials.getAlgorithm().getDataKeyLength()]; + Utils.getSecureRandom().nextBytes(rawKey); + SecretKey key = new SecretKeySpec(rawKey, encryptionMaterials.getAlgorithm().getDataKeyAlgo()); + byte[] encryptedKey = keyEncryptionCipher_.doFinal(key.getEncoded()); + + return encryptionMaterials + .withCleartextDataKey(key, + new KeyringTraceEntry(PROVIDER_ID, keyId_, KeyringTraceFlag.GENERATED_DATA_KEY)) + .withEncryptedDataKey(new KeyBlob(PROVIDER_ID, keyId_.getBytes(PROVIDER_ENCODING), encryptedKey), + new KeyringTraceEntry(PROVIDER_ID, keyId_, KeyringTraceFlag.ENCRYPTED_DATA_KEY)); + } catch (GeneralSecurityException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Statically configured private key. + */ + private static final byte[] privateKey_v1 = Utils.decodeBase64String( + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKLpwqjYtYExVilW/Hg0ogWv9xZ+" + + "THj4IzvISLlPtK8W6KXMcqukfuxdYmndPv8UD1DbdHFYSSistdqoBN32vVQOQnJZyYm45i2TDOV0" + + "M2DtHtR6aMMlBLGtdPeeaT88nQfI1ORjRDyR1byMwomvmKifZYga6FjLt/sgqfSE9BUnAgMBAAEC" + + "gYAqnewGL2qLuVRIzDCPYXVg938zqyZmHsNYyDP+BhPGGcASX0FAFW/+dQ9hkjcAk0bOaBo17Fp3" + + "AXcxE/Lx/bHY+GWZ0wOJfl3aJBVJOpW8J6kwu68BUCmuFtRgbLSFu5+fbey3pKafYSptbX1fAI+z" + + "hTx+a9B8pnn79ad4ziJ2QQJBAM+YHPGAEbr5qcNkwyy0xZgR/TLlcW2NQUt8HZpmErdX6d328iBC" + + "SPb8+whXxCXZC3Mr+35IZ1pxxf0go/zGQv0CQQDI5oH0z1CKxoT6ErswNzB0oHxq/wD5mhutyqHa" + + "mxbG5G3fN7I2IclwaXEA2eutIKxFMQNZYsX5mNYsrveSKivzAkABiujUJpZ7JDXNvObyYxmAyslt" + + "4mSYYs9UZ0S1DAMhl6amPpqIANYX98NJyZUsjtNV9MK2qoUSF/xXqDFvxG1lAkBhP5Ow2Zn3U1mT" + + "Y/XQxSZjjjwr3vyt1neHjQsEMwa3iGPXJbLSmVBVZfUZoGOBDsvVQoCIiFOlGuKyBpA45MkZAkAH" + + "ksUrS9xLrDIUOI2BzMNRsK0bH7KJ+PFxm2SBgJOF9+Uf2A9LIP4IvESZq+ufp6c8YaqgR6Id1vws" + + "7rUyGoa5"); + + /** + * Statically configured public key. + */ + private static final byte[] publicKey_v1 = Utils.decodeBase64String( + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCi6cKo2LWBMVYpVvx4NKIFr/cWfkx4+CM7yEi5" + + "T7SvFuilzHKrpH7sXWJp3T7/FA9Q23RxWEkorLXaqATd9r1UDkJyWcmJuOYtkwzldDNg7R7UemjD" + + "JQSxrXT3nmk/PJ0HyNTkY0Q8kdW8jMKJr5ion2WIGuhYy7f7IKn0hPQVJwIDAQAB"); +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java similarity index 72% rename from src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java rename to src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java index 30ba8ba41..c295d3f9c 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringTest.java @@ -17,8 +17,8 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.AwsCryptoException; import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; -import com.amazonaws.encryptionsdk.exception.MalformedArnException; import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; @@ -38,11 +38,11 @@ import java.util.Map; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.ENCRYPTED_DATA_KEY; import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.GENERATED_DATA_KEY; import static com.amazonaws.encryptionsdk.keyrings.KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -51,7 +51,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class KmsKeyringTest { +class AwsKmsKeyringTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); @@ -59,28 +59,31 @@ class KmsKeyringTest { private static final String GENERATOR_KEY_ID = "arn:aws:kms:us-east-1:999999999999:key/generator-89ab-cdef-fedc-ba9876543210"; private static final String KEY_ID_1 = "arn:aws:kms:us-east-1:999999999999:key/key1-23bv-sdfs-werw-234323nfdsf"; private static final String KEY_ID_2 = "arn:aws:kms:us-east-1:999999999999:key/key2-02ds-wvjs-aswe-a4923489273"; - private static final EncryptedDataKey ENCRYPTED_GENERATOR_KEY = new KeyBlob(KMS_PROVIDER_ID, + private static final AwsKmsCmkId GENERATOR_KEY = AwsKmsCmkId.fromString(GENERATOR_KEY_ID); + private static final AwsKmsCmkId KEY_1 = AwsKmsCmkId.fromString(KEY_ID_1); + private static final AwsKmsCmkId KEY_2 = AwsKmsCmkId.fromString(KEY_ID_2); + private static final EncryptedDataKey ENCRYPTED_GENERATOR_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - private static final EncryptedDataKey ENCRYPTED_KEY_1 = new KeyBlob(KMS_PROVIDER_ID, + private static final EncryptedDataKey ENCRYPTED_KEY_1 = new KeyBlob(AWS_KMS_PROVIDER_ID, KEY_ID_1.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); - private static final EncryptedDataKey ENCRYPTED_KEY_2 = new KeyBlob(KMS_PROVIDER_ID, + private static final EncryptedDataKey ENCRYPTED_KEY_2 = new KeyBlob(AWS_KMS_PROVIDER_ID, KEY_ID_2.getBytes(PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); private static final KeyringTraceEntry ENCRYPTED_GENERATOR_KEY_TRACE = - new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); private static final KeyringTraceEntry ENCRYPTED_KEY_1_TRACE = - new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_1, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); private static final KeyringTraceEntry ENCRYPTED_KEY_2_TRACE = - new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_2, ENCRYPTED_DATA_KEY, SIGNED_ENCRYPTION_CONTEXT); private static final KeyringTraceEntry GENERATED_DATA_KEY_TRACE = - new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, GENERATED_DATA_KEY); + new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, GENERATED_DATA_KEY); @Mock(lenient = true) private DataKeyEncryptionDao dataKeyEncryptionDao; private Keyring keyring; @BeforeEach void setup() { - when(dataKeyEncryptionDao.encryptDataKey(GENERATOR_KEY_ID, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_GENERATOR_KEY); - when(dataKeyEncryptionDao.encryptDataKey(KEY_ID_1, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_1); - when(dataKeyEncryptionDao.encryptDataKey(KEY_ID_2, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_2); + when(dataKeyEncryptionDao.encryptDataKey(GENERATOR_KEY, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_GENERATOR_KEY); + when(dataKeyEncryptionDao.encryptDataKey(KEY_1, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_1); + when(dataKeyEncryptionDao.encryptDataKey(KEY_2, PLAINTEXT_DATA_KEY, ENCRYPTION_CONTEXT)).thenReturn(ENCRYPTED_KEY_2); when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_GENERATOR_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) .thenReturn(new DecryptDataKeyResult(GENERATOR_KEY_ID, PLAINTEXT_DATA_KEY)); @@ -89,39 +92,58 @@ void setup() { when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_2, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) .thenReturn(new DecryptDataKeyResult(KEY_ID_2, PLAINTEXT_DATA_KEY)); - List keyIds = new ArrayList<>(); - keyIds.add(KEY_ID_1); - keyIds.add(KEY_ID_2); - keyring = new KmsKeyring(dataKeyEncryptionDao, keyIds, GENERATOR_KEY_ID); + List keyIds = new ArrayList<>(); + keyIds.add(AwsKmsCmkId.fromString(KEY_ID_1)); + keyIds.add(AwsKmsCmkId.fromString(KEY_ID_2)); + keyring = new AwsKmsKeyring(dataKeyEncryptionDao, keyIds, AwsKmsCmkId.fromString(GENERATOR_KEY_ID), false); } @Test void testMalformedArns() { - assertThrows(MalformedArnException.class, () -> new KmsKeyring(dataKeyEncryptionDao, null, "badArn")); - assertThrows(MalformedArnException.class, () -> new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList("badArn"), GENERATOR_KEY_ID)); - DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); - encryptedDataKeys.add(new KeyBlob(KMS_PROVIDER_ID, "badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + encryptedDataKeys.add(new KeyBlob(AWS_KMS_PROVIDER_ID, "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); encryptedDataKeys.add(ENCRYPTED_KEY_1); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); + decryptionMaterials = DecryptionMaterials.newBuilder() + .setAlgorithm(ALGORITHM_SUITE) + .setEncryptionContext(ENCRYPTION_CONTEXT) + .build(); + // Malformed Arn for a non KMS provider shouldn't fail encryptedDataKeys.clear(); - encryptedDataKeys.add(new KeyBlob("OtherProviderId", "badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + encryptedDataKeys.add(new KeyBlob("OtherProviderId", "arn:badArn".getBytes(PROVIDER_ENCODING), new byte[]{})); + assertFalse(keyring.onDecrypt(decryptionMaterials, encryptedDataKeys).hasCleartextDataKey()); } @Test void testGeneratorKeyInKeyIds() { - assertThrows(IllegalArgumentException.class, () -> new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList(GENERATOR_KEY_ID), GENERATOR_KEY_ID)); + assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, + Collections.singletonList(AwsKmsCmkId.fromString(GENERATOR_KEY_ID)), + AwsKmsCmkId.fromString(GENERATOR_KEY_ID), false)); + } + + @Test + void testNotDiscoveryNoKeysIds() { + assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, + null,null, false)); + } + + @Test + void testDiscoveryWithKeyId() { + assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, + null, + AwsKmsCmkId.fromString(GENERATOR_KEY_ID), true)); + assertThrows(IllegalArgumentException.class, () -> new AwsKmsKeyring(dataKeyEncryptionDao, + Collections.singletonList(AwsKmsCmkId.fromString(GENERATOR_KEY_ID)), + null, true)); } @Test @@ -132,7 +154,7 @@ void testEncryptDecryptExistingDataKey() { .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(3, encryptionMaterials.getEncryptedDataKeys().size()); assertEncryptedDataKeyEquals(ENCRYPTED_KEY_1, encryptionMaterials.getEncryptedDataKeys().get(0)); @@ -147,18 +169,17 @@ void testEncryptDecryptExistingDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(ENCRYPTED_GENERATOR_KEY); encryptedDataKeys.add(ENCRYPTED_KEY_1); encryptedDataKeys.add(ENCRYPTED_KEY_2); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @@ -166,12 +187,12 @@ void testEncryptDecryptExistingDataKey() { void testEncryptNullDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - when(dataKeyEncryptionDao.generateDataKey(GENERATOR_KEY_ID, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenReturn(new DataKeyEncryptionDao.GenerateDataKeyResult(PLAINTEXT_DATA_KEY, ENCRYPTED_GENERATOR_KEY)); - keyring.onEncrypt(encryptionMaterials); + when(dataKeyEncryptionDao.generateDataKey(GENERATOR_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) + .thenReturn(new DataKeyEncryptionDao.GenerateDataKeyResult(PLAINTEXT_DATA_KEY, ENCRYPTED_GENERATOR_KEY)); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(PLAINTEXT_DATA_KEY, encryptionMaterials.getCleartextDataKey()); @@ -184,18 +205,17 @@ void testEncryptNullDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(ENCRYPTED_GENERATOR_KEY); encryptedDataKeys.add(ENCRYPTED_KEY_1); encryptedDataKeys.add(ENCRYPTED_KEY_2); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, GENERATOR_KEY_ID, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @@ -203,14 +223,14 @@ void testEncryptNullDataKey() { void testEncryptNullGenerator() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) - .setKeyringTrace(new KeyringTrace()) .setCleartextDataKey(PLAINTEXT_DATA_KEY) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - Keyring keyring = new KmsKeyring(dataKeyEncryptionDao, Collections.singletonList(KEY_ID_1), null); + Keyring keyring = new AwsKmsKeyring(dataKeyEncryptionDao, + Collections.singletonList(AwsKmsCmkId.fromString(KEY_ID_1)), null, false); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); assertEncryptedDataKeyEquals(ENCRYPTED_KEY_1, encryptionMaterials.getEncryptedDataKeys().get(0)); @@ -223,13 +243,13 @@ void testEncryptNullGenerator() { @Test void testDiscoveryEncrypt() { - keyring = new KmsKeyring(dataKeyEncryptionDao, null, null); + keyring = new AwsKmsKeyring(dataKeyEncryptionDao, null, null, true); EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertFalse(encryptionMaterials.hasCleartextDataKey()); assertEquals(0, encryptionMaterials.getKeyringTrace().getEntries().size()); @@ -237,9 +257,8 @@ void testDiscoveryEncrypt() { @Test void testEncryptNoGeneratorOrCleartextDataKey() { - List keyIds = new ArrayList<>(); - keyIds.add(KEY_ID_1); - keyring = new KmsKeyring(dataKeyEncryptionDao, keyIds, null); + keyring = new AwsKmsKeyring(dataKeyEncryptionDao, + Collections.singletonList(AwsKmsCmkId.fromString(KEY_ID_1)), null, false); EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder().setAlgorithm(ALGORITHM_SUITE).build(); assertThrows(AwsCryptoException.class, () -> keyring.onEncrypt(encryptionMaterials)); @@ -250,7 +269,6 @@ void testDecryptFirstKeyFails() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); when(dataKeyEncryptionDao.decryptDataKey(ENCRYPTED_KEY_1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)).thenThrow(new CannotUnwrapDataKeyException()); @@ -258,11 +276,11 @@ void testDecryptFirstKeyFails() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(ENCRYPTED_KEY_1); encryptedDataKeys.add(ENCRYPTED_KEY_2); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @@ -283,7 +301,6 @@ void testDecryptFirstKeyWrongProvider() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); EncryptedDataKey wrongProviderKey = new KeyBlob("OtherProvider", KEY_ID_1.getBytes(PROVIDER_ENCODING), new byte[]{}); @@ -291,32 +308,31 @@ void testDecryptFirstKeyWrongProvider() { List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(wrongProviderKey); encryptedDataKeys.add(ENCRYPTED_KEY_2); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_2, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @Test void testDiscoveryDecrypt() { - keyring = new KmsKeyring(dataKeyEncryptionDao, null, null); + keyring = new AwsKmsKeyring(dataKeyEncryptionDao, null, null, true); DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); List encryptedDataKeys = new ArrayList<>(); encryptedDataKeys.add(ENCRYPTED_KEY_1); encryptedDataKeys.add(ENCRYPTED_KEY_2); - keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); - KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(KMS_PROVIDER_ID, KEY_ID_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); + KeyringTraceEntry expectedKeyringTraceEntry = new KeyringTraceEntry(AWS_KMS_PROVIDER_ID, KEY_ID_1, KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT); assertEquals(expectedKeyringTraceEntry, decryptionMaterials.getKeyringTrace().getEntries().get(0)); } @@ -328,7 +344,7 @@ void testDecryptAlreadyDecryptedDataKey() { .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_GENERATOR_KEY)); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_GENERATOR_KEY)); assertEquals(PLAINTEXT_DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); @@ -339,10 +355,9 @@ void testDecryptNoDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); - keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); assertFalse(decryptionMaterials.hasCleartextDataKey()); assertEquals(0, decryptionMaterials.getKeyringTrace().getEntries().size()); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java index 5b6105070..e0da5d523 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/KeyringTraceTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.Test; +import java.util.Collections; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -27,10 +29,9 @@ void testOrderMaintained() { KeyringTraceEntry entry2 = new KeyringTraceEntry("ns2", "name2", KeyringTraceFlag.DECRYPTED_DATA_KEY); KeyringTraceEntry entry3 = new KeyringTraceEntry("ns3", "name3", KeyringTraceFlag.ENCRYPTED_DATA_KEY); - KeyringTrace trace = new KeyringTrace(); - trace.add(entry1.getKeyNamespace(), entry1.getKeyName(), entry1.getFlags().iterator().next()); - trace.add(entry2.getKeyNamespace(), entry2.getKeyName(), entry2.getFlags().iterator().next()); - trace.add(entry3.getKeyNamespace(), entry3.getKeyName(), entry3.getFlags().iterator().next()); + KeyringTrace trace = KeyringTrace.EMPTY_TRACE.with(new KeyringTraceEntry(entry1.getKeyNamespace(), entry1.getKeyName(), entry1.getFlags().iterator().next())); + trace = trace.with(new KeyringTraceEntry(entry2.getKeyNamespace(), entry2.getKeyName(), entry2.getFlags().iterator().next())); + trace = trace.with(new KeyringTraceEntry(entry3.getKeyNamespace(), entry3.getKeyName(), entry3.getFlags().iterator().next())); assertEquals(entry1, trace.getEntries().get(0)); assertEquals(entry2, trace.getEntries().get(1)); @@ -39,8 +40,7 @@ void testOrderMaintained() { @Test void testImmutable() { - KeyringTrace trace = new KeyringTrace(); - trace.add("namespace", "name", KeyringTraceFlag.GENERATED_DATA_KEY); + KeyringTrace trace = new KeyringTrace(Collections.singletonList(new KeyringTraceEntry("namespace", "name", KeyringTraceFlag.GENERATED_DATA_KEY))); assertThrows(UnsupportedOperationException.class, () -> trace.getEntries().add(new KeyringTraceEntry("ns1", "name1", KeyringTraceFlag.GENERATED_DATA_KEY))); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java new file mode 100644 index 000000000..00a1d6f58 --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.keyrings; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.internal.RandomBytesGenerator; +import com.amazonaws.encryptionsdk.internal.Utils; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class MasterKeyProviderCompatibilityTest { + + private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); + private static final String KEY_NAMESPACE = "TestKeyNamespace"; + private static final String KEY_NAME = "TestKeyName"; + private static final byte[] PLAINTEXT = RandomBytesGenerator.generate(1000); + private final AwsCrypto awsCrypto = new AwsCrypto(); + + @Tag(TestUtils.TAG_INTEGRATION) + @Test + void testAwsKmsKeyringCompatibility() { + MasterKeyProvider mkp = KmsMasterKeyProvider.builder() + .withKeysForEncryption(KMSTestFixtures.TEST_KEY_IDS[0]).build(); + Keyring keyring = StandardKeyrings.awsKms(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); + + testCompatibility(keyring, mkp); + } + + @Test + void testRawAesKeyringCompatibility() { + SecretKey key = generateRandomKey(); + + JceMasterKey mkp = JceMasterKey.getInstance(key, KEY_NAMESPACE, KEY_NAME, "AES/GCM/NoPadding"); + Keyring keyring = StandardKeyrings.rawAesBuilder() + .keyNamespace(KEY_NAMESPACE) + .keyName(KEY_NAME) + .wrappingKey(key) + .build(); + + testCompatibility(keyring, mkp); + } + + @Test + void testRawRsaKeyringCompatibility() throws Exception { + final String wrappingAlgorithm = "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"; + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + kg.initialize(4096); + KeyPair keyPair = kg.generateKeyPair(); + + JceMasterKey mkp = JceMasterKey.getInstance(keyPair.getPublic(), keyPair.getPrivate(), KEY_NAMESPACE, KEY_NAME, + wrappingAlgorithm); + Keyring keyring = StandardKeyrings.rawRsaBuilder() + .keyNamespace(KEY_NAMESPACE) + .keyName(KEY_NAME) + .publicKey(keyPair.getPublic()) + .privateKey(keyPair.getPrivate()) + .wrappingAlgorithm(wrappingAlgorithm) + .build(); + + testCompatibility(keyring, mkp); + } + + @Tag(TestUtils.TAG_INTEGRATION) + @Test + void testMultiKeyringCompatibility() { + SecretKey key = generateRandomKey(); + MasterKeyProvider mkp1 = KmsMasterKeyProvider.builder() + .withKeysForEncryption(KMSTestFixtures.TEST_KEY_IDS[0]).build(); + JceMasterKey mkp2 = JceMasterKey.getInstance(key, KEY_NAMESPACE, KEY_NAME, "AES/GCM/NoPadding"); + + MasterKeyProvider mkp = MultipleProviderFactory.buildMultiProvider(mkp1, mkp2); + + Keyring keyring1 = StandardKeyrings.awsKms(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); + Keyring keyring2 = StandardKeyrings.rawAesBuilder() + .keyNamespace(KEY_NAMESPACE) + .keyName(KEY_NAME) + .wrappingKey(key) + .build(); + + Keyring keyring = StandardKeyrings.multi(keyring1, keyring2); + + testCompatibility(keyring, mkp); + } + + private void testCompatibility(Keyring keyring, MasterKeyProvider masterKeyProvider) { + CryptoResult mkpResult = awsCrypto.encryptData(masterKeyProvider, PLAINTEXT, ENCRYPTION_CONTEXT); + AwsCryptoResult keyringResult = awsCrypto.decrypt(DecryptRequest.builder() + .keyring(keyring) + .ciphertext(mkpResult.getResult()).build()); + + assertArrayEquals(PLAINTEXT, keyringResult.getResult()); + + keyringResult = awsCrypto.encrypt(EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(ENCRYPTION_CONTEXT) + .plaintext(PLAINTEXT).build()); + mkpResult = awsCrypto.decryptData(masterKeyProvider, keyringResult.getResult()); + + assertArrayEquals(PLAINTEXT, mkpResult.getResult()); + } + + private static SecretKey generateRandomKey() { + byte[] rawKey = new byte[16]; // 128 bits + Utils.getSecureRandom().nextBytes(rawKey); + return new SecretKeySpec(rawKey, "AES"); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java index a9d414754..19c22bb99 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MultiKeyringTest.java @@ -24,7 +24,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import javax.crypto.SecretKey; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,7 +46,6 @@ class MultiKeyringTest { @Mock EncryptionMaterials encryptionMaterials; @Mock DecryptionMaterials decryptionMaterials; @Mock List encryptedDataKeys; - @Mock SecretKey cleartextDataKey; final List childrenKeyrings = new ArrayList<>(); @BeforeEach @@ -68,6 +66,9 @@ void testConstructor() { void testOnEncryptWithGenerator() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); when(encryptionMaterials.hasCleartextDataKey()).thenReturn(true); + when(generatorKeyring.onEncrypt(encryptionMaterials)).thenReturn(encryptionMaterials); + when(keyring1.onEncrypt(encryptionMaterials)).thenReturn(encryptionMaterials); + when(keyring2.onEncrypt(encryptionMaterials)).thenReturn(encryptionMaterials); keyring.onEncrypt(encryptionMaterials); @@ -80,6 +81,8 @@ void testOnEncryptWithGenerator() { void testOnEncryptWithoutGenerator() { MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); when(encryptionMaterials.hasCleartextDataKey()).thenReturn(true); + when(keyring1.onEncrypt(encryptionMaterials)).thenReturn(encryptionMaterials); + when(keyring2.onEncrypt(encryptionMaterials)).thenReturn(encryptionMaterials); keyring.onEncrypt(encryptionMaterials); @@ -111,6 +114,8 @@ void testOnDecryptWithGenerator() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); + when(generatorKeyring.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); + when(keyring1.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); InOrder inOrder = inOrder(generatorKeyring, keyring1); @@ -124,6 +129,8 @@ void testOnDecryptWithoutGenerator() { MultiKeyring keyring = new MultiKeyring(null, childrenKeyrings); when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false).thenReturn(false).thenReturn(true); + when(keyring1.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); + when(keyring2.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); InOrder inOrder = inOrder(keyring1, keyring2); @@ -138,6 +145,7 @@ void testOnDecryptFailureThenSuccess() { when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false).thenReturn(true); doThrow(new IllegalStateException()).when(generatorKeyring).onDecrypt(decryptionMaterials, encryptedDataKeys); + when(keyring1.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); @@ -177,6 +185,9 @@ void testOnDecryptNoFailuresNoPlaintextDataKeys() { MultiKeyring keyring = new MultiKeyring(generatorKeyring, childrenKeyrings); when(decryptionMaterials.hasCleartextDataKey()).thenReturn(false, false, false, false); + when(generatorKeyring.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); + when(keyring1.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); + when(keyring2.onDecrypt(decryptionMaterials, encryptedDataKeys)).thenReturn(decryptionMaterials); keyring.onDecrypt(decryptionMaterials, encryptedDataKeys); InOrder inOrder = inOrder(generatorKeyring, keyring1, keyring2); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java index 556e63132..0f597a3d0 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawAesKeyringTest.java @@ -58,11 +58,10 @@ void testEncryptDecryptExistingDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setCleartextDataKey(DATA_KEY) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); @@ -81,10 +80,9 @@ void testEncryptDecryptExistingDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); - keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); @@ -98,11 +96,10 @@ void testEncryptDecryptExistingDataKey() { void testEncryptDecryptGenerateDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertNotNull(encryptionMaterials.getCleartextDataKey()); assertEquals(encryptionMaterials.getCleartextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); @@ -123,10 +120,9 @@ void testEncryptDecryptGenerateDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); - keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); assertEquals(encryptionMaterials.getCleartextDataKey(), decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java index 60482d403..3b80f7711 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawKeyringTest.java @@ -76,11 +76,10 @@ void testEncryptDecryptExistingDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setCleartextDataKey(DATA_KEY) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); assertEncryptedDataKeyEquals(ENCRYPTED_DATA_KEY, encryptionMaterials.getEncryptedDataKeys().get(0)); @@ -90,10 +89,9 @@ void testEncryptDecryptExistingDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); - keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -103,13 +101,12 @@ void testEncryptDecryptExistingDataKey() { void testEncryptNullDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); ArgumentCaptor dataKeyCaptor = ArgumentCaptor.forClass(byte[].class); when(jceKeyCipher.encryptKey(dataKeyCaptor.capture(), eq(KEYNAME), eq(KEYNAMESPACE), eq(ENCRYPTION_CONTEXT))).thenReturn(ENCRYPTED_DATA_KEY); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(encryptionMaterials.getCleartextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); assertArrayEquals(encryptionMaterials.getCleartextDataKey().getEncoded(), dataKeyCaptor.getValue()); @@ -127,7 +124,6 @@ void testDecryptAlreadyDecryptedDataKey() { .setAlgorithm(ALGORITHM) .setCleartextDataKey(DATA_KEY) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.singletonList(ENCRYPTED_DATA_KEY)); @@ -141,7 +137,6 @@ void testDecryptNoValidDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.singletonList(INVALID_DATA_KEY)); @@ -155,7 +150,6 @@ void testDecryptNoDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); keyring.onDecrypt(decryptionMaterials, Collections.emptyList()); @@ -170,14 +164,13 @@ void testDecryptMultipleKeysOneInvalid() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); final List edks = new ArrayList<>(); edks.add(INVALID_DATA_KEY); edks.add(ENCRYPTED_DATA_KEY); - keyring.onDecrypt(decryptionMaterials, edks); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, edks); assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); @@ -190,7 +183,6 @@ void testDecryptMultipleKeysOneException() throws GeneralSecurityException { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); when(jceKeyCipher.decryptKey(BAD_DATA_KEY, KEYNAME, ENCRYPTION_CONTEXT)) @@ -200,7 +192,7 @@ void testDecryptMultipleKeysOneException() throws GeneralSecurityException { edks.add(BAD_DATA_KEY); edks.add(ENCRYPTED_DATA_KEY); - keyring.onDecrypt(decryptionMaterials, edks); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, edks); assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(DECRYPTED_DATA_KEY_TRACE, decryptionMaterials.getKeyringTrace().getEntries().get(0)); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java index 1f3f1fe63..a5ee08079 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java @@ -65,11 +65,10 @@ void testEncryptDecryptExistingDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setCleartextDataKey(DATA_KEY) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertEquals(1, encryptionMaterials.getEncryptedDataKeys().size()); @@ -86,10 +85,9 @@ void testEncryptDecryptExistingDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); - keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); assertEquals(DATA_KEY, decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); @@ -102,11 +100,10 @@ void testEncryptDecryptExistingDataKey() { void testEncryptDecryptGenerateDataKey() { EncryptionMaterials encryptionMaterials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) - .setKeyringTrace(new KeyringTrace()) .setEncryptionContext(ENCRYPTION_CONTEXT) .build(); - keyring.onEncrypt(encryptionMaterials); + encryptionMaterials = keyring.onEncrypt(encryptionMaterials); assertTrue(encryptionMaterials.hasCleartextDataKey()); assertEquals(encryptionMaterials.getCleartextDataKey().getAlgorithm(), ALGORITHM.getDataKeyAlgo()); @@ -125,10 +122,9 @@ void testEncryptDecryptGenerateDataKey() { DecryptionMaterials decryptionMaterials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM) .setEncryptionContext(ENCRYPTION_CONTEXT) - .setKeyringTrace(new KeyringTrace()) .build(); - keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); + decryptionMaterials = keyring.onDecrypt(decryptionMaterials, encryptionMaterials.getEncryptedDataKeys()); assertEquals(encryptionMaterials.getCleartextDataKey(), decryptionMaterials.getCleartextDataKey()); assertEquals(KEYNAME, decryptionMaterials.getKeyringTrace().getEntries().get(0).getKeyName()); diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplierTest.java similarity index 81% rename from src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java rename to src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplierTest.java index cdca99fd6..748077d40 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsClientSupplierTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplierTest.java @@ -31,6 +31,7 @@ import java.util.Collections; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.times; @@ -38,7 +39,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class KmsClientSupplierTest { +class AwsKmsClientSupplierTest { @Mock AWSKMSClientBuilder kmsClientBuilder; @Mock AWSKMS awskms; @@ -57,7 +58,7 @@ void testCredentialsAndClientConfiguration() { when(kmsClientBuilder.withCredentials(credentialsProvider)).thenReturn(kmsClientBuilder); when(kmsClientBuilder.build()).thenReturn(awskms); - KmsClientSupplier supplier = new KmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplier = new AwsKmsClientSupplier.Builder(kmsClientBuilder) .credentialsProvider(credentialsProvider) .clientConfiguration(clientConfiguration) .build(); @@ -71,7 +72,7 @@ void testCredentialsAndClientConfiguration() { @Test void testAllowedAndExcludedRegions() { - KmsClientSupplier supplierWithDefaultValues = new KmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplierWithDefaultValues = new AwsKmsClientSupplier.Builder(kmsClientBuilder) .build(); when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); @@ -79,7 +80,7 @@ void testAllowedAndExcludedRegions() { assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); - KmsClientSupplier supplierWithAllowed = new KmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplierWithAllowed = new AwsKmsClientSupplier.Builder(kmsClientBuilder) .allowedRegions(Collections.singleton(REGION_1)) .build(); @@ -89,7 +90,7 @@ void testAllowedAndExcludedRegions() { assertNotNull(supplierWithAllowed.getClient(REGION_1)); assertThrows(UnsupportedRegionException.class, () -> supplierWithAllowed.getClient(REGION_2)); - KmsClientSupplier supplierWithExcluded = new KmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplierWithExcluded = new AwsKmsClientSupplier.Builder(kmsClientBuilder) .excludedRegions(Collections.singleton(REGION_1)) .build(); @@ -99,7 +100,7 @@ void testAllowedAndExcludedRegions() { assertThrows(UnsupportedRegionException.class, () -> supplierWithExcluded.getClient(REGION_1)); assertNotNull(supplierWithExcluded.getClient(REGION_2)); - assertThrows(IllegalArgumentException.class, () -> new KmsClientSupplier.Builder(kmsClientBuilder) + assertThrows(IllegalArgumentException.class, () -> new AwsKmsClientSupplier.Builder(kmsClientBuilder) .allowedRegions(Collections.singleton(REGION_1)) .excludedRegions(Collections.singleton(REGION_2)) .build()); @@ -107,7 +108,7 @@ void testAllowedAndExcludedRegions() { @Test void testClientCachingDisabled() { - KmsClientSupplier supplierCachingDisabled = new KmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplierCachingDisabled = new AwsKmsClientSupplier.Builder(kmsClientBuilder) .clientCaching(false) .build(); @@ -126,8 +127,7 @@ void testClientCachingDisabled() { @Test void testClientCaching() { - KmsClientSupplier supplier = new KmsClientSupplier.Builder(kmsClientBuilder) - .clientCaching(true) + AwsKmsClientSupplier supplier = new AwsKmsClientSupplier.Builder(kmsClientBuilder) .build(); when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); @@ -170,4 +170,18 @@ void testClientCaching() { supplier.getClient(REGION_3); verify(kmsClientBuilder, times(8)).build(); } + + @Test + void testGetClientByKeyId() { + + final String arn = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; + final String aliasArn = "arn:aws:kms:us-east-1:999999999999:alias/MyCryptoKey"; + final String alias = "alias/MyCryptoKey"; + final String keyId = "01234567-89ab-cdef-fedc-ba9876543210"; + + assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(arn), s -> awskms)); + assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(aliasArn), s -> awskms)); + assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(alias), s -> awskms)); + assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(keyId), s -> awskms)); + } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkIdTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkIdTest.java new file mode 100644 index 000000000..6d23b2b5c --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsCmkIdTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.encryptionsdk.exception.MalformedArnException; +import org.junit.jupiter.api.Test; + +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.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AwsKmsCmkIdTest { + private static final String VALID_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; + private static final String VALID_ALIAS_ARN = "arn:aws:kms:us-east-1:999999999999:alias/MyCryptoKey"; + private static final String VALID_ALIAS = "alias/MyCryptoKey"; + private static final String VALID_RAW_KEY_ID = "01234567-89ab-cdef-fedc-ba9876543210"; + + @Test + void testFromString() { + assertThrows(MalformedArnException.class, () -> AwsKmsCmkId.fromString("arn:invalid")); + + assertTrue(AwsKmsCmkId.fromString(VALID_ARN).isArn()); + assertTrue(AwsKmsCmkId.fromString(VALID_ALIAS_ARN).isArn()); + assertFalse(AwsKmsCmkId.fromString(VALID_ALIAS).isArn()); + assertFalse(AwsKmsCmkId.fromString(VALID_RAW_KEY_ID).isArn()); + } + + @Test + void testIsKeyIdWellFormed() { + assertTrue(AwsKmsCmkId.isKeyIdWellFormed(VALID_ARN)); + assertTrue(AwsKmsCmkId.isKeyIdWellFormed(VALID_ALIAS_ARN)); + assertTrue(AwsKmsCmkId.isKeyIdWellFormed(VALID_ALIAS)); + assertTrue(AwsKmsCmkId.isKeyIdWellFormed(VALID_RAW_KEY_ID)); + assertFalse(AwsKmsCmkId.isKeyIdWellFormed("arn:invalid")); + assertFalse(AwsKmsCmkId.isKeyIdWellFormed(" ")); + assertFalse(AwsKmsCmkId.isKeyIdWellFormed(null)); + } + + @Test + void testToString() { + assertEquals(VALID_ARN, AwsKmsCmkId.fromString(VALID_ARN).toString()); + } + + @Test + void testEquals() { + assertEquals(AwsKmsCmkId.fromString(VALID_ARN), AwsKmsCmkId.fromString(VALID_ARN)); + assertNotEquals(AwsKmsCmkId.fromString(VALID_ALIAS), AwsKmsCmkId.fromString(VALID_ALIAS_ARN)); + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java similarity index 86% rename from src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java rename to src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java index e9dc140d7..ff4099e45 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsDataKeyEncryptionDaoTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsDataKeyEncryptionDaoTest.java @@ -39,7 +39,7 @@ import java.util.Map; import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -54,22 +54,23 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -class KmsDataKeyEncryptionDaoTest { +class AwsKmsDataKeyEncryptionDaoTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256; private static final SecretKey DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); private static final List GRANT_TOKENS = Collections.singletonList("testGrantToken"); private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("myKey", "myValue"); - private static final EncryptedDataKey ENCRYPTED_DATA_KEY = new KeyBlob(KMS_PROVIDER_ID, + private static final EncryptedDataKey ENCRYPTED_DATA_KEY = new KeyBlob(AWS_KMS_PROVIDER_ID, "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210".getBytes(EncryptedDataKey.PROVIDER_ENCODING), generate(ALGORITHM_SUITE.getDataKeyLength())); @Test void testEncryptAndDecrypt() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); - EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey(keyId, DATA_KEY, ENCRYPTION_CONTEXT); + EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey( + AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT); ArgumentCaptor er = ArgumentCaptor.forClass(EncryptRequest.class); verify(client, times(1)).encrypt(er.capture()); @@ -82,7 +83,7 @@ void testEncryptAndDecrypt() { assertArrayEquals(DATA_KEY.getEncoded(), actualRequest.getPlaintext().array()); assertUserAgent(actualRequest); - assertEquals(KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); + assertEquals(AWS_KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); assertArrayEquals(keyId.getBytes(EncryptedDataKey.PROVIDER_ENCODING), encryptedDataKeyResult.getProviderInformation()); assertNotNull(encryptedDataKeyResult.getEncryptedDataKey()); @@ -104,10 +105,11 @@ void testEncryptAndDecrypt() { @Test void testGenerateAndDecrypt() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); - DataKeyEncryptionDao.GenerateDataKeyResult generateDataKeyResult = dao.generateDataKey(keyId, ALGORITHM_SUITE, ENCRYPTION_CONTEXT); + DataKeyEncryptionDao.GenerateDataKeyResult generateDataKeyResult = dao.generateDataKey( + AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT); ArgumentCaptor gr = ArgumentCaptor.forClass(GenerateDataKeyRequest.class); verify(client, times(1)).generateDataKey(gr.capture()); @@ -143,11 +145,12 @@ void testGenerateAndDecrypt() { @Test void testEncryptWithRawKeyId() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); String rawKeyId = keyId.split("/")[1]; - EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey(rawKeyId, DATA_KEY, ENCRYPTION_CONTEXT); + EncryptedDataKey encryptedDataKeyResult = dao.encryptDataKey( + AwsKmsCmkId.fromString(rawKeyId), DATA_KEY, ENCRYPTION_CONTEXT); ArgumentCaptor er = ArgumentCaptor.forClass(EncryptRequest.class); verify(client, times(1)).encrypt(er.capture()); @@ -160,7 +163,7 @@ void testEncryptWithRawKeyId() { assertArrayEquals(DATA_KEY.getEncoded(), actualRequest.getPlaintext().array()); assertUserAgent(actualRequest); - assertEquals(KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); + assertEquals(AWS_KMS_PROVIDER_ID, encryptedDataKeyResult.getProviderId()); assertArrayEquals(keyId.getBytes(EncryptedDataKey.PROVIDER_ENCODING), encryptedDataKeyResult.getProviderInformation()); assertNotNull(encryptedDataKeyResult.getEncryptedDataKey()); } @@ -171,47 +174,52 @@ void testEncryptWrongKeyFormat() { when(key.getFormat()).thenReturn("BadFormat"); AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); - assertThrows(IllegalArgumentException.class, () -> dao.encryptDataKey(keyId, key, ENCRYPTION_CONTEXT)); + assertThrows(IllegalArgumentException.class, () -> dao.encryptDataKey( + AwsKmsCmkId.fromString(keyId), key, ENCRYPTION_CONTEXT)); } @Test void testKmsFailure() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); doThrow(new KMSInvalidStateException("fail")).when(client).generateDataKey(isA(GenerateDataKeyRequest.class)); doThrow(new KMSInvalidStateException("fail")).when(client).encrypt(isA(EncryptRequest.class)); doThrow(new KMSInvalidStateException("fail")).when(client).decrypt(isA(DecryptRequest.class)); - assertThrows(AwsCryptoException.class, () -> dao.generateDataKey(keyId, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); - assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey(keyId, DATA_KEY, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.generateDataKey( + AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey( + AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT)); assertThrows(AwsCryptoException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); } @Test void testUnsupportedRegionException() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); String keyId = client.createKey().getKeyMetadata().getArn(); doThrow(new UnsupportedRegionException("fail")).when(client).generateDataKey(isA(GenerateDataKeyRequest.class)); doThrow(new UnsupportedRegionException("fail")).when(client).encrypt(isA(EncryptRequest.class)); doThrow(new UnsupportedRegionException("fail")).when(client).decrypt(isA(DecryptRequest.class)); - assertThrows(AwsCryptoException.class, () -> dao.generateDataKey(keyId, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); - assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey(keyId, DATA_KEY, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.generateDataKey( + AwsKmsCmkId.fromString(keyId), ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); + assertThrows(AwsCryptoException.class, () -> dao.encryptDataKey( + AwsKmsCmkId.fromString(keyId), DATA_KEY, ENCRYPTION_CONTEXT)); assertThrows(AwsCryptoException.class, () -> dao.decryptDataKey(ENCRYPTED_DATA_KEY, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)); } @Test void testDecryptBadKmsKeyId() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); DecryptResult badResult = new DecryptResult(); badResult.setKeyId("badKeyId"); @@ -224,7 +232,7 @@ void testDecryptBadKmsKeyId() { @Test void testDecryptBadKmsKeyLength() { AWSKMS client = spy(new MockKMSClient()); - DataKeyEncryptionDao dao = new KmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); + DataKeyEncryptionDao dao = new AwsKmsDataKeyEncryptionDao(s -> client, GRANT_TOKENS); DecryptResult badResult = new DecryptResult(); badResult.setKeyId(new String(ENCRYPTED_DATA_KEY.getProviderInformation(), EncryptedDataKey.PROVIDER_ENCODING)); diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KMSProviderBuilderIntegrationTests.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KMSProviderBuilderIntegrationTests.java index 1a58496dd..7d3062e67 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KMSProviderBuilderIntegrationTests.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KMSProviderBuilderIntegrationTests.java @@ -1,10 +1,10 @@ package com.amazonaws.encryptionsdk.kms; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.never; @@ -19,7 +19,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; +import com.amazonaws.encryptionsdk.TestUtils; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import com.amazonaws.AbortedException; @@ -41,9 +43,10 @@ import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; -public class KMSProviderBuilderIntegrationTests { +@Tag(TestUtils.TAG_INTEGRATION) +class KMSProviderBuilderIntegrationTests { @Test - public void whenBogusRegionsDecrypted_doesNotLeakClients() throws Exception { + void whenBogusRegionsDecrypted_doesNotLeakClients() { AtomicReference> kmsCache = new AtomicReference<>(); KmsMasterKeyProvider mkp = (new KmsMasterKeyProvider.Builder() { @@ -75,7 +78,7 @@ public void whenBogusRegionsDecrypted_doesNotLeakClients() throws Exception { } @Test - public void whenOperationSuccessful_clientIsCached() { + void whenOperationSuccessful_clientIsCached() { AtomicReference> kmsCache = new AtomicReference<>(); KmsMasterKeyProvider mkp = (new KmsMasterKeyProvider.Builder() { @@ -99,7 +102,7 @@ public void whenOperationSuccessful_clientIsCached() { } @Test - public void whenConstructedWithoutArguments_canUseMultipleRegions() throws Exception { + void whenConstructedWithoutArguments_canUseMultipleRegions() { KmsMasterKeyProvider mkp = KmsMasterKeyProvider.builder().build(); for (String key : KMSTestFixtures.TEST_KEY_IDS) { @@ -115,25 +118,27 @@ public void whenConstructedWithoutArguments_canUseMultipleRegions() throws Excep } } - @SuppressWarnings("deprecation") @Test(expected = CannotUnwrapDataKeyException.class) - public void whenLegacyConstructorsUsed_multiRegionDecryptIsNotSupported() throws Exception { + @SuppressWarnings("deprecation") @Test + void whenLegacyConstructorsUsed_multiRegionDecryptIsNotSupported() { KmsMasterKeyProvider mkp = new KmsMasterKeyProvider(); - for (String key : KMSTestFixtures.TEST_KEY_IDS) { - byte[] ciphertext = - new AwsCrypto().encryptData( - KmsMasterKeyProvider.builder() - .withKeysForEncryption(key) - .build(), - new byte[1] - ).getResult(); - - new AwsCrypto().decryptData(mkp, ciphertext); - } + assertThrows(CannotUnwrapDataKeyException.class, () -> { + for (String key : KMSTestFixtures.TEST_KEY_IDS) { + byte[] ciphertext = + new AwsCrypto().encryptData( + KmsMasterKeyProvider.builder() + .withKeysForEncryption(key) + .build(), + new byte[1] + ).getResult(); + + new AwsCrypto().decryptData(mkp, ciphertext); + } + }); } @Test - public void whenHandlerConfigured_handlerIsInvoked() throws Exception { + void whenHandlerConfigured_handlerIsInvoked() { RequestHandler2 handler = spy(new RequestHandler2() {}); KmsMasterKeyProvider mkp = KmsMasterKeyProvider.builder() @@ -150,7 +155,7 @@ public void whenHandlerConfigured_handlerIsInvoked() throws Exception { } @Test - public void whenShortTimeoutSet_timesOut() throws Exception { + void whenShortTimeoutSet_timesOut() { // By setting a timeout of 1ms, it's not physically possible to complete both the us-west-2 and eu-central-1 // requests due to speed of light limits. KmsMasterKeyProvider mkp = KmsMasterKeyProvider.builder() @@ -179,7 +184,7 @@ public void whenShortTimeoutSet_timesOut() throws Exception { } @Test - public void whenCustomCredentialsSet_theyAreUsed() throws Exception { + void whenCustomCredentialsSet_theyAreUsed() { AWSCredentialsProvider customProvider = spy(new DefaultAWSCredentialsProviderChain()); KmsMasterKeyProvider mkp = KmsMasterKeyProvider.builder() @@ -204,7 +209,7 @@ public void whenCustomCredentialsSet_theyAreUsed() throws Exception { } @Test - public void whenBuilderCloned_credentialsAndConfigurationAreRetained() throws Exception { + void whenBuilderCloned_credentialsAndConfigurationAreRetained() { AWSCredentialsProvider customProvider1 = spy(new DefaultAWSCredentialsProviderChain()); AWSCredentialsProvider customProvider2 = spy(new DefaultAWSCredentialsProviderChain()); @@ -237,7 +242,7 @@ public void whenBuilderCloned_credentialsAndConfigurationAreRetained() throws Ex } @Test - public void whenBuilderCloned_clientBuilderCustomizationIsRetained() throws Exception { + void whenBuilderCloned_clientBuilderCustomizationIsRetained() { RequestHandler2 handler = spy(new RequestHandler2() {}); KmsMasterKeyProvider mkp = KmsMasterKeyProvider.builder() @@ -252,9 +257,9 @@ public void whenBuilderCloned_clientBuilderCustomizationIsRetained() throws Exce verify(handler, atLeastOnce()).beforeRequest(any()); } - @Test(expected = IllegalArgumentException.class) - public void whenBogusEndpointIsSet_constructionFails() throws Exception { - KmsMasterKeyProvider.builder() + @Test + void whenBogusEndpointIsSet_constructionFails() { + assertThrows(IllegalArgumentException.class, () -> KmsMasterKeyProvider.builder() .withClientBuilder( AWSKMSClientBuilder.standard() .withEndpointConfiguration( @@ -262,11 +267,11 @@ public void whenBogusEndpointIsSet_constructionFails() throws Exception { "https://this.does.not.exist.example.com", "bad-region") ) - ); + )); } @Test - public void whenUserAgentsOverridden_originalUAsPreserved() throws Exception { + void whenUserAgentsOverridden_originalUAsPreserved() { RequestHandler2 handler = spy(new RequestHandler2() {}); KmsMasterKeyProvider mkp = KmsMasterKeyProvider.builder() @@ -294,7 +299,7 @@ public void whenUserAgentsOverridden_originalUAsPreserved() throws Exception { } @Test - public void whenDefaultRegionSet_itIsUsedForBareKeyIds() throws Exception { + void whenDefaultRegionSet_itIsUsedForBareKeyIds() { // TODO: Need to set up a role to assume as bare key IDs are relative to the caller account } } diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java index e4c731ee7..d1ea1a0ec 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsMasterKeyTest.java @@ -18,7 +18,6 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.exception.MismatchedDataKeyException; import com.amazonaws.encryptionsdk.model.KeyBlob; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -32,8 +31,8 @@ import java.util.Map; import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; +import static com.amazonaws.encryptionsdk.internal.Constants.AWS_KMS_PROVIDER_ID; import static com.amazonaws.encryptionsdk.internal.RandomBytesGenerator.generate; -import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; @@ -43,7 +42,7 @@ class KmsMasterKeyTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("test", "value"); private static final String CMK_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; - @Mock KmsDataKeyEncryptionDao dataKeyEncryptionDao; + @Mock AwsKmsDataKeyEncryptionDao dataKeyEncryptionDao; /** * Test that when decryption of an encrypted data key throws a MismatchedDataKeyException, this @@ -51,8 +50,8 @@ class KmsMasterKeyTest { */ @Test void testMismatchedDataKeyException() { - EncryptedDataKey encryptedDataKey1 = new KeyBlob(KMS_PROVIDER_ID, "KeyId1".getBytes(PROVIDER_ENCODING), generate(64)); - EncryptedDataKey encryptedDataKey2 = new KeyBlob(KMS_PROVIDER_ID, "KeyId2".getBytes(PROVIDER_ENCODING), generate(64)); + EncryptedDataKey encryptedDataKey1 = new KeyBlob(AWS_KMS_PROVIDER_ID, "KeyId1".getBytes(PROVIDER_ENCODING), generate(64)); + EncryptedDataKey encryptedDataKey2 = new KeyBlob(AWS_KMS_PROVIDER_ID, "KeyId2".getBytes(PROVIDER_ENCODING), generate(64)); SecretKey secretKey = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); when(dataKeyEncryptionDao.decryptDataKey(encryptedDataKey1, ALGORITHM_SUITE, ENCRYPTION_CONTEXT)) diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java deleted file mode 100644 index e8bd05477..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/KmsUtilsTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.kms; - -import com.amazonaws.encryptionsdk.exception.MalformedArnException; -import com.amazonaws.services.kms.AWSKMS; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.junit.jupiter.api.Assertions.*; - -@ExtendWith(MockitoExtension.class) -class KmsUtilsTest { - - private static final String VALID_ARN = "arn:aws:kms:us-east-1:999999999999:key/01234567-89ab-cdef-fedc-ba9876543210"; - private static final String VALID_ALIAS_ARN = "arn:aws:kms:us-east-1:999999999999:alias/MyCryptoKey"; - private static final String VALID_ALIAS = "alias/MyCryptoKey"; - private static final String VALID_RAW_KEY_ID = "01234567-89ab-cdef-fedc-ba9876543210"; - - @Mock - private AWSKMS client; - - - @Test - void testGetClientByArn() { - assertEquals(client, KmsUtils.getClientByArn(VALID_ARN, s -> client)); - assertEquals(client, KmsUtils.getClientByArn(VALID_ALIAS_ARN, s -> client)); - assertEquals(client, KmsUtils.getClientByArn(VALID_ALIAS, s -> client)); - assertThrows(MalformedArnException.class, () -> KmsUtils.getClientByArn("arn:invalid", s -> client)); - assertEquals(client, KmsUtils.getClientByArn(VALID_RAW_KEY_ID, s -> client)); - } - - @Test - void testIsArnWellFormed() { - assertTrue(KmsUtils.isArnWellFormed(VALID_ARN)); - assertTrue(KmsUtils.isArnWellFormed(VALID_ALIAS_ARN)); - assertTrue(KmsUtils.isArnWellFormed(VALID_ALIAS)); - assertFalse(KmsUtils.isArnWellFormed(VALID_RAW_KEY_ID)); - assertFalse(KmsUtils.isArnWellFormed("arn:invalid")); - - } -} diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/XCompatKmsDecryptTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/XCompatKmsDecryptTest.java deleted file mode 100644 index f7e7c40f2..000000000 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/XCompatKmsDecryptTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.encryptionsdk.kms; - -import static org.junit.Assert.assertArrayEquals; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.StringUtils; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.CryptoResult; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -@RunWith(Parameterized.class) -public class XCompatKmsDecryptTest { - private String plaintextFileName; - private String ciphertextFileName; - private String kmsKeyId; - - public XCompatKmsDecryptTest(String plaintextFileName, String ciphertextFileName, String kmsKeyId) { - this.plaintextFileName = plaintextFileName; - this.ciphertextFileName = ciphertextFileName; - this.kmsKeyId = kmsKeyId; - } - - @Parameters(name="{index}: testDecryptFromFile({0}, {1}, {2})") - public static Collection data() throws Exception { - String baseDirName; - baseDirName = System.getProperty("staticCompatibilityResourcesDir"); - if (baseDirName == null) { - baseDirName = - XCompatKmsDecryptTest.class.getProtectionDomain().getCodeSource().getLocation().getPath() + - "aws_encryption_sdk_resources"; - } - - List testCases_ = new ArrayList(); - - String ciphertextManifestName = StringUtils.join( - new String[]{ - baseDirName, - "manifests", - "ciphertext.manifest" - }, - File.separator - ); - File ciphertextManifestFile = new File(ciphertextManifestName); - - if (!ciphertextManifestFile.exists()) { - return Collections.emptyList(); - } - - ObjectMapper ciphertextManifestMapper = new ObjectMapper(); - Map ciphertextManifest = ciphertextManifestMapper.readValue( - ciphertextManifestFile, - new TypeReference>(){} - ); - - List> testCases = (List>)ciphertextManifest.get("test_cases"); - for (Map testCase : testCases) { - Map plaintext = (Map)testCase.get("plaintext"); - Map ciphertext = (Map)testCase.get("ciphertext"); - - List> masterKeys = (List>)testCase.get("master_keys"); - for (Map masterKey : masterKeys) { - String providerId = (String) masterKey.get("provider_id"); - if (providerId.equals("aws-kms") && (boolean)masterKey.get("decryptable")) { - testCases_.add(new Object[] { - baseDirName + File.separator + plaintext.get("filename"), - baseDirName + File.separator + ciphertext.get("filename"), - (String)masterKey.get("key_id") - }); - break; - } - } - } - return testCases_; - } - - @Test - public void testDecryptFromFile() throws Exception { - AwsCrypto crypto = new AwsCrypto(); - final KmsMasterKeyProvider masterKeyProvider = new KmsMasterKeyProvider(kmsKeyId); - byte ciphertextBytes[] = Files.readAllBytes(Paths.get(ciphertextFileName)); - byte plaintextBytes[] = Files.readAllBytes(Paths.get(plaintextFileName)); - final CryptoResult decryptResult = crypto.decryptData( - masterKeyProvider, - ciphertextBytes - ); - assertArrayEquals(plaintextBytes, (byte[])decryptResult.getResult()); - } -} \ No newline at end of file diff --git a/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java index 444908478..ea5f67e26 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/model/DecryptionMaterialsTest.java @@ -41,8 +41,8 @@ class DecryptionMaterialsTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("testKey", "testValue"); - private static final KeyringTrace KEYRING_TRACE = new KeyringTrace(); private static final KeyringTraceEntry KEYRING_TRACE_ENTRY = new KeyringTraceEntry("Namespace", "Name", KeyringTraceFlag.ENCRYPTED_DATA_KEY); + private static final KeyringTrace KEYRING_TRACE = new KeyringTrace(Collections.singletonList(KEYRING_TRACE_ENTRY)); private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); private static PublicKey VERIFICATION_KEY; @@ -83,9 +83,9 @@ void testInvalidPlaintextDataKey() { .setTrailingSignatureKey(VERIFICATION_KEY) .build(); assertThrows(IllegalArgumentException.class, () -> materials - .setCleartextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); + .withCleartextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); assertThrows(IllegalArgumentException.class, () -> materials - .setCleartextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); + .withCleartextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); } @Test @@ -105,22 +105,22 @@ void testToBuilder() { } @Test - void testSetPlaintextDataKey() { - DecryptionMaterials materials = DecryptionMaterials.newBuilder() + void testWithPlaintextDataKey() { + final DecryptionMaterials materials = DecryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setTrailingSignatureKey(VERIFICATION_KEY) .build(); - assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(null, KEYRING_TRACE_ENTRY)); - assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, null)); + assertThrows(NullPointerException.class, () -> materials.withCleartextDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.withCleartextDataKey(PLAINTEXT_DATA_KEY, null)); - materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); - assertEquals(PLAINTEXT_DATA_KEY, materials.getCleartextDataKey()); - assertEquals(PLAINTEXT_DATA_KEY, materials.getDataKey().getKey()); - assertEquals(1, materials.getKeyringTrace().getEntries().size()); - assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); + final DecryptionMaterials newMaterials = materials.withCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(PLAINTEXT_DATA_KEY, newMaterials.getCleartextDataKey()); + assertEquals(PLAINTEXT_DATA_KEY, newMaterials.getDataKey().getKey()); + assertEquals(1, newMaterials.getKeyringTrace().getEntries().size()); + assertEquals(KEYRING_TRACE_ENTRY, newMaterials.getKeyringTrace().getEntries().get(0)); - assertThrows(IllegalStateException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); + assertThrows(IllegalStateException.class, () -> newMaterials.withCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); } @Test diff --git a/src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java b/src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java index 54c89ac59..ee06a089c 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/model/EncryptionMaterialsTest.java @@ -45,8 +45,8 @@ class EncryptionMaterialsTest { private static final CryptoAlgorithm ALGORITHM_SUITE = CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384; private static final Map ENCRYPTION_CONTEXT = Collections.singletonMap("testKey", "testValue"); - private static final KeyringTrace KEYRING_TRACE = new KeyringTrace(); private static final KeyringTraceEntry KEYRING_TRACE_ENTRY = new KeyringTraceEntry("Namespace", "Name", KeyringTraceFlag.ENCRYPTED_DATA_KEY); + private static final KeyringTrace KEYRING_TRACE = new KeyringTrace(Collections.singletonList(KEYRING_TRACE_ENTRY)); private static final SecretKey PLAINTEXT_DATA_KEY = new SecretKeySpec(generate(ALGORITHM_SUITE.getDataKeyLength()), ALGORITHM_SUITE.getDataKeyAlgo()); @Mock private static KeyBlob ENCRYPTED_DATA_KEY; @@ -91,9 +91,9 @@ void testInvalidPlaintextDataKey() { .setTrailingSignatureKey(SIGNING_KEY) .build(); assertThrows(IllegalArgumentException.class, () -> materials - .setCleartextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); + .withCleartextDataKey(wrongAlgorithm, KEYRING_TRACE_ENTRY)); assertThrows(IllegalArgumentException.class, () -> materials - .setCleartextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); + .withCleartextDataKey(wrongLength, KEYRING_TRACE_ENTRY)); } @Test @@ -114,38 +114,38 @@ void testToBuilder() { } @Test - void testAddEncryptedDataKey() { + void testWithEncryptedDataKey() { EncryptionMaterials materials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setTrailingSignatureKey(SIGNING_KEY) .build(); - assertThrows(NullPointerException.class, () -> materials.addEncryptedDataKey(null, KEYRING_TRACE_ENTRY)); - assertThrows(NullPointerException.class, () -> materials.addEncryptedDataKey(ENCRYPTED_DATA_KEY, null)); + assertThrows(NullPointerException.class, () -> materials.withEncryptedDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.withEncryptedDataKey(ENCRYPTED_DATA_KEY, null)); - materials.addEncryptedDataKey(ENCRYPTED_DATA_KEY, KEYRING_TRACE_ENTRY); - assertEquals(1, materials.getEncryptedDataKeys().size()); - assertEquals(ENCRYPTED_DATA_KEY, materials.getEncryptedDataKeys().get(0)); - assertEquals(1, materials.getKeyringTrace().getEntries().size()); - assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); + EncryptionMaterials newMaterials = materials.withEncryptedDataKey(ENCRYPTED_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(1, newMaterials.getEncryptedDataKeys().size()); + assertEquals(ENCRYPTED_DATA_KEY, newMaterials.getEncryptedDataKeys().get(0)); + assertEquals(1, newMaterials.getKeyringTrace().getEntries().size()); + assertEquals(KEYRING_TRACE_ENTRY, newMaterials.getKeyringTrace().getEntries().get(0)); } @Test - void testSetPlaintextDataKey() { + void testWithPlaintextDataKey() { EncryptionMaterials materials = EncryptionMaterials.newBuilder() .setAlgorithm(ALGORITHM_SUITE) .setTrailingSignatureKey(SIGNING_KEY) .build(); - assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(null, KEYRING_TRACE_ENTRY)); - assertThrows(NullPointerException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, null)); + assertThrows(NullPointerException.class, () -> materials.withCleartextDataKey(null, KEYRING_TRACE_ENTRY)); + assertThrows(NullPointerException.class, () -> materials.withCleartextDataKey(PLAINTEXT_DATA_KEY, null)); - materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); - assertEquals(PLAINTEXT_DATA_KEY, materials.getCleartextDataKey()); - assertEquals(1, materials.getKeyringTrace().getEntries().size()); - assertEquals(KEYRING_TRACE_ENTRY, materials.getKeyringTrace().getEntries().get(0)); + EncryptionMaterials newMaterials = materials.withCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY); + assertEquals(PLAINTEXT_DATA_KEY, newMaterials.getCleartextDataKey()); + assertEquals(1, newMaterials.getKeyringTrace().getEntries().size()); + assertEquals(KEYRING_TRACE_ENTRY, newMaterials.getKeyringTrace().getEntries().get(0)); - assertThrows(IllegalStateException.class, () -> materials.setCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); + assertThrows(IllegalStateException.class, () -> newMaterials.withCleartextDataKey(PLAINTEXT_DATA_KEY, KEYRING_TRACE_ENTRY)); } @Test From 8705f16675d3e8294ee2e2b3ed64678bb97781d8 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Wed, 12 Feb 2020 09:55:51 -0800 Subject: [PATCH 09/18] Update Readme for Keyrings (#156) * Update Readme for Keyrings --- README.md | 100 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 0387070cc..1a4831a09 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # AWS Encryption SDK for Java -The AWS Encryption SDK enables secure client-side encryption. It uses cryptography best practices to protect your data and the encryption keys used to protect that data. Each data object is protected with a unique data encryption key (DEK), and the DEK is protected with a key encryption key (KEK) called a *master key*. The encrypted DEK is combined with the encrypted data into a single [encrypted message](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/message-format.html), so you don't need to keep track of the DEKs for your data. The SDK supports master keys in [AWS Key Management Service](https://aws.amazon.com/kms/) (KMS), and it also provides APIs to define and use other master key providers. The SDK provides methods for encrypting and decrypting strings, byte arrays, and byte streams. For details, see the [example code][examples] and the [Javadoc](https://aws.github.io/aws-encryption-sdk-java/javadoc/). +The AWS Encryption SDK is a client-side encryption library designed to make it easy for everyone to encrypt and decrypt data using industry standards and best practices. It enables you to focus on the core functionality of your application, rather than on how to best encrypt and decrypt your data. -For more details about the design and architecture of the SDK, see the [official documentation](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/). +For details about the design, architecture and usage of the SDK, see the [official documentation](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/), [example code][examples] and the [Javadoc](https://aws.github.io/aws-encryption-sdk-java/javadoc/). ## Getting Started @@ -54,7 +54,7 @@ You can get the latest release from Maven: com.amazonaws aws-encryption-sdk-java - 1.6.1 + 1.7.0 ``` @@ -63,65 +63,81 @@ You can get the latest release from Maven: The following code sample demonstrates how to get started: 1. Instantiate the SDK. -2. Define the master key provider. +2. Setup a KMS keyring. 3. Encrypt and decrypt data. ```java -// This sample code encrypts and then decrypts a string using a KMS CMK. -// You provide the KMS key ARN and plaintext string as arguments. +// This sample code encrypts and then decrypts data using an AWS Key Management Service (AWS KMS) customer master key (CMK). package com.amazonaws.crypto.examples; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.CryptoResult; -import com.amazonaws.encryptionsdk.kms.KmsMasterKey; -import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -public class StringExample { - private static String keyArn; - private static String data; +public class BasicEncryptionExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); public static void main(final String[] args) { - keyArn = args[0]; - data = args[1]; + encryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); + } - // Instantiate the SDK + static void encryptAndDecrypt(final AwsKmsCmkId keyArn) { + // 1. Instantiate the SDK final AwsCrypto crypto = new AwsCrypto(); - // Set up the master key provider - final KmsMasterKeyProvider prov = new KmsMasterKeyProvider(keyArn); + // 2. Instantiate a KMS keyring. Supply the key ARN for the generator key that generates a + // data key. While using a key ARN is a best practice, for encryption operations you can also + // use an alias name or alias ARN. + final Keyring keyring = StandardKeyrings.awsKms(keyArn); - // Encrypt the data + // 3. Create an encryption context + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. // - // NOTE: Encrypted data should have associated encryption context - // to protect integrity. For this example, just use a placeholder - // value. For more information about encryption context, see - // https://amzn.to/1nSbe9X (blogs.aws.amazon.com) - final Map context = Collections.singletonMap("Example", "String"); - - final String ciphertext = crypto.encryptString(prov, data, context).getResult(); - System.out.println("Ciphertext: " + ciphertext); - - // Decrypt the data - final CryptoResult decryptResult = crypto.decryptString(prov, ciphertext); - // Check the encryption context (and ideally the master key) to - // ensure this is the expected ciphertext - if (!decryptResult.getMasterKeyIds().get(0).equals(keyArn)) { - throw new IllegalStateException("Wrong key id!"); + // For more information see: https://amzn.to/1nSbe9X (blogs.aws.amazon.com) + final Map encryptionContext = Collections.singletonMap("Example", "String"); + + // 4. Encrypt the data with the keyring and encryption context + final AwsCryptoResult encryptResult = crypto.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(EXAMPLE_DATA).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data. You can use the same keyring to encrypt and decrypt, but for decryption + // the key IDs must be in the key ARN format. + final AwsCryptoResult decryptResult = crypto.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + + // 6. To verify the CMK that was actually used in the decrypt operation, inspect the keyring trace. + if(!decryptResult.getKeyringTrace().getEntries().get(0).getKeyName().equals(keyArn.toString())) { + throw new IllegalStateException("Wrong key ID!"); } - // The SDK may add information to the encryption context, so check to - // ensure all of the values are present - for (final Map.Entry e : context.entrySet()) { - if (!e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey()))) { - throw new IllegalStateException("Wrong Encryption Context!"); - } - } + // 7. To verify that the encryption context used to decrypt the data was the encryption context you expected, + // examine the encryption context in the result. This helps to ensure that you decrypted the ciphertext that + // you intended. + // + // When verifying, test that your expected encryption context is a subset of the actual encryption context, + // not an exact match. The Encryption SDK adds the signing key to the encryption context when appropriate. + assert decryptResult.getEncryptionContext().get("Example").equals("String"); - // The data is correct, so output it. - System.out.println("Decrypted: " + decryptResult.getResult()); + // 8. Verify that the decrypted plaintext matches the original plaintext + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); } } ``` From f4973ec91598d662079159a3187d142c0e82d5b3 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Thu, 20 Feb 2020 10:49:46 -0800 Subject: [PATCH 10/18] Adding a simple example of data key caching (#158) --- .../SimpleDataKeyCachingExample.java | 103 ++++++++++++++++++ .../SimpleDataKeyCachingExampleTest.java | 29 +++++ 2 files changed, 132 insertions(+) create mode 100644 src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java new file mode 100644 index 000000000..fa14d7702 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java @@ -0,0 +1,103 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples.datakeycaching; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoMaterialsManager; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.caching.CryptoMaterialsCache; +import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + *

+ * Encrypts a string using an AWS KMS customer master key (CMK) and data key caching + * + *

+ * Arguments: + *

    + *
  1. KMS CMK ARN: To find the Amazon Resource Name of your AWS KMS customer master key (CMK), + * see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class SimpleDataKeyCachingExample { + + /* + * The maximum number of data keys in the cache (required). + * When the cache is full, the oldest entry is evicted to + * make room for a newer one. + */ + private static final int CAPACITY = 10; + + /* + * The maximum number of messages encrypted under a single data key. + * This value is optional, but you should configure the lowest practical value. + */ + private static final int MAX_ENTRY_MESSAGES = 100; + + /* + * The time in seconds that an entry is cached (required). + * The cache actively removes entries that have exceeded the thresholds. + */ + private static final int MAX_ENTRY_AGE_IN_SECONDS = 60; + + /* + * Example data to encrypt + */ + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + encryptWithCaching(AwsKmsCmkId.fromString(args[0])); + } + + static byte[] encryptWithCaching(AwsKmsCmkId kmsCmkArn) { + + // Instantiate the SDK + final AwsCrypto crypto = new AwsCrypto(); + + // Create an encryption context + final Map encryptionContext = Collections.singletonMap("purpose", "test"); + + // Create a keyring + final Keyring keyring = StandardKeyrings.awsKms(kmsCmkArn); + + // Create a cache + final CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(CAPACITY); + + // Create a caching CMM + final CryptoMaterialsManager cachingCmm = + CachingCryptoMaterialsManager.newBuilder() + .withKeyring(keyring) + .withCache(cache) + .withMaxAge(MAX_ENTRY_AGE_IN_SECONDS, TimeUnit.SECONDS) + .withMessageUseLimit(MAX_ENTRY_MESSAGES) + .build(); + + // When the call to encrypt specifies a caching CMM, + // the encryption operation uses the data key cache + return crypto.encrypt(EncryptRequest.builder() + .cryptoMaterialsManager(cachingCmm) + .plaintext(EXAMPLE_DATA) + .encryptionContext(encryptionContext) + .build()).getResult(); + } +} diff --git a/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java new file mode 100644 index 000000000..e1ebb3be9 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java @@ -0,0 +1,29 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.crypto.examples.datakeycaching; + +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag(TestUtils.TAG_INTEGRATION) +class SimpleDataKeyCachingExampleTest { + + @Test + void testEncryptWithCaching() { + SimpleDataKeyCachingExample.encryptWithCaching(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); + } +} From d15f988dc26cb16eccecb735fac3cfb0104da3f6 Mon Sep 17 00:00:00 2001 From: Greg Rubin Date: Fri, 6 Mar 2020 12:25:03 -0800 Subject: [PATCH 11/18] Have Lambda example cache data keys in a static field. --- .../LambdaDecryptAndWriteExample.java | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java index 6cc926d26..2419ebb51 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java @@ -40,10 +40,46 @@ public class LambdaDecryptAndWriteExample implements RequestHandler { private static final long MAX_ENTRY_AGE_MILLISECONDS = 600000; private static final int MAX_CACHE_ENTRIES = 100; + + // For best caching performance in Lambda, we want our cache to be a static final field + // configured by environment variables. + // However, to make this example easier for people to experiment with, we also provide a non-static + // version with simpler configuration. + private static final CachingCryptoMaterialsManager CACHING_CRYPTO_MATERIALS_MANAGER; + private static final String TABLE_NAME = System.getProperty("TABLE_NAME"); + + static { + final String cmkArn = System.getProperty("CMK_ARN"); + CACHING_CRYPTO_MATERIALS_MANAGER = CachingCryptoMaterialsManager.newBuilder() + .withKeyring(StandardKeyrings.awsKms(AwsKmsCmkId.fromString(cmkArn))) + .withCache(new LocalCryptoMaterialsCache(MAX_CACHE_ENTRIES)) + .withMaxAge(MAX_ENTRY_AGE_MILLISECONDS, TimeUnit.MILLISECONDS) + .build(); + } + private final CachingCryptoMaterialsManager cachingMaterialsManager_; private final AwsCrypto crypto_; private final Table table_; + /** + * No-argument constructor for use with Lambda. + * + * This is almost equivalent to calling {@link #LambdaDecryptAndWriteExample(String, String)} with + * {@code cmkArn = System.getProperty("CMK_ARN")} + * and + * {@code tableName = System.getProperty("TABLE_NAME")} + * respectively. + * The only difference is that this constructor will re-use the underlying cache across all instances + * for better cache performance. + * + * @see #LambdaDecryptAndWriteExample(String, String) + * @see #CACHING_CRYPTO_MATERIALS_MANAGER + * @see #TABLE_NAME + */ + public LambdaDecryptAndWriteExample() { + this(CACHING_CRYPTO_MATERIALS_MANAGER, TABLE_NAME); + } + /** * This code doesn't set the max bytes or max message security thresholds that are enforced * only on data keys used for encryption. @@ -52,15 +88,21 @@ public class LambdaDecryptAndWriteExample implements RequestHandler Date: Mon, 9 Mar 2020 18:09:17 -0700 Subject: [PATCH 12/18] Define an enum for the RSA padding scheme (#162) * Define an enum for the RSA padding scheme * Making RsaPaddingScheme an inner class of RawRsaKeyringBuilder * Updated comment regarding transformation --- .../examples/EscrowedEncryptExample.java | 5 ++- .../examples/RawRsaKeyringDecryptExample.java | 3 +- .../examples/RawRsaKeyringEncryptExample.java | 3 +- .../encryptionsdk/keyrings/RawRsaKeyring.java | 5 ++- .../keyrings/RawRsaKeyringBuilder.java | 39 ++++++++++++++++--- .../RawRsaKeyringEncryptExampleTest.java | 3 +- .../encryptionsdk/TestVectorRunner.java | 29 ++++++++++---- .../MasterKeyProviderCompatibilityTest.java | 7 ++-- .../keyrings/RawRsaKeyringTest.java | 5 ++- .../model/RsaPaddingSchemeTest.java | 31 +++++++++++++++ 10 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 src/test/java/com/amazonaws/encryptionsdk/model/RsaPaddingSchemeTest.java diff --git a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java index 32be46198..bb12f737d 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java @@ -17,6 +17,7 @@ import com.amazonaws.encryptionsdk.DecryptRequest; import com.amazonaws.encryptionsdk.EncryptRequest; import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; @@ -94,7 +95,7 @@ private static byte[] standardEncrypt(final AwsKmsCmkId kmsArn, final PublicKey .keyNamespace("Escrow") .keyName("Escrow") .publicKey(publicEscrowKey) - .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) .build(); // 4. Combine the providers into a single MultiKeyring @@ -138,7 +139,7 @@ private static byte[] escrowDecrypt(final byte[] cipherText, final PrivateKey pr .keyNamespace("Escrow") .keyName("Escrow") .privateKey(privateEscrowKey) - .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) .build(); // 3. Decrypt the data with the keyring diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java index a5f3ba488..b2cf3010e 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java @@ -17,6 +17,7 @@ import com.amazonaws.encryptionsdk.AwsCryptoResult; import com.amazonaws.encryptionsdk.DecryptRequest; import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import java.security.KeyPair; @@ -35,7 +36,7 @@ public static byte[] decrypt(byte[] ciphertext, KeyPair keyPair) { final Keyring keyring = StandardKeyrings.rawRsaBuilder() .keyNamespace("ExampleKeyNamespace") .keyName("ExampleKeyName") - .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) .privateKey(keyPair.getPrivate()).build(); // 3. Decrypt the ciphertext with the keyring diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java index 480ac0092..82a8001f4 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java @@ -17,6 +17,7 @@ import com.amazonaws.encryptionsdk.AwsCryptoResult; import com.amazonaws.encryptionsdk.EncryptRequest; import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import java.nio.charset.StandardCharsets; @@ -39,7 +40,7 @@ public static byte[] encrypt(PublicKey publicKey) { final Keyring keyring = StandardKeyrings.rawRsaBuilder() .keyNamespace("ExampleKeyNamespace") .keyName("ExampleKeyName") - .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) .publicKey(publicKey).build(); // 3. Create an encryption context diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java index d41f6d683..9b8a7b453 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyring.java @@ -15,6 +15,7 @@ import com.amazonaws.encryptionsdk.EncryptedDataKey; import com.amazonaws.encryptionsdk.internal.JceKeyCipher; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import java.security.PrivateKey; import java.security.PublicKey; @@ -28,8 +29,8 @@ */ class RawRsaKeyring extends RawKeyring { - RawRsaKeyring(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, String transformation) { - super(keyNamespace, keyName, JceKeyCipher.rsa(publicKey, privateKey, transformation)); + RawRsaKeyring(String keyNamespace, String keyName, PublicKey publicKey, PrivateKey privateKey, RsaPaddingScheme rsaPaddingScheme) { + super(keyNamespace, keyName, JceKeyCipher.rsa(publicKey, privateKey, rsaPaddingScheme.getTransformation())); } @Override diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java index 190143954..f3e2a9aed 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringBuilder.java @@ -21,7 +21,7 @@ public class RawRsaKeyringBuilder { private String keyName; private PublicKey publicKey; private PrivateKey privateKey; - private String wrappingAlgorithm; + private RsaPaddingScheme paddingScheme; private RawRsaKeyringBuilder() { // Use RawRsaKeyringBuilder.standard() or StandardKeyrings.rawRsa() to instantiate @@ -81,13 +81,13 @@ public RawRsaKeyringBuilder privateKey(PrivateKey privateKey) { } /** - * The RSA algorithm to use with this keyring (required). + * The RSA padding scheme to use with this keyring (required). * - * @param wrappingAlgorithm The algorithm + * @param paddingScheme The RSA padding scheme * @return The RawRsaKeyringBuilder, for method chaining */ - public RawRsaKeyringBuilder wrappingAlgorithm(String wrappingAlgorithm) { - this.wrappingAlgorithm = wrappingAlgorithm; + public RawRsaKeyringBuilder paddingScheme(RsaPaddingScheme paddingScheme) { + this.paddingScheme = paddingScheme; return this; } @@ -97,6 +97,33 @@ public RawRsaKeyringBuilder wrappingAlgorithm(String wrappingAlgorithm) { * @return The {@link Keyring} instance */ public Keyring build() { - return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, wrappingAlgorithm); + return new RawRsaKeyring(keyNamespace, keyName, publicKey, privateKey, paddingScheme); + } + + public enum RsaPaddingScheme { + + PKCS1("RSA/ECB/PKCS1Padding"), + OAEP_SHA1_MGF1("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"), + OAEP_SHA256_MGF1("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"), + OAEP_SHA384_MGF1("RSA/ECB/OAEPWithSHA-384AndMGF1Padding"), + OAEP_SHA512_MGF1("RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + private final String transformation; + + RsaPaddingScheme(String transformation) { + this.transformation = transformation; + } + + /** + * The Cipher transformation standard name as specified in + * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Cipher + * Note: In all cases the hash function used with MGF1 is the + * same as the hash function used directly with the message. + * + * @return The transformation name + */ + public String getTransformation() { + return transformation; + } } } diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java index 25a8bddf2..0329b4e48 100644 --- a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java +++ b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java @@ -16,6 +16,7 @@ import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.DecryptRequest; import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import org.junit.jupiter.api.Test; @@ -38,7 +39,7 @@ void testEncrypt() throws Exception { .keyNamespace("ExampleKeyNamespace") .keyName("ExampleKeyName") .privateKey(keyPair.getPrivate()) - .wrappingAlgorithm("RSA/ECB/OAEPWithSHA-512AndMGF1Padding") + .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) .build(); diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java index 8a8e8048e..078dff781 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java @@ -16,6 +16,7 @@ import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.encryptionsdk.jce.JceMasterKey; import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; @@ -171,15 +172,27 @@ private static TestCase parseTest(String testName, Map data, Map .wrappingKey((SecretKey) key.key).build()); mks.add(JceMasterKey.getInstance((SecretKey) key.key, provId, key.keyId, "AES/GCM/NoPadding")); } else if ("rsa".equals(algorithm)) { - String transformation = "RSA/ECB/"; + final RsaPaddingScheme paddingScheme; final String padding = mkEntry.get("padding-algorithm"); if ("pkcs1".equals(padding)) { - transformation += "PKCS1Padding"; + paddingScheme = RsaPaddingScheme.PKCS1; } else if ("oaep-mgf1".equals(padding)) { - final String hashName = mkEntry.get("padding-hash") - .replace("sha", "sha-") - .toUpperCase(); - transformation += "OAEPWith" + hashName + "AndMGF1Padding"; + switch(mkEntry.get("padding-hash")) { + case "sha1": + paddingScheme = RsaPaddingScheme.OAEP_SHA1_MGF1; + break; + case "sha256": + paddingScheme = RsaPaddingScheme.OAEP_SHA256_MGF1; + break; + case "sha384": + paddingScheme = RsaPaddingScheme.OAEP_SHA384_MGF1; + break; + case "sha512": + paddingScheme = RsaPaddingScheme.OAEP_SHA512_MGF1; + break; + default: + throw new IllegalArgumentException("Unsupported padding hash:" + mkEntry.get("padding-hash")); + } } else { throw new IllegalArgumentException("Unsupported padding:" + padding); } @@ -197,8 +210,8 @@ private static TestCase parseTest(String testName, Map data, Map .privateKey(unwrappingKey) .keyNamespace(provId) .keyName(key.keyId) - .wrappingAlgorithm(transformation).build()); - mks.add(JceMasterKey.getInstance(wrappingKey, unwrappingKey, provId, key.keyId, transformation)); + .paddingScheme(paddingScheme).build()); + mks.add(JceMasterKey.getInstance(wrappingKey, unwrappingKey, provId, key.keyId, paddingScheme.getTransformation())); } else { throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); } diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java index 00a1d6f58..ffe759989 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/MasterKeyProviderCompatibilityTest.java @@ -23,6 +23,7 @@ import com.amazonaws.encryptionsdk.internal.RandomBytesGenerator; import com.amazonaws.encryptionsdk.internal.Utils; import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; import com.amazonaws.encryptionsdk.kms.KmsMasterKey; @@ -74,19 +75,19 @@ void testRawAesKeyringCompatibility() { @Test void testRawRsaKeyringCompatibility() throws Exception { - final String wrappingAlgorithm = "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"; + final RsaPaddingScheme paddingScheme = RsaPaddingScheme.OAEP_SHA512_MGF1; final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); kg.initialize(4096); KeyPair keyPair = kg.generateKeyPair(); JceMasterKey mkp = JceMasterKey.getInstance(keyPair.getPublic(), keyPair.getPrivate(), KEY_NAMESPACE, KEY_NAME, - wrappingAlgorithm); + paddingScheme.getTransformation()); Keyring keyring = StandardKeyrings.rawRsaBuilder() .keyNamespace(KEY_NAMESPACE) .keyName(KEY_NAME) .publicKey(keyPair.getPublic()) .privateKey(keyPair.getPrivate()) - .wrappingAlgorithm(wrappingAlgorithm) + .paddingScheme(paddingScheme) .build(); testCompatibility(keyring, mkp); diff --git a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java index a5ee08079..59fff4da1 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/keyrings/RawRsaKeyringTest.java @@ -14,6 +14,7 @@ package com.amazonaws.encryptionsdk.keyrings; import com.amazonaws.encryptionsdk.EncryptedDataKey; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; import com.amazonaws.encryptionsdk.model.DecryptionMaterials; import com.amazonaws.encryptionsdk.model.EncryptionMaterials; import com.amazonaws.encryptionsdk.model.KeyBlob; @@ -37,7 +38,7 @@ class RawRsaKeyringTest { - private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + private static final RsaPaddingScheme PADDING_SCHEME = RsaPaddingScheme.PKCS1; private static RawRsaKeyring keyring; @BeforeAll @@ -45,7 +46,7 @@ static void setup() throws Exception { final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); final KeyPair keyPair = keyPairGenerator.generateKeyPair(); - keyring = new RawRsaKeyring(KEYNAMESPACE, KEYNAME, keyPair.getPublic(), keyPair.getPrivate(), TRANSFORMATION); + keyring = new RawRsaKeyring(KEYNAMESPACE, KEYNAME, keyPair.getPublic(), keyPair.getPrivate(), PADDING_SCHEME); } @Test diff --git a/src/test/java/com/amazonaws/encryptionsdk/model/RsaPaddingSchemeTest.java b/src/test/java/com/amazonaws/encryptionsdk/model/RsaPaddingSchemeTest.java new file mode 100644 index 000000000..30a96988c --- /dev/null +++ b/src/test/java/com/amazonaws/encryptionsdk/model/RsaPaddingSchemeTest.java @@ -0,0 +1,31 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.model; + +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; +import org.junit.jupiter.api.Test; + +import javax.crypto.Cipher; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class RsaPaddingSchemeTest { + + @Test + void testCipherInitialization() throws Exception { + for (RsaPaddingScheme paddingScheme : RsaPaddingScheme.values()) { + assertNotNull(Cipher.getInstance(paddingScheme.getTransformation())); + } + } +} From c60ad5939d0f3a8d1204776ee75bc15b335e484b Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Mon, 16 Mar 2020 13:02:19 -0700 Subject: [PATCH 13/18] Making client suppliers composable (#163) * Make client suppliers composable * Refactor to move suppliers to StandardAwsKmsClientSuppliers class * Using ConcurrentHashMap for the client cache to be thread safe --- .../MultiRegionRecordPusherExample.java | 12 +- .../keyrings/AwsKmsKeyringBuilder.java | 3 +- .../keyrings/StandardKeyrings.java | 9 +- .../kms/AwsKmsClientSupplier.java | 174 ------------ .../kms/StandardAwsKmsClientSuppliers.java | 257 ++++++++++++++++++ .../encryptionsdk/TestVectorRunner.java | 3 +- ...=> StandardAwsKmsClientSuppliersTest.java} | 104 ++++--- 7 files changed, 316 insertions(+), 246 deletions(-) create mode 100644 src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java rename src/test/java/com/amazonaws/encryptionsdk/kms/{AwsKmsClientSupplierTest.java => StandardAwsKmsClientSuppliersTest.java} (80%) diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java index d7d7e071f..62ff53d45 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java @@ -13,7 +13,6 @@ package com.amazonaws.crypto.examples.datakeycaching; -import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.AwsCryptoResult; @@ -22,8 +21,8 @@ import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache; import com.amazonaws.encryptionsdk.keyrings.Keyring; import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; import com.amazonaws.regions.Region; import com.amazonaws.services.kinesis.AmazonKinesis; import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; @@ -32,7 +31,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -73,10 +71,10 @@ public MultiRegionRecordPusherExample(final Region[] regions, final String kmsAl .build()); keyrings.add(StandardKeyrings.awsKmsBuilder() - .awsKmsClientSupplier(AwsKmsClientSupplier.builder() - .credentialsProvider(credentialsProvider) - .allowedRegions(Collections.singleton(region.getName())) - .build()) + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers + .allowRegionsBuilder(Collections.singleton(region.getName())) + .baseClientSupplier(StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(credentialsProvider).build()).build()) .generatorKeyId(AwsKmsCmkId.fromString(kmsAliasName)).build()); } diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java index 483a13506..5a67334e8 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/AwsKmsKeyringBuilder.java @@ -16,6 +16,7 @@ import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; import java.util.List; @@ -111,7 +112,7 @@ public AwsKmsKeyringBuilder generatorKeyId(AwsKmsCmkId generatorKeyId) { */ public Keyring build() { if (awsKmsClientSupplier == null) { - awsKmsClientSupplier = AwsKmsClientSupplier.builder().build(); + awsKmsClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); } return new AwsKmsKeyring(DataKeyEncryptionDao.awsKms(awsKmsClientSupplier, grantTokens), diff --git a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java index 9cf37ea12..1a3fc16f6 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java +++ b/src/main/java/com/amazonaws/encryptionsdk/keyrings/StandardKeyrings.java @@ -13,8 +13,8 @@ package com.amazonaws.encryptionsdk.keyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; import java.util.Arrays; import java.util.List; @@ -80,15 +80,14 @@ public static AwsKmsKeyringBuilder awsKmsBuilder() { * AWS KMS Discovery keyrings do not specify any CMKs to decrypt with, and thus will attempt to decrypt * using any encrypted data key in an encrypted message. AWS KMS Discovery keyrings do not perform encryption. *

- * To create an AWS KMS Regional Discovery Keyring, construct an {@link AwsKmsClientSupplier} using - * {@link AwsKmsClientSupplier#builder()} to specify which regions to include/exclude. + * To create an AWS KMS Regional Discovery Keyring, use {@link StandardAwsKmsClientSuppliers#allowRegionsBuilder} or + * {@link StandardAwsKmsClientSuppliers#denyRegionsBuilder} to specify which regions to include/exclude. *

* For example, to include only CMKs in the us-east-1 region: *
      * StandardKeyrings.awsKmsDiscovery()
      *             .awsKmsClientSupplier(
-     *                     AwsKmsClientSupplier.builder()
-     *                     .allowedRegions(Collections.singleton("us-east-1")).build())
+     *                     StandardAwsKmsClientSuppliers.allowRegionsBuilder(Collections.singleton("us-east-1")).build()
      *             .build();
      * 
* diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java index ceccb5936..92a0bfee1 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplier.java @@ -13,26 +13,13 @@ package com.amazonaws.encryptionsdk.kms; -import com.amazonaws.ClientConfiguration; import com.amazonaws.arn.Arn; -import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; import com.amazonaws.services.kms.AWSKMS; -import com.amazonaws.services.kms.AWSKMSClientBuilder; -import com.amazonaws.services.kms.model.AWSKMSException; import javax.annotation.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Proxy; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import static java.util.Objects.requireNonNull; -import static org.apache.commons.lang3.Validate.isTrue; -import static org.apache.commons.lang3.Validate.notEmpty; /** * Represents a function that accepts an AWS region and returns an {@code AWSKMS} client for that region. The @@ -51,15 +38,6 @@ public interface AwsKmsClientSupplier { */ AWSKMS getClient(@Nullable String regionId) throws UnsupportedRegionException; - /** - * Gets a Builder for constructing an AwsKmsClientSupplier - * - * @return The builder - */ - static Builder builder() { - return new Builder(AWSKMSClientBuilder.standard()); - } - /** * Parses region from the given key id (if possible) and passes that region to the * given clientSupplier to produce an {@code AWSKMS} client. @@ -78,156 +56,4 @@ static AWSKMS getClientByKeyId(AwsKmsCmkId keyId, AwsKmsClientSupplier clientSup return clientSupplier.getClient(null); } - - /** - * Builder to construct an AwsKmsClientSupplier given various - * optional settings. - */ - class Builder { - - private AWSCredentialsProvider credentialsProvider; - private ClientConfiguration clientConfiguration; - private Set allowedRegions = Collections.emptySet(); - private Set excludedRegions = Collections.emptySet(); - private boolean clientCachingEnabled = true; - private final Map clientsCache = new HashMap<>(); - private static final Set AWSKMS_METHODS = new HashSet<>(); - private AWSKMSClientBuilder awsKmsClientBuilder; - - static { - AWSKMS_METHODS.add("generateDataKey"); - AWSKMS_METHODS.add("encrypt"); - AWSKMS_METHODS.add("decrypt"); - } - - Builder(AWSKMSClientBuilder awsKmsClientBuilder) { - this.awsKmsClientBuilder = awsKmsClientBuilder; - } - - public AwsKmsClientSupplier build() { - isTrue(allowedRegions.isEmpty() || excludedRegions.isEmpty(), - "Either allowed regions or excluded regions may be set, not both."); - - return regionId -> { - if (!allowedRegions.isEmpty() && !allowedRegions.contains(regionId)) { - throw new UnsupportedRegionException(String.format("Region %s is not in the list of allowed regions %s", - regionId, allowedRegions)); - } - - if (excludedRegions.contains(regionId)) { - throw new UnsupportedRegionException(String.format("Region %s is in the list of excluded regions %s", - regionId, excludedRegions)); - } - - if (clientsCache.containsKey(regionId)) { - return clientsCache.get(regionId); - } - - if (credentialsProvider != null) { - awsKmsClientBuilder = awsKmsClientBuilder.withCredentials(credentialsProvider); - } - - if (clientConfiguration != null) { - awsKmsClientBuilder = awsKmsClientBuilder.withClientConfiguration(clientConfiguration); - } - - if (regionId != null) { - awsKmsClientBuilder = awsKmsClientBuilder.withRegion(regionId); - } - - AWSKMS client = awsKmsClientBuilder.build(); - - if (clientCachingEnabled) { - client = newCachingProxy(client, regionId); - } - - return client; - }; - } - - /** - * Sets the AWSCredentialsProvider used by the client. - * - * @param credentialsProvider New AWSCredentialsProvider to use. - */ - public Builder credentialsProvider(AWSCredentialsProvider credentialsProvider) { - this.credentialsProvider = credentialsProvider; - return this; - } - - /** - * Sets the ClientConfiguration to be used by the client. - * - * @param clientConfiguration Custom configuration to use. - */ - public Builder clientConfiguration(ClientConfiguration clientConfiguration) { - this.clientConfiguration = clientConfiguration; - return this; - } - - /** - * Sets the AWS regions that the client supplier is permitted to use. - * - * @param regions The set of regions. - */ - public Builder allowedRegions(Set regions) { - notEmpty(regions, "At least one region is required"); - this.allowedRegions = Collections.unmodifiableSet(new HashSet<>(regions)); - return this; - } - - /** - * Sets the AWS regions that the client supplier is not permitted to use. - * - * @param regions The set of regions. - */ - public Builder excludedRegions(Set regions) { - requireNonNull(regions, "regions is required"); - this.excludedRegions = Collections.unmodifiableSet(new HashSet<>(regions)); - return this; - } - - /** - * When set to false, disables the AWSKMS client for each region from being cached and reused. - * By default, client caching is enabled. - * - * @param enabled Whether or not caching is enabled. - */ - public Builder clientCaching(boolean enabled) { - this.clientCachingEnabled = enabled; - return this; - } - - /** - * Creates a proxy for the AWSKMS client that will populate the client into the client cache - * after an AWS KMS method successfully completes or an AWS KMS exception occurs. This is to prevent a - * a malicious user from causing a local resource DOS by sending ciphertext with a large number - * of spurious regions, thereby filling the cache with regions and exhausting resources. - * - * @param client The client to proxy - * @param regionId The region the client is associated with - * @return The proxy - */ - private AWSKMS newCachingProxy(AWSKMS client, String regionId) { - return (AWSKMS) Proxy.newProxyInstance( - AWSKMS.class.getClassLoader(), - new Class[]{AWSKMS.class}, - (proxy, method, methodArgs) -> { - try { - final Object result = method.invoke(client, methodArgs); - if (AWSKMS_METHODS.contains(method.getName())) { - clientsCache.put(regionId, client); - } - return result; - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof AWSKMSException && - AWSKMS_METHODS.contains(method.getName())) { - clientsCache.put(regionId, client); - } - - throw e.getTargetException(); - } - }); - } - } } diff --git a/src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java b/src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java new file mode 100644 index 000000000..24b71c45c --- /dev/null +++ b/src/main/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliers.java @@ -0,0 +1,257 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except + * in compliance with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package com.amazonaws.encryptionsdk.kms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; +import com.amazonaws.services.kms.AWSKMS; +import com.amazonaws.services.kms.AWSKMSClientBuilder; +import com.amazonaws.services.kms.model.AWSKMSException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang3.Validate.notEmpty; + +/** + * Factory methods for instantiating the standard {@code AwsKmsClientSupplier}s provided by the AWS Encryption SDK. + */ +public class StandardAwsKmsClientSuppliers { + + /** + * A builder to construct the default AwsKmsClientSupplier that will create and cache clients + * for any region. Credentials and client configuration may be specified if necessary. + * + * @return The builder + */ + public static DefaultAwsKmsClientSupplierBuilder defaultBuilder() { + return new DefaultAwsKmsClientSupplierBuilder(AWSKMSClientBuilder.standard()); + } + + /** + * A builder to construct an AwsKmsClientSupplier that will + * only supply clients for a given set of AWS regions. + * + * @param allowedRegions the AWS regions that the client supplier is allowed to supply clients for + * @return The builder + */ + public static AllowRegionsAwsKmsClientSupplierBuilder allowRegionsBuilder(Set allowedRegions) { + return new AllowRegionsAwsKmsClientSupplierBuilder(allowedRegions); + } + + /** + * A builder to construct an AwsKmsClientSupplier that will + * supply clients for all AWS regions except the given set of regions. + * + * @param deniedRegions the AWS regions that the client supplier will not supply clients for + * @return The builder + */ + public static DenyRegionsAwsKmsClientSupplierBuilder denyRegionsBuilder(Set deniedRegions) { + return new DenyRegionsAwsKmsClientSupplierBuilder(deniedRegions); + } + + + /** + * Builder to construct an AwsKmsClientSupplier that will create and cache clients + * for any region. CredentialProvider and ClientConfiguration are optional and may + * be configured if necessary. + */ + public static class DefaultAwsKmsClientSupplierBuilder { + + private AWSCredentialsProvider credentialsProvider; + private ClientConfiguration clientConfiguration; + private final Map clientsCache = new ConcurrentHashMap<>(); + private static final Set AWSKMS_METHODS = new HashSet<>(); + private AWSKMSClientBuilder awsKmsClientBuilder; + private static final String NULL_REGION = "null-region"; + + static { + AWSKMS_METHODS.add("generateDataKey"); + AWSKMS_METHODS.add("encrypt"); + AWSKMS_METHODS.add("decrypt"); + } + + DefaultAwsKmsClientSupplierBuilder(AWSKMSClientBuilder awsKmsClientBuilder) { + this.awsKmsClientBuilder = awsKmsClientBuilder; + } + + public AwsKmsClientSupplier build() { + + return regionId -> { + + if(regionId == null) { + regionId = NULL_REGION; + } + + if (clientsCache.containsKey(regionId)) { + return clientsCache.get(regionId); + } + + if (credentialsProvider != null) { + awsKmsClientBuilder = awsKmsClientBuilder.withCredentials(credentialsProvider); + } + + if (clientConfiguration != null) { + awsKmsClientBuilder = awsKmsClientBuilder.withClientConfiguration(clientConfiguration); + } + + if (!regionId.equals(NULL_REGION)) { + awsKmsClientBuilder = awsKmsClientBuilder.withRegion(regionId); + } + + return newCachingProxy(awsKmsClientBuilder.build(), regionId); + }; + } + + /** + * Sets the AWSCredentialsProvider used by the client. + * + * @param credentialsProvider New AWSCredentialsProvider to use. + */ + public DefaultAwsKmsClientSupplierBuilder credentialsProvider(AWSCredentialsProvider credentialsProvider) { + this.credentialsProvider = credentialsProvider; + return this; + } + + /** + * Sets the ClientConfiguration to be used by the client. + * + * @param clientConfiguration Custom configuration to use. + */ + public DefaultAwsKmsClientSupplierBuilder clientConfiguration(ClientConfiguration clientConfiguration) { + this.clientConfiguration = clientConfiguration; + return this; + } + + /** + * Creates a proxy for the AWSKMS client that will populate the client into the client cache + * after an AWS KMS method successfully completes or an AWS KMS exception occurs. This is to prevent a + * a malicious user from causing a local resource DOS by sending ciphertext with a large number + * of spurious regions, thereby filling the cache with regions and exhausting resources. + * + * @param client The client to proxy + * @param regionId The region the client is associated with + * @return The proxy + */ + private AWSKMS newCachingProxy(AWSKMS client, String regionId) { + return (AWSKMS) Proxy.newProxyInstance( + AWSKMS.class.getClassLoader(), + new Class[]{AWSKMS.class}, + (proxy, method, methodArgs) -> { + try { + final Object result = method.invoke(client, methodArgs); + if (AWSKMS_METHODS.contains(method.getName())) { + clientsCache.put(regionId, client); + } + return result; + } catch (InvocationTargetException e) { + if (e.getTargetException() instanceof AWSKMSException && + AWSKMS_METHODS.contains(method.getName())) { + clientsCache.put(regionId, client); + } + + throw e.getTargetException(); + } + }); + } + } + + /** + * An AwsKmsClientSupplier that will only supply clients for a given set of AWS regions. + */ + public static class AllowRegionsAwsKmsClientSupplierBuilder { + + private final Set allowedRegions; + private AwsKmsClientSupplier baseClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); + + private AllowRegionsAwsKmsClientSupplierBuilder(Set allowedRegions) { + notEmpty(allowedRegions, "At least one region is required"); + requireNonNull(baseClientSupplier, "baseClientSupplier is required"); + + this.allowedRegions = allowedRegions; + } + + /** + * Constructs the AwsKmsClientSupplier. + * + * @return The AwsKmsClientSupplier + */ + public AwsKmsClientSupplier build() { + return regionId -> { + + if (!allowedRegions.contains(regionId)) { + throw new UnsupportedRegionException(String.format("Region %s is not in the set of allowed regions %s", + regionId, allowedRegions)); + } + + return baseClientSupplier.getClient(regionId); + }; + } + + /** + * Sets the client supplier that will supply the client if the region is allowed. + * + * @param baseClientSupplier the client supplier that will supply the client if the region is allowed. + */ + public AllowRegionsAwsKmsClientSupplierBuilder baseClientSupplier(AwsKmsClientSupplier baseClientSupplier) { + this.baseClientSupplier = baseClientSupplier; + return this; + } + } + + /** + * A client supplier that supplies clients for any region except the specified AWS regions. + */ + public static class DenyRegionsAwsKmsClientSupplierBuilder { + + private final Set deniedRegions; + private AwsKmsClientSupplier baseClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); + + private DenyRegionsAwsKmsClientSupplierBuilder(Set deniedRegions) { + notEmpty(deniedRegions, "At least one region is required"); + requireNonNull(baseClientSupplier, "baseClientSupplier is required"); + + this.deniedRegions = deniedRegions; + } + + /** + * Sets the client supplier that will supply the client if the region is allowed. + * + * @param baseClientSupplier the client supplier that will supply the client if the region is allowed. + */ + public DenyRegionsAwsKmsClientSupplierBuilder baseClientSupplier(AwsKmsClientSupplier baseClientSupplier) { + this.baseClientSupplier = baseClientSupplier; + return this; + } + + public AwsKmsClientSupplier build() { + + return regionId -> { + + if (deniedRegions.contains(regionId)) { + throw new UnsupportedRegionException(String.format("Region %s is in the set of denied regions %s", + regionId, deniedRegions)); + } + + return baseClientSupplier.getClient(regionId); + }; + } + } +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java index 078dff781..89f21479b 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java +++ b/src/test/java/com/amazonaws/encryptionsdk/TestVectorRunner.java @@ -21,6 +21,7 @@ import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; import com.amazonaws.util.IOUtils; import com.fasterxml.jackson.core.type.TypeReference; @@ -64,7 +65,7 @@ class TestVectorRunner { // We save the files in memory to avoid repeatedly retrieving them. // This won't work if the plaintexts are too large or numerous private static final Map cachedData = new HashMap<>(); - private static final AwsKmsClientSupplier awsKmsClientSupplier = AwsKmsClientSupplier.builder() + private static final AwsKmsClientSupplier awsKmsClientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() .credentialsProvider(new DefaultAWSCredentialsProviderChain()) .build(); private static final KmsMasterKeyProvider kmsProv = KmsMasterKeyProvider diff --git a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplierTest.java b/src/test/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliersTest.java similarity index 80% rename from src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplierTest.java rename to src/test/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliersTest.java index 748077d40..dec01d5a3 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/kms/AwsKmsClientSupplierTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/kms/StandardAwsKmsClientSuppliersTest.java @@ -16,6 +16,7 @@ import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.encryptionsdk.exception.UnsupportedRegionException; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers.DefaultAwsKmsClientSupplierBuilder; import com.amazonaws.services.kms.AWSKMS; import com.amazonaws.services.kms.AWSKMSClientBuilder; import com.amazonaws.services.kms.model.AWSKMSException; @@ -39,7 +40,7 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -class AwsKmsClientSupplierTest { +class StandardAwsKmsClientSuppliersTest { @Mock AWSKMSClientBuilder kmsClientBuilder; @Mock AWSKMS awskms; @@ -58,7 +59,7 @@ void testCredentialsAndClientConfiguration() { when(kmsClientBuilder.withCredentials(credentialsProvider)).thenReturn(kmsClientBuilder); when(kmsClientBuilder.build()).thenReturn(awskms); - AwsKmsClientSupplier supplier = new AwsKmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplier = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) .credentialsProvider(credentialsProvider) .clientConfiguration(clientConfiguration) .build(); @@ -70,64 +71,9 @@ void testCredentialsAndClientConfiguration() { verify(kmsClientBuilder).build(); } - @Test - void testAllowedAndExcludedRegions() { - AwsKmsClientSupplier supplierWithDefaultValues = new AwsKmsClientSupplier.Builder(kmsClientBuilder) - .build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); - - AwsKmsClientSupplier supplierWithAllowed = new AwsKmsClientSupplier.Builder(kmsClientBuilder) - .allowedRegions(Collections.singleton(REGION_1)) - .build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertNotNull(supplierWithAllowed.getClient(REGION_1)); - assertThrows(UnsupportedRegionException.class, () -> supplierWithAllowed.getClient(REGION_2)); - - AwsKmsClientSupplier supplierWithExcluded = new AwsKmsClientSupplier.Builder(kmsClientBuilder) - .excludedRegions(Collections.singleton(REGION_1)) - .build(); - - when(kmsClientBuilder.withRegion(REGION_2)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - assertThrows(UnsupportedRegionException.class, () -> supplierWithExcluded.getClient(REGION_1)); - assertNotNull(supplierWithExcluded.getClient(REGION_2)); - - assertThrows(IllegalArgumentException.class, () -> new AwsKmsClientSupplier.Builder(kmsClientBuilder) - .allowedRegions(Collections.singleton(REGION_1)) - .excludedRegions(Collections.singleton(REGION_2)) - .build()); - } - - @Test - void testClientCachingDisabled() { - AwsKmsClientSupplier supplierCachingDisabled = new AwsKmsClientSupplier.Builder(kmsClientBuilder) - .clientCaching(false) - .build(); - - when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); - when(kmsClientBuilder.build()).thenReturn(awskms); - - AWSKMS uncachedClient = supplierCachingDisabled.getClient(REGION_1); - verify(kmsClientBuilder, times(1)).build(); - - when(awskms.encrypt(encryptRequest)).thenReturn(new EncryptResult()); - - uncachedClient.encrypt(encryptRequest); - supplierCachingDisabled.getClient(REGION_1); - verify(kmsClientBuilder, times(2)).build(); - } - @Test void testClientCaching() { - AwsKmsClientSupplier supplier = new AwsKmsClientSupplier.Builder(kmsClientBuilder) + AwsKmsClientSupplier supplier = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) .build(); when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); @@ -184,4 +130,46 @@ void testGetClientByKeyId() { assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(alias), s -> awskms)); assertEquals(awskms, AwsKmsClientSupplier.getClientByKeyId(AwsKmsCmkId.fromString(keyId), s -> awskms)); } + + @Test + void testAllowedRegions() { + AwsKmsClientSupplier supplierWithDefaultValues = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) + .build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); + + AwsKmsClientSupplier supplierWithAllowed = StandardAwsKmsClientSuppliers + .allowRegionsBuilder(Collections.singleton(REGION_1)) + .baseClientSupplier(new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder).build()).build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertNotNull(supplierWithAllowed.getClient(REGION_1)); + assertThrows(UnsupportedRegionException.class, () -> supplierWithAllowed.getClient(REGION_2)); + } + + @Test + void testDeniedRegions() { + AwsKmsClientSupplier supplierWithDefaultValues = new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder) + .build(); + + when(kmsClientBuilder.withRegion(REGION_1)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertNotNull(supplierWithDefaultValues.getClient(REGION_1)); + + AwsKmsClientSupplier supplierWithDenied = StandardAwsKmsClientSuppliers + .denyRegionsBuilder(Collections.singleton(REGION_1)) + .baseClientSupplier(new DefaultAwsKmsClientSupplierBuilder(kmsClientBuilder).build()).build(); + + when(kmsClientBuilder.withRegion(REGION_2)).thenReturn(kmsClientBuilder); + when(kmsClientBuilder.build()).thenReturn(awskms); + + assertThrows(UnsupportedRegionException.class, () -> supplierWithDenied.getClient(REGION_1)); + assertNotNull(supplierWithDenied.getClient(REGION_2)); + } } From 2d025a856eb2f70dc80457f8253a9e9611bf579e Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Mon, 30 Mar 2020 21:07:22 -0700 Subject: [PATCH 14/18] Adding new examples and example test runner (#165) * Adding new examples and example test runner to follow the format set in https://github.com/aws/aws-encryption-sdk-python/pull/219 * Updated wording and copyright notice * Adding periods * Adding NIST recommendation for RSA * Adding example for DER formatted RSA keys * Wording changes based on feedback --- src/examples/README.md | 114 ++++++++++++ .../examples/BasicEncryptionExample.java | 97 ----------- .../examples/EscrowedEncryptExample.java | 158 ----------------- .../examples/FileStreamingDefaults.java | 135 ++++++++++++++ .../crypto/examples/FileStreamingExample.java | 150 ---------------- .../examples/InMemoryStreamingDefaults.java | 103 +++++++++++ .../crypto/examples/OneStepDefaults.java | 83 +++++++++ .../crypto/examples/OneStepUnsigned.java | 97 +++++++++++ .../crypto/examples/RawAesKeyringExample.java | 100 ----------- .../examples/RawRsaKeyringDecryptExample.java | 57 ------ .../examples/RawRsaKeyringEncryptExample.java | 64 ------- .../keyring/awskms/CustomClientSupplier.java | 139 +++++++++++++++ .../keyring/awskms/CustomKmsClientConfig.java | 118 +++++++++++++ .../keyring/awskms/DiscoveryDecrypt.java | 102 +++++++++++ .../awskms/DiscoveryDecryptInRegionOnly.java | 118 +++++++++++++ .../DiscoveryDecryptWithPreferredRegions.java | 134 ++++++++++++++ .../keyring/awskms/MultipleRegions.java | 122 +++++++++++++ .../examples/keyring/awskms/SingleCmk.java | 92 ++++++++++ .../keyring/multi/AwsKmsWithEscrow.java | 149 ++++++++++++++++ .../examples/keyring/rawaes/RawAes.java | 101 +++++++++++ .../rawrsa/PublicPrivateKeySeparate.java | 147 ++++++++++++++++ .../examples/keyring/rawrsa/RawRsa.java | 112 ++++++++++++ .../keyring/rawrsa/RawRsaDerEncoded.java | 134 ++++++++++++++ .../legacy/BasicEncryptionExample.java | 81 +++++++++ .../legacy/EscrowedEncryptExample.java | 164 ++++++++++++++++++ .../examples/legacy/FileStreamingExample.java | 93 ++++++++++ .../LambdaDecryptAndWriteExample.java | 22 +-- .../MultiRegionRecordPusherExample.java | 24 +-- .../SimpleDataKeyCachingExample.java | 28 +-- .../examples/BasicEncryptionExampleTest.java | 29 ---- .../examples/EscrowedEncryptExampleTest.java | 31 ---- .../crypto/examples/ExamplesTest.java | 119 +++++++++++++ .../examples/FileStreamingExampleTest.java | 44 ----- .../examples/RawAesKeyringExampleTest.java | 24 --- .../RawRsaKeyringDecryptExampleTest.java | 37 ---- .../RawRsaKeyringEncryptExampleTest.java | 55 ------ .../SimpleDataKeyCachingExampleTest.java | 29 ---- 37 files changed, 2479 insertions(+), 927 deletions(-) create mode 100644 src/examples/README.md delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java delete mode 100644 src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java rename src/examples/java/com/amazonaws/crypto/examples/{datakeycaching => legacy}/LambdaDecryptAndWriteExample.java (87%) rename src/examples/java/com/amazonaws/crypto/examples/{datakeycaching => legacy}/MultiRegionRecordPusherExample.java (86%) rename src/examples/java/com/amazonaws/crypto/examples/{datakeycaching => legacy}/SimpleDataKeyCachingExample.java (78%) delete mode 100644 src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java delete mode 100644 src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java create mode 100644 src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java delete mode 100644 src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java delete mode 100644 src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java delete mode 100644 src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java delete mode 100644 src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java delete mode 100644 src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java diff --git a/src/examples/README.md b/src/examples/README.md new file mode 100644 index 000000000..b6e778219 --- /dev/null +++ b/src/examples/README.md @@ -0,0 +1,114 @@ +# AWS Encryption SDK Examples + +This section features examples that show you +how to use the AWS Encryption SDK. +We demonstrate how to use the encryption and decryption APIs +and how to set up some common configuration patterns. + +## APIs + +The AWS Encryption SDK provides two high-level APIs: +one-step APIs that process the entire operation in memory +and streaming APIs. + +You can find examples that demonstrate these APIs +in the [`examples`](./java/com/amazonaws/crypto/examples) directory. + +## Configuration + +To use the encryption and decryption APIs, +you need to describe how you want the library to protect your data keys. +You can do this by configuring +[keyrings](#keyrings) or [cryptographic materials managers](#cryptographic-materials-managers), +or by configuring [master key providers](#master-key-providers). +These examples will show you how to use the configuration tools that we include for you +and how to create some of your own. +We start with AWS KMS examples, then show how to use other wrapping keys. + +* Using AWS Key Management Service (AWS KMS) + * How to use one AWS KMS CMK + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java) + * How to use multiple AWS KMS CMKs in different regions + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java) + * How to decrypt when you don't know the CMK + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java) + * How to decrypt within a region + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java) + * How to decrypt with a preferred region but failover to others + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java) +* Using raw wrapping keys + * How to use a raw AES wrapping key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java) + * How to use a raw RSA wrapping key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java) + * How to encrypt with a raw RSA public key wrapping key without access to the private key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java) + * How to use a raw RSA wrapping key when the key is DER encoded + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java) +* Combining wrapping keys + * How to combine AWS KMS with an offline escrow key + * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java) + +### Keyrings + +Keyrings are the most common way for you to configure the AWS Encryption SDK. +They determine how the AWS Encryption SDK protects your data. +You can find these examples in ['examples/keyring`](./java/com/amazonaws/crypto/examples/keyring). + +### Cryptographic Materials Managers + +Keyrings define how your data keys are protected, +but there is more going on here than just protecting data keys. + +Cryptographic materials managers give you higher-level controls +over how the AWS Encryption SDK protects your data. +This can include things like +enforcing the use of certain algorithm suites or encryption context settings, +reusing data keys across messages, +or changing how you interact with keyrings. +You can find these examples in +[`examples/crypto_materials_manager`](./java/com/amazonaws/crypto/examples/cryptomaterialsmanager). + +### Master Key Providers + +Before there were keyrings, there were master key providers. +Master key providers were the original configuration structure +that we provided for defining how you want to protect your data keys. +Keyrings provide a simpler experience and often more powerful configuration options, +but if you need to use master key providers, +need help migrating from master key providers to keyrings, +or simply want to see the difference between these configuration experiences, +you can find these examples in [`examples/masterkeyprovider`](./java/com/amazonaws/crypto/examples/masterkeyprovider). + +## Legacy + +This section includes older examples, +including examples of using master keys and master key providers. +You can use them as a reference, +but we recommend looking at the newer examples, which explain the preferred ways of using this library. +You can find these examples in [`examples/legacy`](./java/com/amazonaws/crypto/examples/legacy). + +# Writing Examples + +If you want to contribute a new example, that's awesome! +To make sure that your example is tested in our CI, +please make sure that it meets the following requirements: + +1. The example MUST be a distinct class in the [`examples`](./java/com/amazonaws/crypto/examples) directory. +1. Each example file MUST contain exactly one example. +1. Each example file MUST contain a static method called `run` that runs the example. +1. If your `run` method needs any of the following inputs, + the parameters MUST have the following types: + * `com.amazonaws.encryptionsdk.kms.AwsKmsCmkId` : A single AWS KMS CMK ARN. + * NOTE: You can assume that automatically discovered credentials have + `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` permissions on this CMK. + * `List` : + A list of AWS KMS CMK ARNs to use for encrypting and decrypting data keys. + * NOTE: You can assume that automatically discovered credentials have + `kms:Encrypt` and `kms:Decrypt` permissions on these CMKs. + * `byte[]` : Plaintext data to encrypt. + * `java.io.File` : A path to a file containing plaintext to encrypt. + * NOTE: You can assume that you have write access to the parent directory + and that anything you do in that directory will be cleaned up + by our test runners. +1. Any additional parameters MUST be optional and nullable and not of the same type as the above parameters. diff --git a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java deleted file mode 100644 index ce1dcca3c..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/BasicEncryptionExample.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; - -/** - *

- * Encrypts and then decrypts data using an AWS Key Management Service (AWS KMS) customer master key. - * - *

- * Arguments: - *

    - *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master - * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html - *
- */ -public class BasicEncryptionExample { - - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) { - encryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); - } - - static void encryptAndDecrypt(final AwsKmsCmkId keyArn) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring. Supply the key ARN for the generator key - // that generates a data key. While using a key ARN is a best practice, - // for encryption operations you can also use an alias name or alias ARN. - final Keyring keyring = StandardKeyrings.awsKms(keyArn); - - // 3. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - - // 4. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt( - EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - final byte[] ciphertext = encryptResult.getResult(); - - // 5. Decrypt the data. You can use the same keyring to encrypt and decrypt, but for decryption - // the key IDs must be in the key ARN format. - final AwsCryptoResult decryptResult = crypto.decrypt( - DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 6. To verify the CMK that was actually used in the decrypt operation, inspect the keyring trace. - if(!decryptResult.getKeyringTrace().getEntries().get(0).getKeyName().equals(keyArn.toString())) { - throw new IllegalStateException("Wrong key ID!"); - } - - // 7. To verify that the encryption context used to decrypt the data was the encryption context you expected, - // examine the encryption context in the result. This helps to ensure that you decrypted the ciphertext that - // you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. The Encryption SDK adds the signing key to the encryption context when appropriate. - assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); - - // 8. Verify that the decrypted plaintext matches the original plaintext - assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java deleted file mode 100644 index bb12f737d..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/EscrowedEncryptExample.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; - -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Arrays; - -/** - *

- * Encrypts data using both KMS and an asymmetric key pair. - * - *

- * Arguments: - *

    - *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master - * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html - *
- * - * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but - * still want the option of decrypting your data offline independently of KMS. This sample - * demonstrates one way to do this. - * - * The sample encrypts data under both a KMS customer master key (CMK) and an "escrowed" RSA key pair - * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, - * at any time, you can use the private RSA key to decrypt the ciphertext independent of KMS. - * - * This sample uses a RawRsaKeyring to generate a RSA public-private key pair - * and saves the key pair in memory. In practice, you would store the private key in a secure offline - * location, such as an offline HSM, and distribute the public key to your development team. - * - */ -public class EscrowedEncryptExample { - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) throws GeneralSecurityException { - escrowEncryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); - } - - static void escrowEncryptAndDecrypt(AwsKmsCmkId kmsArn) throws GeneralSecurityException { - // This sample generates a new random key for each operation. - // In practice, you would distribute the public key and save the private key in secure storage. - final KeyPair escrowKeyPair = generateEscrowKeyPair(); - - // Encrypt the data under both a KMS Key and an escrowed RSA Key - byte[] encryptedData = standardEncrypt(kmsArn, escrowKeyPair.getPublic()); - - // Decrypt the data using the KMS Key - byte[] standardDecryptedData = standardDecrypt(kmsArn, encryptedData); - - // Decrypt the data using the escrowed RSA Key - byte[] escrowedDecryptedData = escrowDecrypt(encryptedData, escrowKeyPair.getPrivate()); - - // Verify both decrypted data instances are the same as the original plaintext - assert Arrays.equals(standardDecryptedData, EXAMPLE_DATA); - assert Arrays.equals(escrowedDecryptedData, EXAMPLE_DATA); - } - - private static byte[] standardEncrypt(final AwsKmsCmkId kmsArn, final PublicKey publicEscrowKey) { - // Encrypt with the KMS CMK and the escrowed public key - - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring, supplying the keyArn as the generator for generating a data key. - final Keyring kmsKeyring = StandardKeyrings.awsKms(kmsArn); - - // 3. Instantiate a RawRsaKeyring - // Because the user does not have access to the private escrow key, - // they do not provide the private key parameter. - final Keyring rsaKeyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("Escrow") - .keyName("Escrow") - .publicKey(publicEscrowKey) - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .build(); - - // 4. Combine the providers into a single MultiKeyring - final Keyring keyring = StandardKeyrings.multi(kmsKeyring, rsaKeyring); - - // 5. Encrypt the data with the keyring. - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - return crypto.encrypt(EncryptRequest.builder() - .keyring(keyring) - .plaintext(EXAMPLE_DATA).build()) - .getResult(); - } - - private static byte[] standardDecrypt(final AwsKmsCmkId kmsArn, final byte[] cipherText) { - // Decrypt with the KMS CMK - - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring, supplying the keyArn as the generator for generating a data key. - final Keyring kmsKeyring = StandardKeyrings.awsKms(kmsArn); - - // 4. Decrypt the data with the keyring. - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - return crypto.decrypt(DecryptRequest.builder() - .keyring(kmsKeyring) - .ciphertext(cipherText).build()).getResult(); - } - - private static byte[] escrowDecrypt(final byte[] cipherText, final PrivateKey privateEscrowKey) { - // You can decrypt the stream using only the private key. - // This method does not call KMS. - - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a RawRsaKeyring using the escrowed private key - final Keyring rsaKeyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("Escrow") - .keyName("Escrow") - .privateKey(privateEscrowKey) - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .build(); - - // 3. Decrypt the data with the keyring - // To simplify the code, we omit the encryption context. Production code should always - // use an encryption context. For an example, see the other SDK samples. - return crypto.decrypt(DecryptRequest.builder() - .keyring(rsaKeyring) - .ciphertext(cipherText).build()).getResult(); - } - - private static KeyPair generateEscrowKeyPair() throws GeneralSecurityException { - final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); - kg.initialize(4096); // Escrow keys should be very strong - return kg.generateKeyPair(); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java new file mode 100644 index 000000000..ae9b97e5f --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingDefaults.java @@ -0,0 +1,135 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoInputStream; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.util.IOUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This example shows how to use the streaming encrypt and decrypt APIs when working with files. + *

+ * One benefit of using the streaming API is that + * we can check the encryption context before we start decrypting. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + */ +public class FileStreamingDefaults { + + /** + * Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs with files. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintextFile Plaintext file to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final File sourcePlaintextFile) throws IOException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // We assume that you can also write to the directory containing the plaintext file, + // so that is where we will put all of the results. + final File encryptedFile = new File(sourcePlaintextFile.getPath() + ".encrypted"); + final File decryptedFile = new File(sourcePlaintextFile.getPath() + ".decrypted"); + encryptedFile.deleteOnExit(); + decryptedFile.deleteOnExit(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create the encrypting input stream with the keyring and encryption context. + // Because the file might be too large to load into memory, + // we stream the data, instead of loading it all at once. + try (final AwsCryptoInputStream encryptingStream = awsEncryptionSdk.createEncryptingInputStream( + CreateEncryptingInputStreamRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .inputStream(new FileInputStream(sourcePlaintextFile)).build())) { + + // Encrypt the data and write the ciphertext to the encrypted file. + try (FileOutputStream out = new FileOutputStream(encryptedFile)) { + IOUtils.copy(encryptingStream, out); + } + } + + // Demonstrate that the ciphertext and plaintext are different. + assert !compareFiles(sourcePlaintextFile, encryptedFile); + + // Create the decrypting input stream with the keyring. + try (final AwsCryptoInputStream decryptingStream = awsEncryptionSdk.createDecryptingInputStream( + CreateDecryptingInputStreamRequest.builder() + .keyring(keyring) + .inputStream(new FileInputStream(encryptedFile)).build())) { + + // Check the encryption context before we start decrypting. + // + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + final AwsCryptoResult decryptResult = decryptingStream.getAwsCryptoResult(); + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + + // Now that we are more confident that we will decrypt the right message, + // we can start decrypting. + try (FileOutputStream out = new FileOutputStream(decryptedFile)) { + IOUtils.copy(decryptingStream, out); + } + } + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert compareFiles(sourcePlaintextFile, decryptedFile); + } + + private static boolean compareFiles(File file1, File file2) throws IOException { + if (file1.length() != file2.length()) { + return false; + } + + try (BufferedReader file1Reader = Files.newBufferedReader(file1.toPath()); + BufferedReader file2Reader = Files.newBufferedReader(file2.toPath())) { + String file1Line; + String file2Line; + + while ((file1Line = file1Reader.readLine()) != null && + (file2Line = file2Reader.readLine()) != null) { + if (!Objects.equals(file1Line, file2Line)) { + return false; + } + } + + return true; + } + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java deleted file mode 100644 index 94cd38853..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/FileStreamingExample.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoInputStream; -import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; -import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.util.IOUtils; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.security.SecureRandom; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; - -/** - *

- * Encrypts and then decrypts a file under a random key. - * - *

- * Arguments: - *

    - *
  1. Name of file containing plaintext data to encrypt - *
- * - *

- * This program demonstrates using a standard Java {@link SecretKey} object in a {@link Keyring} to - * encrypt and decrypt streaming data. - */ -public class FileStreamingExample { - - public static void main(String[] args) throws IOException { - final File srcFile = new File(args[0]); - final File encryptedFile = new File(args[1]); - final File decryptedFile = new File(args[2]); - - encryptAndDecrypt(srcFile, encryptedFile, decryptedFile); - - } - - static void encryptAndDecrypt(final File srcFile, final File encryptedFile, final File decryptedFile) throws IOException { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Get an encryption key. In this example, we generate a random key. - // In practice, you would get a key from an existing key store. - final SecretKey cryptoKey = generateEncryptKey(); - - // 3. Instantiate a RawAesKeyring using the random key - final Keyring keyring = StandardKeyrings.rawAesBuilder() - .keyNamespace("Example") - .keyName("RandomKey") - .wrappingKey(cryptoKey) - .build(); - - // 4. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("Example", "FileStreaming"); - - // 5. Create the encrypting input stream with the keyring and encryption context. - // Because the file might be too large to load into memory, - // we stream the data, instead of loading it all at once. - try (final AwsCryptoInputStream encryptingStream = crypto.createEncryptingInputStream( - CreateEncryptingInputStreamRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .inputStream(new FileInputStream(srcFile)).build())) { - - // 6. Copy the encrypted data into the encrypted file. - try (FileOutputStream out = new FileOutputStream(encryptedFile)) { - IOUtils.copy(encryptingStream, out); - } - } - - // 7. Create the decrypting input stream with the keyring. - try (final AwsCryptoInputStream decryptingStream = crypto.createDecryptingInputStream( - CreateDecryptingInputStreamRequest.builder() - .keyring(keyring) - .inputStream(new FileInputStream(encryptedFile)).build())) { - - // 8. Verify that the encryption context that was used to decrypt the data is the one that you expect. - // This helps to ensure that the ciphertext that you decrypted was the one that you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. - assert "FileStreaming".equals(decryptingStream.getAwsCryptoResult().getEncryptionContext().get("Example")); - - // 9. Copy the plaintext data to a file - try (FileOutputStream out = new FileOutputStream(decryptedFile)) { - IOUtils.copy(decryptingStream, out); - } - } - - // 10. Compare the decrypted file to the original - compareFiles(decryptedFile, srcFile); - } - - /** - * In practice, this key would be saved in a secure location. - * In this example, we generate a new random key for each operation. - */ - private static SecretKey generateEncryptKey() { - SecureRandom rnd = new SecureRandom(); - byte[] rawKey = new byte[16]; // 128 bits - rnd.nextBytes(rawKey); - return new SecretKeySpec(rawKey, "AES"); - } - - private static void compareFiles(File file1, File file2) throws IOException { - assert file1.length() == file2.length(); - - try (BufferedReader file1Reader = Files.newBufferedReader(file1.toPath()); - BufferedReader file2Reader = Files.newBufferedReader(file2.toPath())) { - String file1Line; - String file2Line; - - while ((file1Line = file1Reader.readLine()) != null && - (file2Line = file2Reader.readLine()) != null) { - assert Objects.equals(file1Line, file2Line); - } - } - } - -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java new file mode 100644 index 000000000..94586f7a6 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/InMemoryStreamingDefaults.java @@ -0,0 +1,103 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoInputStream; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CreateDecryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.CreateEncryptingInputStreamRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.util.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to use the streaming encrypt and decrypt APIs on data in memory. + *

+ * One benefit of using the streaming API is that + * we can check the encryption context before we start decrypting. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + */ +public class InMemoryStreamingDefaults { + + /** + * Demonstrate an encrypt/decrypt cycle using the streaming encrypt/decrypt APIs in-memory. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) throws IOException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + ByteArrayInputStream inputStream = new ByteArrayInputStream(sourcePlaintext); + + // Create the encrypting input stream with the keyring and encryption context. + final AwsCryptoInputStream encryptingStream = awsEncryptionSdk.createEncryptingInputStream( + CreateEncryptingInputStreamRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .inputStream(inputStream).build()); + + // Encrypt the plaintext and write the results into the ciphertext. + ByteArrayOutputStream ciphertext = new ByteArrayOutputStream(); + IOUtils.copy(encryptingStream, ciphertext); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext.toByteArray(), sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoInputStream decryptingStream = awsEncryptionSdk.createDecryptingInputStream( + CreateDecryptingInputStreamRequest.builder() + .keyring(keyring) + .inputStream(new ByteArrayInputStream(ciphertext.toByteArray())).build()); + + // Check the encryption context before we start decrypting. + // + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + final AwsCryptoResult decryptResult = decryptingStream.getAwsCryptoResult(); + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + + // Now that we are more confident that we will decrypt the right message, + // we can start decrypting. + ByteArrayOutputStream decrypted = new ByteArrayOutputStream(); + IOUtils.copy(decryptingStream, decrypted); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted.toByteArray(), sourcePlaintext); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java b/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java new file mode 100644 index 000000000..5a9c4bc13 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/OneStepDefaults.java @@ -0,0 +1,83 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to use the one-step encrypt and decrypt APIs. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + */ +public class OneStepDefaults { + + /** + * Demonstrate an encrypt/decrypt cycle using the one-step encrypt/decrypt APIs. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java b/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java new file mode 100644 index 000000000..5d457d2f3 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/OneStepUnsigned.java @@ -0,0 +1,97 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to specify an algorithm suite + * when using the one-step encrypt and decrypt APIs. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + *

+ * The default algorithm suite includes a message-level signature + * that protects you from an attacker who has *decrypt* but not *encrypt* capability + * for a wrapping key that you used when encrypting a message + * under multiple wrapping keys. + *

+ * However, if all of your readers and writers have the same permissions, + * then this additional protection does not always add value. + * This example shows you how to select another algorithm suite + * that has all of the other properties of the default suite + * but does not include a message-level signature. + */ +public class OneStepUnsigned { + + /** + * Demonstrate requesting a specific algorithm suite through the one-step encrypt/decrypt APIs. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK and specify the algorithm suite that we want to use. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + awsEncryptionSdk.setEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java deleted file mode 100644 index 9af19ca54..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/RawAesKeyringExample.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.StandardCharsets; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -/** - *

- * Encrypts and then decrypts data using the Raw AES keyring. - */ -public class RawAesKeyringExample { - - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) { - encryptAndDecrypt(); - } - - static void encryptAndDecrypt() { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Get an encryption key. In this example, we generate a random key. - // In practice, you would get a key from an existing key store - final SecretKey cryptoKey = generateEncryptKey(); - - // 3. Instantiate a Raw AES keyring with the encryption key - final Keyring keyring = StandardKeyrings.rawAesBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .wrappingKey(cryptoKey).build(); - - // 4. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - - // 5. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt(EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - final byte[] ciphertext = encryptResult.getResult(); - - // 6. Decrypt the data - final AwsCryptoResult decryptResult = crypto.decrypt(DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 7. Verify that the encryption context that was used to decrypt the data is the one that you expect. - // This helps to ensure that the ciphertext that you decrypted was the one that you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. - assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); - - // 8. Verify that the decrypted plaintext matches the original plaintext - assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); - } - - /** - * In practice, this key would be saved in a secure location. - * For this demo, we generate a new random key for each operation. - */ - private static SecretKey generateEncryptKey() { - SecureRandom rnd = new SecureRandom(); - byte[] rawKey = new byte[16]; // 128 bits - rnd.nextBytes(rawKey); - return new SecretKeySpec(rawKey, "AES"); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java deleted file mode 100644 index b2cf3010e..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExample.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; - -import java.security.KeyPair; - -/** - *

- * Decrypts data using the Raw RSA keyring. - */ -public class RawRsaKeyringDecryptExample { - - public static byte[] decrypt(byte[] ciphertext, KeyPair keyPair) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a Raw RSA keyring with the private key - final Keyring keyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .privateKey(keyPair.getPrivate()).build(); - - // 3. Decrypt the ciphertext with the keyring - final AwsCryptoResult decryptResult = crypto.decrypt(DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 4. Verify that the encryption context that was used to decrypt the data is the one that you expect. - // This helps to ensure that the ciphertext that you decrypted was the one that you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. When appropriate, the Encryption SDK adds the signing key to the encryption context. - assert decryptResult.getEncryptionContext().get("ExampleContextKey").equals("ExampleContextValue"); - - // 5. Return the decrypted byte array result - return decryptResult.getResult(); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java deleted file mode 100644 index 82a8001f4..000000000 --- a/src/examples/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExample.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; - -import java.nio.charset.StandardCharsets; -import java.security.PublicKey; -import java.util.Collections; -import java.util.Map; - -/** - * Encrypts data using the Raw RSA keyring. - */ -public class RawRsaKeyringEncryptExample { - - static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static byte[] encrypt(PublicKey publicKey) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a Raw RSA keyring with the public key - final Keyring keyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .publicKey(publicKey).build(); - - // 3. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: - // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management - final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); - - // 4. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt(EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - - // 5. Return the encrypted byte array result - return encryptResult.getResult(); - } -} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java new file mode 100644 index 000000000..0e6ddcdf5 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomClientSupplier.java @@ -0,0 +1,139 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.auth.profile.ProfileCredentialsProvider; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.services.kms.AWSKMS; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * By default, the KMS keyring uses a client supplier that + * supplies a client with the same configuration for every region. + * If you need different behavior, you can write your own client supplier. + *

+ * You might use this + * if you need different credentials in different AWS regions. + * This might be because you are crossing partitions (ex: "aws" and "aws-cn") + * or if you are working with regions that have separate authentication silos + * like "ap-east-1" and "me-south-1". + *

+ * This example shows how to create a client supplier + * that will supply KMS clients with valid credentials for the target region + * even when working with regions that need different credentials. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For another example of how to use the KMS keyring with a custom client configuration, + * see the {@link CustomKmsClientConfig} example. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class CustomClientSupplier { + + static class MultiPartitionClientSupplier implements AwsKmsClientSupplier { + + private final AwsKmsClientSupplier chinaSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("china")).build(); + private final AwsKmsClientSupplier middleEastSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("middle-east")).build(); + private final AwsKmsClientSupplier hongKongSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .credentialsProvider(new ProfileCredentialsProvider("hong-kong")).build(); + private final AwsKmsClientSupplier defaultSupplier = StandardAwsKmsClientSuppliers.defaultBuilder().build(); + + /** + * Returns a client for the requested region. + * + * @param regionId The AWS region + * @return The AWSKMS client + */ + @Override + public AWSKMS getClient(String regionId) { + if (regionId.startsWith("cn-")) { + return chinaSupplier.getClient(regionId); + } else if (regionId.startsWith("me-")) { + return middleEastSupplier.getClient(regionId); + } else if (regionId.equals("ap-east-1")) { + return hongKongSupplier.getClient(regionId); + } else { + return defaultSupplier.getClient(regionId); + } + } + } + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with a custom client supplier. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKmsBuilder() + .generatorKeyId(awsKmsCmk) + .awsKmsClientSupplier(new MultiPartitionClientSupplier()) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java new file mode 100644 index 000000000..fd3af280d --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/CustomKmsClientConfig.java @@ -0,0 +1,118 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.internal.VersionInfo; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsClientSupplier; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * By default, the KMS keyring uses the default configurations + * for all KMS clients and uses the default discoverable credentials. + * If you need to change this configuration, + * you can configure the client supplier. + *

+ * This example shows how to use custom-configured clients with the KMS keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For another example of how to use the KMS keyring with a custom client configuration, + * see the {@link CustomKmsClientConfig} example. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class CustomKmsClientConfig { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with custom KMS client configuration. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Prepare your custom configuration values. + // + // Set your custom connection timeout value. + // https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html + final ClientConfiguration clientConfiguration = new ClientConfiguration() + .withConnectionTimeout(10000) // 10,000 milliseconds + .withUserAgentSuffix(VersionInfo.USER_AGENT); + + // Use your custom configuration values to configure your client supplier. + // For this example we will just use the default credentials provider + // but if you need to, you can set a custom credentials provider as well. + final AwsKmsClientSupplier clientSupplier = StandardAwsKmsClientSuppliers.defaultBuilder() + .clientConfiguration(clientConfiguration) + .build(); + + // Create the keyring that determines how your data keys are protected, + // providing the client supplier that you created. + final Keyring keyring = StandardKeyrings.awsKmsBuilder() + .generatorKeyId(awsKmsCmk) + .awsKmsClientSupplier(clientSupplier) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java new file mode 100644 index 000000000..64b031887 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java @@ -0,0 +1,102 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * When you give the KMS keyring specific key IDs it will use those CMKs and nothing else. + * This is true both on encrypt and on decrypt. + * However, sometimes you need more flexibility on decrypt, + * especially when you don't know which CMKs were used to encrypt a message. + * To address this need, you can use a KMS discovery keyring. + * The KMS discovery keyring does nothing on encrypt + * but attempts to decrypt *any* data keys that were encrypted under a KMS CMK. + *

+ * This example shows how to configure and use a KMS discovery keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS discovery keyring on decrypt, + * see the {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class DiscoveryDecrypt { + + /** + * Demonstrate configuring a KMS discovery keyring for decryption. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create a KMS discovery keyring to use on decrypt. + final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder().build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the KMS discovery keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(decryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java new file mode 100644 index 000000000..ad545cbec --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java @@ -0,0 +1,118 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.arn.Arn; +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singleton; + +/** + * When you give the KMS keyring specific key IDs it will use those CMKs and nothing else. + * This is true both on encrypt and on decrypt. + * However, sometimes you need more flexibility on decrypt, + * especially if you don't know which CMK was used to encrypt a message. + * To address this need, you can use a KMS discovery keyring. + * The KMS discovery keyring does nothing on encrypt + * but attempts to decrypt *any* data keys that were encrypted under a KMS CMK. + *

+ * However, sometimes you need to be a *bit* more restrictive than that. + * To address this need, you can use a client supplier that restricts the regions a KMS keyring can talk to. + *

+ * This example shows how to configure and use a KMS regional discovery keyring that is restricted to one region. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class DiscoveryDecryptInRegionOnly { + + /** + * Demonstrate configuring a KMS keyring to only work within a single region. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Extract the region from the CMK ARN. + final String decryptRegion = Arn.fromString(awsKmsCmk.toString()).getRegion(); + + // Create the KMS discovery keyring that we will use on decrypt. + // + // The client supplier that we specify here will only supply clients for the specified region. + // The keyring only attempts to decrypt data keys if it can get a client for that region, + // so this keyring will now ignore any data keys that were encrypted under a CMK in another region. + final Keyring decryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.allowRegionsBuilder(singleton(decryptRegion)).build()) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the KMS discovery keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(decryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java new file mode 100644 index 000000000..5068395d2 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptWithPreferredRegions.java @@ -0,0 +1,134 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.StandardAwsKmsClientSuppliers; +import com.amazonaws.services.kms.AWSKMSClientBuilder; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singleton; + +/** + * When you give the KMS keyring specific key IDs it will use those CMKs and nothing else. + * This is true both on encrypt and on decrypt. + * However, sometimes you need more flexibility on decrypt, + * especially if you might not know beforehand which CMK was used to encrypt a message. + * To address this need, you can use a KMS discovery keyring. + * The KMS discovery keyring will do nothing on encrypt + * but will attempt to decrypt *any* data keys that were encrypted under a KMS CMK. + *

+ * However, sometimes you need to be a *bit* more restrictive than that. + * To address this need, you can use a client supplier to restrict what regions a KMS keyring can talk to. + *

+ * A more complex but more common use-case is that you would *prefer* to stay within a region, + * but you would rather make calls to other regions than fail to decrypt the message. + * In this case, you want a keyring that will try to decrypt data keys in this region first, + * then try other regions. + *

+ * This example shows how to configure and use a multi-keyring with the KMS keyring + * to prefer the current AWS region while also failing over to other AWS regions. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * and {@link DiscoveryDecryptInRegionOnly} examples. + */ +public class DiscoveryDecryptWithPreferredRegions { + + /** + * Demonstrate configuring a keyring preferring a particular AWS region and failing over to others. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring encryptKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // To create our decrypt keyring, we need to know our current default AWS region. + final String localRegion = AWSKMSClientBuilder.standard().getRegion(); + + // Now, use that region name to create two KMS discovery keyrings: + // + // One that only works in the local region + final Keyring localRegionDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.allowRegionsBuilder(singleton(localRegion)).build()) + .build(); + // and one that will work in any other region but NOT the local region. + final Keyring otherRegionsDecryptKeyring = StandardKeyrings.awsKmsDiscoveryBuilder() + .awsKmsClientSupplier(StandardAwsKmsClientSuppliers.denyRegionsBuilder(singleton(localRegion)).build()) + .build(); + + // Finally, combine those two keyrings into a multi-keyring. + // + // The multi-keyring steps through its member keyrings in the order that you provide them, + // attempting to decrypt every encrypted data key with each keyring before moving on to the next keyring. + // Because of this, otherRegionsDecryptKeyring will not be called + // unless localRegionDecryptKeyring fails to decrypt every encrypted data key. + final Keyring decryptKeyring = StandardKeyrings.multi(localRegionDecryptKeyring, otherRegionsDecryptKeyring); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the multi-keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(decryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java new file mode 100644 index 000000000..8eabf06c6 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java @@ -0,0 +1,122 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This example shows how to configure and use a KMS keyring with CMKs in multiple regions. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with a single CMK, + * see the {@link SingleCmk} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class MultipleRegions { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with CMKs in multiple regions. + * + * @param awsKmsGeneratorCmk The ARN of an AWS KMS CMK that protects data keys + * @param awsKmsAdditionalCmks Additional ARNs of secondary KMS CMKs + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsGeneratorCmk, final List awsKmsAdditionalCmks, byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that will encrypt your data keys under all requested CMKs. + final Keyring manyCmksKeyring = StandardKeyrings.awsKmsBuilder() + .generatorKeyId(awsKmsGeneratorCmk) + .keyIds(awsKmsAdditionalCmks) + .build(); + + // Create keyrings that each only use one of the CMKs. + // We will use these later to demonstrate that any of the CMKs can be used to decrypt the message. + // + // We provide these in "keyIds" rather than "generatorKeyId" + // so that these keyrings cannot be used to generate a new data key. + // We will only be using them on decrypt. + final Keyring singleCmkKeyringThatGenerated = StandardKeyrings.awsKmsBuilder() + .keyIds(Collections.singletonList(awsKmsGeneratorCmk)) + .build(); + final Keyring singleCmkKeyringThatEncrypted = StandardKeyrings.awsKmsBuilder() + .keyIds(Collections.singletonList(awsKmsAdditionalCmks.get(0))) + .build(); + + // Encrypt your plaintext data using the keyring that uses all requests CMKs. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(manyCmksKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Verify that the header contains the expected number of encrypted data keys (EDKs). + // It should contain one EDK for each CMK. + assert encryptResult.getHeaders().getEncryptedKeyBlobCount() == awsKmsAdditionalCmks.size() + 1; + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data separately using the single-CMK keyrings. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult1 = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(singleCmkKeyringThatGenerated) + .ciphertext(ciphertext).build()); + final byte[] decrypted1 = decryptResult1.getResult(); + final AwsCryptoResult decryptResult2 = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(singleCmkKeyringThatEncrypted) + .ciphertext(ciphertext).build()); + final byte[] decrypted2 = decryptResult2.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted1, sourcePlaintext); + assert Arrays.equals(decrypted2, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult1.getEncryptionContext().get(k)); + assert v.equals(decryptResult2.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java new file mode 100644 index 000000000..b77824e83 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java @@ -0,0 +1,92 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example shows how to configure and use a KMS keyring with a single KMS CMK. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-kms-keyring + *

+ * For an example of how to use the KMS keyring with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For examples of how to use the KMS keyring with custom client configurations, + * see the {@link CustomClientSupplier} + * and {@link CustomKmsClientConfig} examples. + *

+ * For examples of how to use the KMS Discovery keyring on decrypt, + * see the {@link DiscoveryDecrypt}, + * {@link DiscoveryDecryptInRegionOnly}, + * and {@link DiscoveryDecryptWithPreferredRegions} examples. + */ +public class SingleCmk { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS keyring with a single CMK. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java new file mode 100644 index 000000000..541c017cb --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java @@ -0,0 +1,149 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.multi; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * One use-case that we have seen customers need is + * the ability to enjoy the benefits of AWS KMS during normal operation + * but retain the ability to decrypt encrypted messages without access to AWS KMS. + * This example shows how you can use the multi-keyring to achieve this + * by combining a KMS keyring with a raw RSA keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-multi-keyring + *

+ * For more examples of how to use the KMS keyring, see the keyring/awskms examples. + *

+ * For more examples of how to use the raw RSA keyring, see the keyring/rawrsa examples. + *

+ * In this example we generate a RSA keypair + * but in practice you would want to keep your private key in an HSM + * or other key management system. + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class AwsKmsWithEscrow { + + /** + * Demonstrate configuring a keyring to use an AWS KMS CMK and a RSA wrapping key. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the encrypt keyring that only has access to the public key. + final Keyring escrowEncryptKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .publicKey(keyPair.getPublic()) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Create the decrypt keyring that has access to the private key. + final Keyring escrowDecryptKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name MUST match the encrypt keyring. + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(keyPair.getPrivate()) + // The padding scheme MUST match the encrypt keyring. + .paddingScheme(RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Create the KMS keyring that you will use from decryption during normal operations. + final Keyring kmsKeyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Combine the KMS keyring and the escrow encrypt keyring using the multi-keyring. + final Keyring encryptKeyring = StandardKeyrings.multi(kmsKeyring, escrowEncryptKeyring); + + // Encrypt your plaintext data using the multi-keyring. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(encryptKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Verify that the header contains the expected number of encrypted data keys (EDKs). + // It should contain one EDK for KMS and one for the escrow key. + assert encryptResult.getHeaders().getEncryptedKeyBlobCount() == 2; + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data separately using the KMS keyring and the escrow decrypt keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptedKmsResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(kmsKeyring) + .ciphertext(ciphertext).build()); + final byte[] decryptedKms = decryptedKmsResult.getResult(); + final AwsCryptoResult decryptedEscrowResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(escrowDecryptKeyring) + .ciphertext(ciphertext).build()); + final byte[] decryptedEscrow = decryptedKmsResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decryptedKms, sourcePlaintext); + assert Arrays.equals(decryptedEscrow, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptedKmsResult.getEncryptionContext().get(k)); + assert v.equals(decryptedEscrowResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java new file mode 100644 index 000000000..9d79f7440 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java @@ -0,0 +1,101 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawaes; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This examples shows how to configure and use a raw AES keyring. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawAes { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw AES keyring. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an AES key to use with your keyring. + // + // In practice, you should get this key from a secure key management system such as an HSM. + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[32]; // 256 bits + rnd.nextBytes(rawKey); + SecretKey key = new SecretKeySpec(rawKey, "AES"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.rawAesBuilder() + // The key namespace and key name are defined by you + // and are used by the raw AES keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-aes-keyring + .keyNamespace("some managed raw keys") + .keyName("my AES wrapping key") + .wrappingKey(key) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java new file mode 100644 index 000000000..0bc31b191 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java @@ -0,0 +1,147 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.exception.AwsCryptoException; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * One of the benefits of asymmetric encryption + * is that you can encrypt with just the public key. + * This means that you can give someone the ability to encrypt + * without giving them the ability to decrypt. + *

+ * The raw RSA keyring supports encrypt-only operations + * when it only has access to a public key. + *

+ * This example shows how to construct and use the raw RSA keyring + * to encrypt with only the public key and decrypt with the private key. + *

+ * If your RSA key is in DER format, + * see the {@link RawRsaDerEncoded} example. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class PublicPrivateKeySeparate { + + /** + * Demonstrate an encrypt/decrypt cycle using separate public and private raw RSA keyrings. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the keyring that determines how your data keys are protected. + // + // Create the encrypt keyring that only has access to the public key. + final Keyring publicKeyKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .publicKey(keyPair.getPublic()) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Create the decrypt keyring that has access to the private key. + final Keyring privateKeyKeyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name MUST match the encrypt keyring. + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(keyPair.getPrivate()) + // The padding scheme MUST match the encrypt keyring. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Encrypt your plaintext data using the encrypt keyring. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(publicKeyKeyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Try to decrypt your encrypted data using the *encrypt* keyring. + // This demonstrates that you cannot decrypt using the public key. + try { + awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(publicKeyKeyring) + .ciphertext(ciphertext) + .build()); + throw new AssertionError("The public key can never decrypt!"); + } catch (AwsCryptoException ex) { + // The public key cannot decrypt. + // Reaching this point means everything is working as expected. + } + + // Decrypt your encrypted data using the decrypt keyring. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(privateKeyKeyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java new file mode 100644 index 000000000..3ca0a6c0e --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java @@ -0,0 +1,112 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This examples shows how to configure and use a raw RSA keyring using a pre-loaded RSA key pair. + *

+ * If your RSA key is in DER format, + * see the {@link RawRsaDerEncoded} example. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawRsa { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw RSA keyring. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(keyPair.getPrivate()) + .publicKey(keyPair.getPublic()) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java new file mode 100644 index 000000000..c74c80495 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsaDerEncoded.java @@ -0,0 +1,134 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.keyring.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; + +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * When you store RSA keys, you have to serialize them somehow. + *

+ * This example shows how to configure and use a raw RSA keyring using a DER-encoded RSA private key. + *

+ * The most commonly used encodings for RSA keys tend to be PEM and DER. + * For parsing PEM-encoded keys, see https://www.bouncycastle.org/docs/pkixdocs1.4/org/bouncycastle/openssl/PEMParser.html + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawRsaDerEncoded { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw RSA keyring loaded from a DER-encoded key. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your keyring. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Serialize the RSA keys to DER encoding. + // This or PEM encoding is likely to be what you get from your key management system in practice. + byte[] publicKeyEncoded = keyPair.getPublic().getEncoded(); + byte[] privateKeyEncoded = keyPair.getPrivate().getEncoded(); + + final KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + + // Deserialize the RSA private key. + final PrivateKey privateKey = keyFactory.generatePrivate( + new PKCS8EncodedKeySpec(privateKeyEncoded)); + + // Deserialize the RSA public key. + final PublicKey publicKey = keyFactory.generatePublic( + new X509EncodedKeySpec(publicKeyEncoded)); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.rawRsaBuilder() + // The key namespace and key name are defined by you + // and are used by the raw RSA keyring + // to determine whether it should attempt to decrypt + // an encrypted data key. + // + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/choose-keyring.html#use-raw-rsa-keyring + .keyNamespace("some managed raw keys") + .keyName("my RSA wrapping key") + .privateKey(privateKey) + .publicKey(publicKey) + // The padding scheme tells the raw RSA keyring + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + .paddingScheme(RawRsaKeyringBuilder.RsaPaddingScheme.OAEP_SHA256_MGF1) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same keyring you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .keyring(keyring) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java new file mode 100644 index 000000000..4093004ed --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/BasicEncryptionExample.java @@ -0,0 +1,81 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.legacy; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; + +/** + *

+ * Encrypts and then decrypts data using an AWS KMS customer master key. + * NOTE: Master key providers are deprecated and replaced by keyrings. + * We keep these older examples as reference material, + * but we recommend that you use the new examples in examples/keyring + * The new examples reflect our current guidance for using the library. + *

+ * Arguments: + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master + * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + *
+ */ +public class BasicEncryptionExample { + + private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); + + public static void main(final String[] args) { + final String keyArn = args[0]; + + encryptAndDecrypt(keyArn); + } + + static void encryptAndDecrypt(final String keyArn) { + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider. + final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder().withKeysForEncryption(keyArn).build(); + + // 3. Create an encryption context. + // + // Most encrypted data should have an associated encryption context + // to protect integrity. This sample uses placeholder values. + // + // For more information see: + // blogs.aws.amazon.com/security/post/Tx2LZ6WBJJANTNW/How-to-Protect-the-Integrity-of-Your-Encrypted-Data-by-Using-AWS-Key-Management + final Map encryptionContext = Collections.singletonMap("ExampleContextKey", "ExampleContextValue"); + + // 4. Encrypt the data. + final CryptoResult encryptResult = crypto.encryptData(masterKeyProvider, EXAMPLE_DATA, encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // 5. Decrypt the data. + final CryptoResult decryptResult = crypto.decryptData(masterKeyProvider, ciphertext); + + // 6. Before verifying the plaintext, verify that the customer master key that + // was used in the encryption operation was the one supplied to the master key provider. + if (!decryptResult.getMasterKeyIds().get(0).equals(keyArn)) { + throw new IllegalStateException("Wrong key ID!"); + } + + // 7. Also, verify that the encryption context in the result contains the + // encryption context supplied to the encryptData method. Because the + // SDK can add values to the encryption context, don't require that + // the entire context matches. + if (!encryptionContext.entrySet().stream() + .allMatch(e -> e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey())))) { + throw new IllegalStateException("Wrong Encryption Context!"); + } + + // 8. Verify that the decrypted plaintext matches the original plaintext. + assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java new file mode 100644 index 000000000..18286624f --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/EscrowedEncryptExample.java @@ -0,0 +1,164 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.legacy; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoOutputStream; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; +import com.amazonaws.util.IOUtils; + +/** + *

+ * Encrypts a file using both KMS and an asymmetric key pair. + * NOTE: Master key providers are deprecated and replaced by keyrings. + * We keep these older examples as reference material, + * but we recommend that you use the new examples in examples/keyring + * The new examples reflect our current guidance for using the library. + * + *

+ * Arguments: + *

    + *
  1. Key ARN: For help finding the Amazon Resource Name (ARN) of your KMS customer master + * key (CMK), see 'Viewing Keys' at http://docs.aws.amazon.com/kms/latest/developerguide/viewing-keys.html + * + *
  2. Name of file containing plaintext data to encrypt + *
+ * + * You might use AWS Key Management Service (KMS) for most encryption and decryption operations, but + * still want the option of decrypting your data offline independently of KMS. This sample + * demonstrates one way to do this. + * + * The sample encrypts data under both a KMS customer master key (CMK) and an "escrowed" RSA key pair + * so that either key alone can decrypt it. You might commonly use the KMS CMK for decryption. However, + * at any time, you can use the private RSA key to decrypt the ciphertext independent of KMS. + * + * This sample uses the JCEMasterKey class to generate a RSA public-private key pair + * and saves the key pair in memory. In practice, you would store the private key in a secure offline + * location, such as an offline HSM, and distribute the public key to your development team. + * + */ +public class EscrowedEncryptExample { + private static PublicKey publicEscrowKey; + private static PrivateKey privateEscrowKey; + + public static void main(final String[] args) throws Exception { + // This sample generates a new random key for each operation. + // In practice, you would distribute the public key and save the private key in secure + // storage. + generateEscrowKeyPair(); + + final String kmsArn = args[0]; + final String fileName = args[1]; + + standardEncrypt(kmsArn, fileName); + standardDecrypt(kmsArn, fileName); + + escrowDecrypt(fileName); + } + + private static void standardEncrypt(final String kmsArn, final String fileName) throws Exception { + // Encrypt with the KMS CMK and the escrowed public key + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider. + final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); + + // 3. Instantiate a JCE master key provider. + // Because the user does not have access to the private escrow key, + // they pass in "null" for the private key parameter. + final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // 4. Combine the providers into a single master key provider. + final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); + + // 5. Encrypt the file. + // To simplify the code, we omit the encryption context. Production code should always + // use an encryption context. For an example, see the other SDK samples. + final FileInputStream in = new FileInputStream(fileName); + final FileOutputStream out = new FileOutputStream(fileName + ".encrypted"); + final CryptoOutputStream encryptingStream = crypto.createEncryptingStream(provider, out); + + IOUtils.copy(in, encryptingStream); + in.close(); + encryptingStream.close(); + } + + private static void standardDecrypt(final String kmsArn, final String fileName) throws Exception { + // Decrypt with the KMS CMK and the escrow public key. You can use a combined provider, + // as shown here, or just the KMS master key provider. + + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a KMS master key provider. + final KmsMasterKeyProvider kms = new KmsMasterKeyProvider(kmsArn); + + // 3. Instantiate a JCE master key provider. + // Because the user does not have access to the private + // escrow key, they pass in "null" for the private key parameter. + final JceMasterKey escrowPub = JceMasterKey.getInstance(publicEscrowKey, null, "Escrow", "Escrow", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // 4. Combine the providers into a single master key provider. + final MasterKeyProvider provider = MultipleProviderFactory.buildMultiProvider(kms, escrowPub); + + // 5. Decrypt the file. + // To simplify the code, we omit the encryption context. Production code should always + // 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); + IOUtils.copy(in, decryptingStream); + in.close(); + decryptingStream.close(); + } + + private static void escrowDecrypt(final String fileName) throws Exception { + // You can decrypt the stream using only the private key. + // This method does not call KMS. + + // 1. Instantiate the AWS Encryption SDK. + final AwsCrypto crypto = new AwsCrypto(); + + // 2. Instantiate a JCE master key provider. + // This method call uses the escrowed private key, not null. + final JceMasterKey escrowPriv = JceMasterKey.getInstance(publicEscrowKey, privateEscrowKey, "Escrow", "Escrow", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // 3. Decrypt the file. + // To simplify the code, we omit the encryption context. Production code should always + // 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 + ".deescrowed"); + final CryptoOutputStream decryptingStream = crypto.createDecryptingStream(escrowPriv, out); + IOUtils.copy(in, decryptingStream); + in.close(); + decryptingStream.close(); + + } + + private static void generateEscrowKeyPair() throws GeneralSecurityException { + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + publicEscrowKey = keyPair.getPublic(); + privateEscrowKey = keyPair.getPrivate(); + + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java new file mode 100644 index 000000000..050050972 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/FileStreamingExample.java @@ -0,0 +1,93 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazonaws.crypto.examples.legacy; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.Map; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoInputStream; +import com.amazonaws.encryptionsdk.MasterKey; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.util.IOUtils; + +/** + *

+ * Encrypts and then decrypts a file under a random key. + * NOTE: Master key providers are deprecated and replaced by keyrings. + * We keep these older examples as reference material, + * but we recommend that you use the new examples in examples/keyring + * The new examples reflect our current guidance for using the library. + * + *

+ * Arguments: + *

    + *
  1. Name of file containing plaintext data to encrypt + *
+ * + *

+ * This program demonstrates using a standard Java {@link SecretKey} object as a {@link MasterKey} to + * encrypt and decrypt streaming data. + */ +public class FileStreamingExample { + private static String srcFile; + + public static void main(String[] args) throws IOException { + srcFile = args[0]; + + // In this example, we generate a random key. In practice, + // you would get a key from an existing store. + SecretKey cryptoKey = retrieveEncryptionKey(); + + // Create a JCE master key provider using the random key and an AES-GCM encryption algorithm. + JceMasterKey masterKey = JceMasterKey.getInstance(cryptoKey, "Example", "RandomKey", "AES/GCM/NoPadding"); + + // Instantiate the AWS Encryption SDK. + AwsCrypto crypto = new AwsCrypto(); + + // Create an encryption context to identify this ciphertext. + Map context = Collections.singletonMap("Example", "FileStreaming"); + + // Because the file might be to large to load into memory, we stream the data, instead of + //loading it all at once. + FileInputStream in = new FileInputStream(srcFile); + CryptoInputStream encryptingStream = crypto.createEncryptingStream(masterKey, in, context); + + FileOutputStream out = new FileOutputStream(srcFile + ".encrypted"); + IOUtils.copy(encryptingStream, out); + encryptingStream.close(); + out.close(); + + // Decrypt the file. Verify the encryption context before returning the plaintext. + in = new FileInputStream(srcFile + ".encrypted"); + CryptoInputStream decryptingStream = crypto.createDecryptingStream(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. + out = new FileOutputStream(srcFile + ".decrypted"); + IOUtils.copy(decryptingStream, out); + decryptingStream.close(); + out.close(); + } + + /** + * In practice, this key would be saved in a secure location. + * For this demo, we generate a new random key for each operation. + */ + private static SecretKey retrieveEncryptionKey() { + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[16]; // 128 bits + rnd.nextBytes(rawKey); + return new SecretKeySpec(rawKey, "AES"); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java similarity index 87% rename from src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java rename to src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java index 2419ebb51..badadb3e0 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/LambdaDecryptAndWriteExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/LambdaDecryptAndWriteExample.java @@ -1,17 +1,7 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples.datakeycaching; +package com.amazonaws.crypto.examples.legacy; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -115,20 +105,20 @@ public Void handleRequest(KinesisEvent event, Context context) { ByteBuffer ciphertextBuffer = record.getKinesis().getData(); byte[] ciphertext = BinaryUtils.copyAllBytesFrom(ciphertextBuffer); - // Decrypt and unpack record + // Decrypt and unpack record. AwsCryptoResult plaintextResult = crypto_.decrypt( DecryptRequest.builder() .cryptoMaterialsManager(cachingMaterialsManager_) .ciphertext(ciphertext).build()); - // Verify the encryption context value + // Verify the encryption context value. String streamArn = record.getEventSourceARN(); String streamName = streamArn.substring(streamArn.indexOf("/") + 1); if (!streamName.equals(plaintextResult.getEncryptionContext().get("stream"))) { throw new IllegalStateException("Wrong Encryption Context!"); } - // Write record to DynamoDB + // Write record to DynamoDB. String jsonItem = new String(plaintextResult.getResult(), StandardCharsets.UTF_8); table_.putItem(Item.fromJSON(jsonItem)); } diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java similarity index 86% rename from src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java rename to src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java index 62ff53d45..a42e78ae1 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/MultiRegionRecordPusherExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/MultiRegionRecordPusherExample.java @@ -1,17 +1,7 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples.datakeycaching; +package com.amazonaws.crypto.examples.legacy; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.encryptionsdk.AwsCrypto; @@ -61,7 +51,7 @@ public MultiRegionRecordPusherExample(final Region[] regions, final String kmsAl final DefaultAWSCredentialsProviderChain credentialsProvider = new DefaultAWSCredentialsProviderChain(); - // Build AwsKmsKeyring and AmazonKinesisClient objects for each target Region + // Build AwsKmsKeyring and AmazonKinesisClient objects for each target Region. final List keyrings = new ArrayList<>(); for (Region region : regions) { @@ -98,10 +88,10 @@ public void putRecord(final Map data) { String partitionKey = UUID.randomUUID().toString(); Map encryptionContext = Collections.singletonMap("stream", streamName_); - // JSON serialize data + // JSON serialize data. String jsonData = Jackson.toJsonString(data); - // Encrypt data + // Encrypt data. AwsCryptoResult result = crypto_.encrypt( EncryptRequest.builder() .cryptoMaterialsManager(cachingMaterialsManager_) @@ -111,7 +101,7 @@ public void putRecord(final Map data) { byte[] encryptedData = result.getResult(); - // Put records to Kinesis stream in all Regions + // Put records to Kinesis stream in all Regions. for (AmazonKinesis regionalKinesisClient : kinesisClients_) { regionalKinesisClient.putRecord( streamName_, diff --git a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java b/src/examples/java/com/amazonaws/crypto/examples/legacy/SimpleDataKeyCachingExample.java similarity index 78% rename from src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java rename to src/examples/java/com/amazonaws/crypto/examples/legacy/SimpleDataKeyCachingExample.java index fa14d7702..90ac05335 100644 --- a/src/examples/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExample.java +++ b/src/examples/java/com/amazonaws/crypto/examples/legacy/SimpleDataKeyCachingExample.java @@ -1,17 +1,7 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 -package com.amazonaws.crypto.examples.datakeycaching; +package com.amazonaws.crypto.examples.legacy; import com.amazonaws.encryptionsdk.AwsCrypto; import com.amazonaws.encryptionsdk.CryptoMaterialsManager; @@ -71,19 +61,19 @@ public static void main(final String[] args) { static byte[] encryptWithCaching(AwsKmsCmkId kmsCmkArn) { - // Instantiate the SDK + // Instantiate the AWS Encryption SDK. final AwsCrypto crypto = new AwsCrypto(); - // Create an encryption context + // Create an encryption context. final Map encryptionContext = Collections.singletonMap("purpose", "test"); - // Create a keyring + // Create a keyring. final Keyring keyring = StandardKeyrings.awsKms(kmsCmkArn); - // Create a cache + // Create a cache. final CryptoMaterialsCache cache = new LocalCryptoMaterialsCache(CAPACITY); - // Create a caching CMM + // Create a caching CMM. final CryptoMaterialsManager cachingCmm = CachingCryptoMaterialsManager.newBuilder() .withKeyring(keyring) @@ -93,7 +83,7 @@ static byte[] encryptWithCaching(AwsKmsCmkId kmsCmkArn) { .build(); // When the call to encrypt specifies a caching CMM, - // the encryption operation uses the data key cache + // the encryption operation uses the data key cache. return crypto.encrypt(EncryptRequest.builder() .cryptoMaterialsManager(cachingCmm) .plaintext(EXAMPLE_DATA) diff --git a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java deleted file mode 100644 index 95405fa2b..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/BasicEncryptionExampleTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.TestUtils; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -@Tag(TestUtils.TAG_INTEGRATION) -class BasicEncryptionExampleTest { - - @Test - void testEncryptAndDecrypt() { - BasicEncryptionExample.encryptAndDecrypt(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java deleted file mode 100644 index 6e3cf5137..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/EscrowedEncryptExampleTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.TestUtils; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.security.GeneralSecurityException; - -@Tag(TestUtils.TAG_INTEGRATION) -class EscrowedEncryptExampleTest { - - @Test - void testEncryptAndDecrypt() throws GeneralSecurityException { - EscrowedEncryptExample.escrowEncryptAndDecrypt(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java b/src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java new file mode 100644 index 000000000..26b9e3ee6 --- /dev/null +++ b/src/test/java/com/amazonaws/crypto/examples/ExamplesTest.java @@ -0,0 +1,119 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples; + +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ReflectionSupport; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.fail; + +@Tag(TestUtils.TAG_INTEGRATION) +class ExamplesTest { + + private static final Logger LOGGER = Logger.getLogger(ExamplesTest.class.getName()); + private static final String RUN_METHOD_NAME = "run"; + private static final String TEST_CLASS_SUFFIX = "Test"; + private static final byte[] STATIC_PLAINTEXT = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + + "Praesent non feugiat leo. Aenean iaculis tellus ut velit consectetur, " + + "quis convallis orci eleifend. Sed eu dictum sapien. Nulla facilisi. Suspendisse potenti. " + + "Proin vehicula vehicula maximus. Donec varius et elit vel rutrum. Nulla lacinia neque turpis," + + " quis consequat orci pharetra et. Etiam consequat ullamcorper mauris. Vivamus molestie mollis " + + "mauris a gravida. Curabitur sed bibendum nisl. Cras varius tortor non erat sodales, quis congue" + + " tellus laoreet. Etiam fermentum purus eu diam sagittis, vitae commodo est vehicula. " + + "Nulla feugiat viverra orci vel interdum. Quisque pulvinar elit eget nulla facilisis varius. " + + "Mauris at suscipit sem. Aliquam in purus ut velit fringilla volutpat id non mi. " + + "Curabitur quis nunc eleifend, ornare lectus non, fringilla quam. Nam maximus volutpat placerat. " + + "Nulla ullamcorper lorem velit, nec sagittis ex tristique posuere. Aliquam fringilla magna commodo" + + " libero faucibus tempor. Vestibulum non ligula tincidunt, finibus sapien in, sollicitudin " + + "ex. Pellentesque congue laoreet mi in condimentum. Cras convallis nisi ac nunc tincidunt " + + "venenatis. Suspendisse urna elit, cursus eu lacus a, aliquet porttitor mi. " + + "Nulla vel congue nibh, sed condimentum dui. Ut ante ligula, blandit eu finibus nec, " + + "scelerisque quis eros. Maecenas gravida odio eget nibh dictum, dictum varius lacus interdum. " + + "Integer quis nulla vulputate, rhoncus diam vitae, mollis mauris. Sed ut porttitor dolor. " + + "Fusce ut justo a ex bibendum imperdiet nec sit amet magna. Sed ullamcorper luctus augue, " + + "tempor viverra elit interdum sed. Cras sit amet arcu eu turpis molestie sollicitudin. " + + "Curabitur fermentum varius nibh, ut aliquet nisi. Aliquam id tempus tellus. " + + "Nulla porttitor nulla at nibh interdum, quis sollicitudin erat egestas. " + + "Ut blandit mauris quis efficitur efficitur. Morbi neque sapien, posuere ut aliquam eget, " + + "aliquam at velit. Morbi sit amet rhoncus felis, et hendrerit sem. Nulla porta dictum ligula " + + "eget iaculis. Cras lacinia ligula quis risus ultrices, sed consectetur metus imperdiet. " + + "Nullam id enim vestibulum nibh ultricies auctor. Morbi neque lacus, faucibus vitae commodo quis, " + + "malesuada sed velit.").getBytes(StandardCharsets.UTF_8); + + @TestFactory + Stream testExamples() { + final List> exampleClasses = ReflectionSupport.findAllClassesInPackage(getClass().getPackage().getName(), + c -> Arrays.stream(c.getDeclaredMethods()).anyMatch(m -> m.getName().equals(RUN_METHOD_NAME)), + c -> !c.endsWith(TEST_CLASS_SUFFIX)); + + return exampleClasses.stream() + .map(c -> ReflectionSupport.findMethods(c, m -> m.getName().equals(RUN_METHOD_NAME), HierarchyTraversalMode.TOP_DOWN).get(0)) + .map(ExamplesTest::createTest); + } + + /** + * Creates a DynamicTest for the given method, matching each parameter type + * to the 4 parameter types that we have predefined values for. + */ + private static DynamicTest createTest(Method method) { + final Class[] parameterTypes = method.getParameterTypes(); + final Object[] parameterValues = new Object[parameterTypes.length]; + + for (int i = 0; i < parameterTypes.length; i++) { + if (parameterTypes[i].isAssignableFrom(AwsKmsCmkId.class)) { + parameterValues[i] = AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0]); + } else if (parameterTypes[i].isAssignableFrom(List.class)) { + parameterValues[i] = Collections.singletonList(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[1])); + } else if (parameterTypes[i].isAssignableFrom(byte[].class)) { + parameterValues[i] = STATIC_PLAINTEXT; + } else if (parameterTypes[i].isAssignableFrom(File.class)) { + try { + final File tempFile = File.createTempFile(method.getDeclaringClass().getSimpleName(), ".tmp"); + tempFile.deleteOnExit(); + + try (OutputStream os = Files.newOutputStream(tempFile.toPath())) { + os.write(STATIC_PLAINTEXT); + } + + parameterValues[i] = tempFile; + } catch (IOException e) { + fail("Failed to create temp file", e); + } + } else { + LOGGER.info(String.format("Setting unsupported parameter type[%s] to null", parameterTypes[i])); + parameterValues[i] = null; + } + } + + return DynamicTest.dynamicTest(method.getDeclaringClass().getName(), () -> { + try { + method.invoke(null, parameterValues); + } catch (IllegalAccessException e) { + fail(method.getDeclaringClass().getName() + " failed", e); + } catch (InvocationTargetException e) { + fail(method.getDeclaringClass().getName() + " failed", e.getCause()); + } + }); + } + +} diff --git a/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java deleted file mode 100644 index 28b3f8f66..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/FileStreamingExampleTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import org.apache.commons.lang3.RandomStringUtils; -import org.junit.jupiter.api.Test; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -class FileStreamingExampleTest { - - @Test - void testEncryptAndDecrypt() throws IOException { - final File tempFile = File.createTempFile("FileStreamingExampleTest-TempTestData", ".tmp"); - final File encryptedFile = new File(tempFile.getPath() + ".encrypted"); - final File decryptedFile = new File(tempFile.getPath() + ".decrypted"); - tempFile.deleteOnExit(); - encryptedFile.deleteOnExit(); - decryptedFile.deleteOnExit(); - - try (BufferedWriter writer = Files.newBufferedWriter(tempFile.toPath())) { - for (int i = 0; i < 1000 ; i++) { - writer.write(RandomStringUtils.randomAlphanumeric(100)); - writer.newLine(); - } - } - - FileStreamingExample.encryptAndDecrypt(tempFile, encryptedFile, decryptedFile); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java deleted file mode 100644 index fa4d2906b..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/RawAesKeyringExampleTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import org.junit.jupiter.api.Test; - -class RawAesKeyringExampleTest { - - @Test - void testEncryptAndDecrypt() { - RawAesKeyringExample.encryptAndDecrypt(); - } -} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java deleted file mode 100644 index ca4c165f1..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringDecryptExampleTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import org.junit.jupiter.api.Test; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -class RawRsaKeyringDecryptExampleTest { - - @Test - void testDecrypt() throws Exception { - final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); - kg.initialize(4096); - final KeyPair keyPair = kg.generateKeyPair(); - - byte[] ciphertext = RawRsaKeyringEncryptExample.encrypt(keyPair.getPublic()); - byte[] decryptedResult = RawRsaKeyringDecryptExample.decrypt(ciphertext, keyPair); - - assertArrayEquals(RawRsaKeyringEncryptExample.EXAMPLE_DATA, decryptedResult); - } - -} diff --git a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java deleted file mode 100644 index 0329b4e48..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/RawRsaKeyringEncryptExampleTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.RawRsaKeyringBuilder.RsaPaddingScheme; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import org.junit.jupiter.api.Test; - -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -class RawRsaKeyringEncryptExampleTest { - - @Test - void testEncrypt() throws Exception { - final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); - kg.initialize(4096); - final KeyPair keyPair = kg.generateKeyPair(); - - byte[] ciphertext = RawRsaKeyringEncryptExample.encrypt(keyPair.getPublic()); - - final Keyring keyring = StandardKeyrings.rawRsaBuilder() - .keyNamespace("ExampleKeyNamespace") - .keyName("ExampleKeyName") - .privateKey(keyPair.getPrivate()) - .paddingScheme(RsaPaddingScheme.OAEP_SHA512_MGF1) - .build(); - - - final AwsCrypto awsCrypto = new AwsCrypto(); - byte[] decryptedResult = awsCrypto.decrypt(DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext) - .build()).getResult(); - - assertArrayEquals(RawRsaKeyringEncryptExample.EXAMPLE_DATA, decryptedResult); - } - -} diff --git a/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java b/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java deleted file mode 100644 index e1ebb3be9..000000000 --- a/src/test/java/com/amazonaws/crypto/examples/datakeycaching/SimpleDataKeyCachingExampleTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except - * in compliance with the License. A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package com.amazonaws.crypto.examples.datakeycaching; - -import com.amazonaws.encryptionsdk.TestUtils; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; -import com.amazonaws.encryptionsdk.kms.KMSTestFixtures; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -@Tag(TestUtils.TAG_INTEGRATION) -class SimpleDataKeyCachingExampleTest { - - @Test - void testEncryptWithCaching() { - SimpleDataKeyCachingExample.encryptWithCaching(AwsKmsCmkId.fromString(KMSTestFixtures.TEST_KEY_IDS[0])); - } -} From b893a0efbf9c5f35a4e0a694720386da5b69e44a Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Mon, 6 Apr 2020 13:50:02 -0700 Subject: [PATCH 15/18] Add master key provider examples (#168) * Add master key provider examples * Updating example intro and adding links in readme * Fixing indentation of comments --- src/examples/README.md | 6 + .../awskms/DiscoveryDecrypt.java | 94 +++++++++++ .../awskms/MultipleRegions.java | 114 +++++++++++++ .../masterkeyprovider/awskms/SingleCmk.java | 87 ++++++++++ .../multi/AwsKmsWithEscrow.java | 154 ++++++++++++++++++ .../masterkeyprovider/rawaes/RawAes.java | 98 +++++++++++ .../masterkeyprovider/rawrsa/RawRsa.java | 104 ++++++++++++ 7 files changed, 657 insertions(+) create mode 100644 src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/DiscoveryDecrypt.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/MultipleRegions.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/SingleCmk.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/multi/AwsKmsWithEscrow.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawaes/RawAes.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawrsa/RawRsa.java diff --git a/src/examples/README.md b/src/examples/README.md index b6e778219..19201887b 100644 --- a/src/examples/README.md +++ b/src/examples/README.md @@ -28,10 +28,13 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * Using AWS Key Management Service (AWS KMS) * How to use one AWS KMS CMK * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/SingleCmk.java) + * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/SingleCmk.java) * How to use multiple AWS KMS CMKs in different regions * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/MultipleRegions.java) + * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/MultipleRegions.java) * How to decrypt when you don't know the CMK * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecrypt.java) + * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/DiscoveryDecrypt.java) * How to decrypt within a region * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/awskms/DiscoveryDecryptInRegionOnly.java) * How to decrypt with a preferred region but failover to others @@ -39,8 +42,10 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * Using raw wrapping keys * How to use a raw AES wrapping key * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawaes/RawAes.java) + * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/rawaes/RawAes.java) * How to use a raw RSA wrapping key * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/RawRsa.java) + * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/rawrsa/RawRsa.java) * How to encrypt with a raw RSA public key wrapping key without access to the private key * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/rawrsa/PublicPrivateKeySeparate.java) * How to use a raw RSA wrapping key when the key is DER encoded @@ -48,6 +53,7 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * Combining wrapping keys * How to combine AWS KMS with an offline escrow key * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java) + * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/multi/AwsKmsWithEscrow.java) ### Keyrings diff --git a/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/DiscoveryDecrypt.java b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/DiscoveryDecrypt.java new file mode 100644 index 000000000..e16095224 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/DiscoveryDecrypt.java @@ -0,0 +1,94 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.masterkeyprovider.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example is intended to serve as reference material for users migrating away from master key providers. + * We recommend using keyrings rather than master key providers. + * For examples using keyrings, see the 'examples/keyring' directory. + *

+ * The KMS master key provider uses any key IDs that you specify on encrypt, + * but attempts to decrypt *any* data keys that were encrypted under a KMS CMK. + * This means that you do not need to know which CMKs were used to encrypt a message. + *

+ * This example shows how to configure and use a KMS master key provider to decrypt without provider key IDs. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + *

+ * For an example of how to use the KMS master key with a single CMK, + * see the {@link SingleCmk} example. + *

+ * For an example of how to use the KMS master key provider with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + */ +public class DiscoveryDecrypt { + + /** + * Demonstrate configuring a KMS master key provider for decryption. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the master key that determines how your data keys are protected. + final KmsMasterKeyProvider encryptMasterKeyProvider = KmsMasterKeyProvider.builder() + .withKeysForEncryption(awsKmsCmk.toString()).build(); + + // Create a KMS master key provider to use on decrypt. + final KmsMasterKeyProvider decryptMasterKeyProvider = KmsMasterKeyProvider.builder().build(); + + // Encrypt your plaintext data. + final CryptoResult encryptResult = awsEncryptionSdk.encryptData( + encryptMasterKeyProvider, + sourcePlaintext, + encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the KMS master key provider. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final CryptoResult decryptResult = awsEncryptionSdk.decryptData( + decryptMasterKeyProvider, + ciphertext); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/MultipleRegions.java b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/MultipleRegions.java new file mode 100644 index 000000000..e5cb68349 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/MultipleRegions.java @@ -0,0 +1,114 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.masterkeyprovider.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.stream.Collectors.toList; + +/** + * This example is intended to serve as reference material for users migrating away from master key providers. + * We recommend using keyrings rather than master key providers. + * For examples using keyrings, see the 'examples/keyring' directory. + *

+ * This example shows how to configure and use a KMS master key provider with with CMKs in multiple regions. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + *

+ * For an example of how to use the KMS master key with a single CMK, + * see the {@link SingleCmk} example. + *

+ * For an example of how to use the KMS master key provider in discovery mode on decrypt, + * see the {@link DiscoveryDecrypt} example. + */ +public class MultipleRegions { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS master key provider with CMKs in multiple regions. + * + * @param awsKmsGeneratorCmk The ARN of an AWS KMS CMK that protects data keys + * @param awsKmsAdditionalCmks Additional ARNs of secondary KMS CMKs + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsGeneratorCmk, final List awsKmsAdditionalCmks, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the master key provider that will encrypt your data keys under all requested CMKs. + // + // The KMS master key provider generates the data key using the first key ID in the list. + final List awsKmsCmks = new ArrayList<>(); + awsKmsCmks.add(awsKmsGeneratorCmk.toString()); + awsKmsCmks.addAll(awsKmsAdditionalCmks.stream().map(AwsKmsCmkId::toString).collect(toList())); + final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder() + .withKeysForEncryption(awsKmsCmks).build(); + + // Create master key providers that each only use one of the CMKs. + // We will use these later to demonstrate that any of the CMKs can be used to decrypt the message. + final KmsMasterKeyProvider singleCmkMasterKeyThatGenerated = KmsMasterKeyProvider.builder() + .withKeysForEncryption(awsKmsGeneratorCmk.toString()).build(); + final KmsMasterKeyProvider singleCmkMasterKeyThatEncrypted = KmsMasterKeyProvider.builder() + .withKeysForEncryption(awsKmsAdditionalCmks.get(0).toString()).build(); + + // Encrypt your plaintext data using the master key provider that uses all requests CMKs. + final CryptoResult encryptResult = awsEncryptionSdk.encryptData( + masterKeyProvider, + sourcePlaintext, + encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Verify that the header contains the expected number of encrypted data keys (EDKs). + // It should contain one EDK for each CMK. + assert encryptResult.getHeaders().getEncryptedKeyBlobCount() == awsKmsAdditionalCmks.size() + 1; + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data separately using the single-CMK master keys. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final CryptoResult decryptResult1 = awsEncryptionSdk.decryptData( + singleCmkMasterKeyThatGenerated, + ciphertext); + final byte[] decrypted1 = decryptResult1.getResult(); + final CryptoResult decryptResult2 = awsEncryptionSdk.decryptData( + singleCmkMasterKeyThatEncrypted, + ciphertext); + final byte[] decrypted2 = decryptResult2.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted1, sourcePlaintext); + assert Arrays.equals(decrypted2, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult1.getEncryptionContext().get(k)); + assert v.equals(decryptResult2.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/SingleCmk.java b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/SingleCmk.java new file mode 100644 index 000000000..2ce88f7fd --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/awskms/SingleCmk.java @@ -0,0 +1,87 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.masterkeyprovider.awskms; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example is intended to serve as reference material for users migrating away from master key providers. + * We recommend using keyrings rather than master key providers. + * For examples using keyrings, see the 'examples/keyring' directory. + *

+ * This example shows how to configure and use a KMS master key with a single KMS CMK. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + *

+ * For an example of how to use the KMS master key provider with CMKs in multiple regions, + * see the {@link MultipleRegions} example. + *

+ * For an example of how to use the KMS master key provider in discovery mode on decrypt, + * see the {@link DiscoveryDecrypt} example. + */ +public class SingleCmk { + + /** + * Demonstrate an encrypt/decrypt cycle using a KMS master key provider with a single CMK. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the master key provider that determines how your data keys are protected. + final KmsMasterKeyProvider masterKeyProvider = KmsMasterKeyProvider.builder() + .withKeysForEncryption(awsKmsCmk.toString()).build(); + + // Encrypt your plaintext data. + final CryptoResult encryptResult = awsEncryptionSdk.encryptData( + masterKeyProvider, + sourcePlaintext, + encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same master key provider you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final CryptoResult decryptResult = awsEncryptionSdk.decryptData( + masterKeyProvider, + ciphertext); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/multi/AwsKmsWithEscrow.java b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/multi/AwsKmsWithEscrow.java new file mode 100644 index 000000000..425f8cff6 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/multi/AwsKmsWithEscrow.java @@ -0,0 +1,154 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.masterkeyprovider.multi; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.MasterKeyProvider; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.kms.KmsMasterKey; +import com.amazonaws.encryptionsdk.kms.KmsMasterKeyProvider; +import com.amazonaws.encryptionsdk.multi.MultipleProviderFactory; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example is intended to serve as reference material for users migrating away from master key providers. + * We recommend using keyrings rather than master key providers. + * For examples using keyrings, see the 'examples/keyring' directory. + *

+ * One use-case that we have seen customers need is + * the ability to enjoy the benefits of AWS KMS during normal operation + * but retain the ability to decrypt encrypted messages without access to AWS KMS. + * This example shows how you can achieve this + * by combining a KMS master key with a raw RSA master key. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + *

+ * For more examples of how to use the KMS master key provider, see the + * 'masterkeyprovider/awskms' examples' + *

+ * For more examples of how to use the raw RSA master key, see the + * see the 'masterkeyprovider/rawrsa' examples. + *

+ * In this example we generate a RSA keypair + * but in practice you would want to keep your private key in an HSM + * or other key management system. + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class AwsKmsWithEscrow { + + /** + * Demonstrate configuring a master key provider to use an AWS KMS CMK and a RSA wrapping key. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your master key. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the encrypt master key that only has access to the public key. + final JceMasterKey escrowEncryptMasterKey = JceMasterKey.getInstance( + keyPair.getPublic(), + null, // Private key is null + // The provider ID and key ID are defined by you + // and are used by the raw RSA master key + // to determine whether it should attempt to decrypt + // an encrypted data key. + "some managed raw keys", // provider corresponds to key namespace for keyrings + "my RSA wrapping key", // key ID corresponds to key name for keyrings + // The padding scheme tells the raw RSA master key + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + // Create the decrypt master key that has access to the private key. + final JceMasterKey escrowDecryptMasterKey = JceMasterKey.getInstance( + null, // Public key is null + keyPair.getPrivate(), + // The key namespace and key name MUST match the encrypt master key. + "some managed raw keys", // provider corresponds to key namespace for keyrings + "my RSA wrapping key", // key ID corresponds to key name for keyrings + // The wrapping algorithm MUST match the encrypt master key. + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + + // Create the KMS master key that you will use for decryption during normal operations. + final KmsMasterKeyProvider kmsMasterKeyProvider = KmsMasterKeyProvider.builder() + .withKeysForEncryption(awsKmsCmk.toString()).build(); + + // Combine the KMS and escrow providers into a single master key provider. + final MasterKeyProvider masterKeyProvider = MultipleProviderFactory.buildMultiProvider( + kmsMasterKeyProvider, escrowEncryptMasterKey); + + // Encrypt your plaintext data using the combined master keys. + final CryptoResult encryptResult = awsEncryptionSdk.encryptData( + masterKeyProvider, + sourcePlaintext, + encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Verify that the header contains the expected number of encrypted data keys (EDKs). + // It should contain one EDK for KMS and one for the escrow key. + assert encryptResult.getHeaders().getEncryptedKeyBlobCount() == 2; + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data separately using the KMS master key provider + // and the escrow decrypt master key. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final CryptoResult decryptedKmsResult = awsEncryptionSdk.decryptData( + kmsMasterKeyProvider, + ciphertext); + final byte[] decryptedKms = decryptedKmsResult.getResult(); + final CryptoResult decryptedEscrowResult = awsEncryptionSdk.decryptData( + escrowDecryptMasterKey, + ciphertext); + final byte[] decryptedEscrow = decryptedKmsResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decryptedKms, sourcePlaintext); + assert Arrays.equals(decryptedEscrow, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptedKmsResult.getEncryptionContext().get(k)); + assert v.equals(decryptedEscrowResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawaes/RawAes.java b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawaes/RawAes.java new file mode 100644 index 000000000..89a8d90cc --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawaes/RawAes.java @@ -0,0 +1,98 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.masterkeyprovider.rawaes; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example is intended to serve as reference material for users migrating away from master key providers. + * We recommend using keyrings rather than master key providers. + * For examples using keyrings, see the 'examples/keyring' directory. + *

+ * This examples shows how to configure and use a raw AES master key. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawAes { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw AES master key. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an AES key to use with your master key. + // + // In practice, you should get this key from a secure key management system such as an HSM. + SecureRandom rnd = new SecureRandom(); + byte[] rawKey = new byte[32]; // 256 bits + rnd.nextBytes(rawKey); + SecretKey key = new SecretKeySpec(rawKey, "AES"); + + // Create the master key that determines how your data keys are protected. + final JceMasterKey masterKey = JceMasterKey.getInstance( + // The key namespace and key name are defined by you + // and are used by the raw AES master key + // to determine whether it should attempt to decrypt + // an encrypted data key. + key, + "some managed raw keys", // provider corresponds to key namespace for keyrings + "my AES wrapping key", // key ID corresponds to key name for keyrings + "AES/GCM/NOPADDING"); // the AES JceMasterKey only supports AES/GCM/NOPADDING + + // Encrypt your plaintext data. + final CryptoResult encryptResult = awsEncryptionSdk.encryptData( + masterKey, + sourcePlaintext, + encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same master key you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final CryptoResult decryptResult = awsEncryptionSdk.decryptData( + masterKey, + ciphertext); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawrsa/RawRsa.java b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawrsa/RawRsa.java new file mode 100644 index 000000000..ff1d972f0 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/masterkeyprovider/rawrsa/RawRsa.java @@ -0,0 +1,104 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.masterkeyprovider.rawrsa; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.CryptoResult; +import com.amazonaws.encryptionsdk.jce.JceMasterKey; + +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * This example is intended to serve as reference material for users migrating away from master key providers. + * We recommend using keyrings rather than master key providers. + * For examples using keyrings, see the 'examples/keyring' directory. + *

+ * This examples shows how to configure and use a raw RSA master key using a pre-loaded RSA key pair. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#master-key-provider + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class RawRsa { + + /** + * Demonstrate an encrypt/decrypt cycle using a raw RSA master key. + * + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final byte[] sourcePlaintext) throws GeneralSecurityException { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Generate an RSA key pair to use with your master key. + // In practice, you should get this key from a secure key management system such as an HSM. + final KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA"); + // The National Institute of Standards and Technology (NIST) recommends a minimum of 2048-bit keys for RSA. + // https://www.nist.gov/publications/transitioning-use-cryptographic-algorithms-and-key-lengths + kg.initialize(4096); + final KeyPair keyPair = kg.generateKeyPair(); + + // Create the master key that determines how your data keys are protected. + final JceMasterKey masterKey = JceMasterKey.getInstance( + keyPair.getPublic(), + keyPair.getPrivate(), + // The provider ID and key ID are defined by you + // and are used by the raw RSA master key + // to determine whether it should attempt to decrypt + // an encrypted data key. + "some managed raw keys", // provider corresponds to key namespace for keyrings + "my RSA wrapping key", // key ID corresponds to key name for keyrings + // The padding scheme tells the raw RSA master key + // how to use your wrapping key to encrypt data keys. + // + // We recommend using OAEP_SHA256_MGF1. + // You should not use PKCS1 unless you require it for backwards compatibility. + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + + // Encrypt your plaintext data. + final CryptoResult encryptResult = awsEncryptionSdk.encryptData( + masterKey, + sourcePlaintext, + encryptionContext); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same master key you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final CryptoResult decryptResult = awsEncryptionSdk.decryptData( + masterKey, + ciphertext); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} From ebda7746c9624fa24278259948c529a0cbfac548 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Tue, 7 Apr 2020 13:52:56 -0700 Subject: [PATCH 16/18] Merge head of master into keyring (#169) * Add a basic example for encrypting and decrypting with a KMS CMK (#136) * *Issue #, if available:* #108 *Description of changes:* Add a basic example for encrypting and decrypting with a KMS CMK. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. * Add test and Maven plugin to include examples directory as test source * Update docs in prep for 1.6.1 (#133) * Update docs in prep for 1.6.1 * Actually bump version for release * Fix for new versions of gpg * Refactor JceMasterKey to extract logic to be shared by raw keyrings. (#139) * Refactor JceMasterKey to extract logic to be shared by raw keyrings. *Issue #, if available:* #102 *Description of changes:* In anticipation of the RawAesKeyring and RawRsaKeyring needing logic currently embedded in the JceMasterKey, this change extracts that logic into the JceKeyCipher class so it may be shared. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files. * fix: The final frame can not be larger than the Frame Length (#166) * Add validation to ensure the length of the final frame in the final frame header does not exceed the frame size specified in the message header. * Validate that frame length is positive for framed data * Reverting removal of variable frame length code * Reverting removal of variable frame length code * Fix spacing after if Co-authored-by: SalusaSecondus Co-authored-by: Greg Rubin --- .../internal/FrameDecryptionHandler.java | 5 +++++ .../model/CiphertextHeaders.java | 2 +- .../internal/FrameDecryptionHandlerTest.java | 19 ++++++++++++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandler.java b/src/main/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandler.java index 82d9cab76..2cfd14a1a 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandler.java +++ b/src/main/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandler.java @@ -133,6 +133,11 @@ public ProcessingSummary processBytes(final byte[] in, final int off, final int int protectedContentLen = -1; if (currentFrameHeaders_.isFinalFrame()) { protectedContentLen = currentFrameHeaders_.getFrameContentLength(); + + // The final frame should not be able to exceed the frameLength + if (frameSize_ > 0 && protectedContentLen > frameSize_) { + throw new BadCiphertextException("Final frame length exceeds frame length."); + } } else { protectedContentLen = frameSize_; } diff --git a/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java b/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java index 5e3b90886..aca71665b 100644 --- a/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java +++ b/src/main/java/com/amazonaws/encryptionsdk/model/CiphertextHeaders.java @@ -859,4 +859,4 @@ public void setHeaderNonce(final byte[] headerNonce) { public void setHeaderTag(final byte[] headerTag) { headerTag_ = headerTag.clone(); } -} \ No newline at end of file +} diff --git a/src/test/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandlerTest.java b/src/test/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandlerTest.java index 40554f72d..9f0a637e9 100644 --- a/src/test/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandlerTest.java +++ b/src/test/java/com/amazonaws/encryptionsdk/internal/FrameDecryptionHandlerTest.java @@ -15,11 +15,14 @@ import static org.junit.Assert.assertTrue; +import java.nio.ByteBuffer; import java.security.SecureRandom; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import com.amazonaws.encryptionsdk.TestUtils; +import com.amazonaws.encryptionsdk.exception.BadCiphertextException; import org.junit.Before; import org.junit.Test; @@ -72,4 +75,18 @@ public void decryptMaxContentLength() { frameDecryptionHandler_.processBytes(in, 0, in.length, out, 0); frameDecryptionHandler_.processBytes(in, 0, Integer.MAX_VALUE, out, 0); } -} \ No newline at end of file + + @Test(expected = BadCiphertextException.class) + public void finalFrameLengthTooLarge() { + + final ByteBuffer byteBuffer = ByteBuffer.allocate(25); + byteBuffer.put(TestUtils.unsignedBytesToSignedBytes( + new int[] {255, 255, 255, 255, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})); + byteBuffer.putInt(AwsCrypto.getDefaultFrameSize() + 1); + + final byte[] in = byteBuffer.array(); + final byte[] out = new byte[in.length]; + + frameDecryptionHandler_.processBytes(in, 0, in.length, out, 0); + } +} From b16af248de41b0fca563015c7cea10428a937876 Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Thu, 9 Apr 2020 16:36:25 -0700 Subject: [PATCH 17/18] add CMM examples (#170) --- src/examples/README.md | 6 + .../caching/SimpleCache.java | 122 +++++++++++ .../custom/AlgorithmSuiteEnforcement.java | 183 ++++++++++++++++ .../RequiringEncryptionContextFields.java | 197 ++++++++++++++++++ 4 files changed, 508 insertions(+) create mode 100644 src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java create mode 100644 src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java diff --git a/src/examples/README.md b/src/examples/README.md index 19201887b..c8ae480b7 100644 --- a/src/examples/README.md +++ b/src/examples/README.md @@ -54,6 +54,12 @@ We start with AWS KMS examples, then show how to use other wrapping keys. * How to combine AWS KMS with an offline escrow key * [with keyrings](./java/com/amazonaws/crypto/examples/keyring/multi/AwsKmsWithEscrow.java) * [with master key providers](./java/com/amazonaws/crypto/examples/masterkeyprovider/multi/AwsKmsWithEscrow.java) +* How to reuse data keys across multiple messages + * [with the caching cryptographic materials manager](./java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java) +* How to restrict algorithm suites + * [with a custom cryptographic materials manager](./java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java) +* How to require encryption context fields + * [with a custom cryptographic materials manager](./java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java) ### Keyrings diff --git a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java new file mode 100644 index 000000000..e7038d6c8 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/caching/SimpleCache.java @@ -0,0 +1,122 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.cryptomaterialsmanager.caching; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CryptoMaterialsManager; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.caching.CachingCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.caching.LocalCryptoMaterialsCache; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * The default cryptographic materials manager (CMM) + * creates new encryption and decryption materials + * on every call. + * This means every encrypted message is protected by a unique data key, + * but it also means that you need to interact with your key management system + * in order to process any message. + * If this causes performance, operations, or cost issues for you, + * you might benefit from data key caching. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/data-key-caching.html + *

+ * This example shows how to configure the caching CMM + * to reuse data keys across multiple encrypted messages. + *

+ * In this example, we use an AWS KMS customer master key (CMK), + * but you can use other key management options with the AWS Encryption SDK. + * For examples that demonstrate how to use other key management configurations, + * see the 'keyring' and 'masterkeyprovider' directories. + *

+ * In this example, we use the one-step encrypt and decrypt APIs. + */ +public class SimpleCache { + + /** + * Demonstrate an encrypt/decrypt cycle using the caching cryptographic materials manager. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create the caching cryptographic materials manager using your keyring. + final CryptoMaterialsManager cmm = CachingCryptoMaterialsManager.newBuilder() + .withKeyring(keyring) + // The cache is where the caching CMM stores the materials. + // + // LocalCryptoMaterialsCache gives you a local, in-memory, cache. + .withCache(new LocalCryptoMaterialsCache(100)) + // Max Age determines how long the caching CMM will reuse materials. + // + // This example uses two minutes. + // In production, always chose as small a value as possible + // that works for your requirements. + .withMaxAge(2, TimeUnit.MINUTES) + // Message Use Limit determines how many messages + // the caching CMM will protect with the same materials. + // + // In production, always choose as small a value as possible + // that works for your requirements. + .withMessageUseLimit(10) + .build(); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same cryptographic materials manager you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .cryptoMaterialsManager(cmm) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java new file mode 100644 index 000000000..45328b15a --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/AlgorithmSuiteEnforcement.java @@ -0,0 +1,183 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.cryptomaterialsmanager.custom; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CryptoAlgorithm; +import com.amazonaws.encryptionsdk.CryptoMaterialsManager; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * The AWS Encryption SDK supports several different algorithm suites + * that offer different security properties. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html + *

+ * By default, the AWS Encryption SDK will let you use any of these, + * but you might want to restrict that further. + *

+ * We recommend that you use the default algorithm suite, + * which uses AES-GCM with 256-bit keys, HKDF, and ECDSA message signing. + * If your readers and writers have the same permissions, + * you might want to omit the message signature for faster operation. + * For more information about choosing a signed or unsigned algorithm suite, + * see the AWS Encryption SDK developer guide: + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/supported-algorithms.html#other-algorithms + *

+ * This example shows how you can make a custom cryptographic materials manager (CMM) + * that only allows encrypt requests that either specify one of these two algorithm suites + * or do not specify an algorithm suite, in which case the default CMM uses the default algorithm suite. + */ +public class AlgorithmSuiteEnforcement { + + /** + * Indicates that an unsupported algorithm suite was requested. + */ + static class UnapprovedAlgorithmSuiteException extends RuntimeException { + UnapprovedAlgorithmSuiteException() { + super("Unapproved algorithm suite requested!"); + } + } + + /** + * Only allow encryption requests for approved algorithm suites. + */ + static class RequireApprovedAlgorithmSuitesCryptoMaterialsManager implements CryptoMaterialsManager { + + private final CryptoMaterialsManager cmm; + private final Set ALLOWED_ALGORITHM_SUITES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384, // the default algorithm suite + CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256))); // the recommended unsigned algorithm suite + + /** + * Set up the inner cryptographic materials manager using the provided keyring. + * + * @param keyring Keyring to use in the inner cryptographic materials manager + */ + RequireApprovedAlgorithmSuitesCryptoMaterialsManager(Keyring keyring) { + // Wrap the provided keyring in the default cryptographic materials manager (CMM). + // + // This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, + // do if you provide a keyring instead of a CMM. + cmm = new DefaultCryptoMaterialsManager(keyring); + } + + /** + * Block any requests that include an unapproved algorithm suite. + */ + @Override + public EncryptionMaterials getMaterialsForEncrypt(EncryptionMaterialsRequest request) { + if (request.getRequestedAlgorithm() != null && !ALLOWED_ALGORITHM_SUITES.contains(request.getRequestedAlgorithm())) { + throw new UnapprovedAlgorithmSuiteException(); + } + + return cmm.getMaterialsForEncrypt(request); + } + + /** + * Be more permissive on decrypt and just pass through. + */ + @Override + public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) { + return cmm.decryptMaterials(request); + } + } + + + /** + * Demonstrate an encrypt/decrypt cycle using a custom cryptographic materials manager that filters requests. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create the algorithm suite restricting cryptographic materials manager using your keyring. + final CryptoMaterialsManager cmm = new RequireApprovedAlgorithmSuitesCryptoMaterialsManager(keyring); + + // Demonstrate that the algorithm suite restricting CMM will not let you use an unapproved algorithm suite. + awsEncryptionSdk.setEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF); + + try { + awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + // The algorithm suite restricting CMM keeps this from happening. + throw new AssertionError("The algorithm suite restricting CMM does not let this happen!"); + } catch (UnapprovedAlgorithmSuiteException ex) { + // You asked for an unapproved algorithm suite. + // Reaching this point means everything is working as expected. + } + + // Set an approved algorithm suite. + awsEncryptionSdk.setEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same cryptographic materials manager you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .cryptoMaterialsManager(cmm) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + } +} diff --git a/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java new file mode 100644 index 000000000..c03a2f550 --- /dev/null +++ b/src/examples/java/com/amazonaws/crypto/examples/cryptomaterialsmanager/custom/RequiringEncryptionContextFields.java @@ -0,0 +1,197 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.amazonaws.crypto.examples.cryptomaterialsmanager.custom; + +import com.amazonaws.encryptionsdk.AwsCrypto; +import com.amazonaws.encryptionsdk.AwsCryptoResult; +import com.amazonaws.encryptionsdk.CryptoMaterialsManager; +import com.amazonaws.encryptionsdk.DecryptRequest; +import com.amazonaws.encryptionsdk.DefaultCryptoMaterialsManager; +import com.amazonaws.encryptionsdk.EncryptRequest; +import com.amazonaws.encryptionsdk.keyrings.Keyring; +import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; +import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; +import com.amazonaws.encryptionsdk.model.DecryptionMaterials; +import com.amazonaws.encryptionsdk.model.DecryptionMaterialsRequest; +import com.amazonaws.encryptionsdk.model.EncryptionMaterials; +import com.amazonaws.encryptionsdk.model.EncryptionMaterialsRequest; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Encryption context is a powerful tool for access and audit controls + * because it lets you tie *non-secret* metadata about a plaintext value to the encrypted message. + * Within the AWS Encryption SDK, + * you can use cryptographic materials managers to analyse the encryption context + * to provide logical controls and additional metadata. + *

+ * https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + *

+ * If you are using the AWS Encryption SDK with AWS KMS, + * you can use AWS KMS to provide additional powerful controls using the encryption context. + * For more information on that, see the KMS developer guide: + *

+ * https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context + *

+ * This example shows how to create a custom cryptographic materials manager (CMM) + * that requires a particular field in the encryption context. + */ +public class RequiringEncryptionContextFields { + + /** + * Indicates that an encryption context was found that lacked a classification identifier. + */ + static class MissingClassificationException extends RuntimeException { + MissingClassificationException() { + super("Encryption context does not contain classification!"); + } + } + + /** + * Only allow requests when the encryption context contains a classification identifier. + */ + static class ClassificationRequiringCryptoMaterialsManager implements CryptoMaterialsManager { + + private final CryptoMaterialsManager cmm; + private static final String CLASSIFICATION_KEY = "classification"; + + ClassificationRequiringCryptoMaterialsManager(Keyring keyring) { + // Wrap the provided keyring in the default cryptographic materials manager (CMM). + // + // This is the same thing that the encrypt and decrypt APIs, as well as the caching CMM, + // do if you provide a keyring instead of a CMM. + cmm = new DefaultCryptoMaterialsManager(keyring); + } + + /** + * Block any requests that do not contain a classification identifier in the encryption context. + */ + @Override + public EncryptionMaterials getMaterialsForEncrypt(EncryptionMaterialsRequest request) { + if (!request.getContext().containsKey(CLASSIFICATION_KEY)) { + throw new MissingClassificationException(); + } + + return cmm.getMaterialsForEncrypt(request); + } + + /** + * Block any requests that do not contain a classification identifier in the encryption context. + */ + @Override + public DecryptionMaterials decryptMaterials(DecryptionMaterialsRequest request) { + if (!request.getEncryptionContext().containsKey(CLASSIFICATION_KEY)) { + throw new MissingClassificationException(); + } + + return cmm.decryptMaterials(request); + } + } + + + /** + * Demonstrate an encrypt/decrypt cycle using a custom cryptographic materials manager that filters requests. + * + * @param awsKmsCmk The ARN of an AWS KMS CMK that protects data keys + * @param sourcePlaintext Plaintext to encrypt + */ + public static void run(final AwsKmsCmkId awsKmsCmk, final byte[] sourcePlaintext) { + // Instantiate the AWS Encryption SDK. + final AwsCrypto awsEncryptionSdk = new AwsCrypto(); + + // Prepare your encryption context. + // https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context + final Map encryptionContext = new HashMap<>(); + encryptionContext.put("encryption", "context"); + encryptionContext.put("is not", "secret"); + encryptionContext.put("but adds", "useful metadata"); + encryptionContext.put("that can help you", "be confident that"); + encryptionContext.put("the data you are handling", "is what you think it is"); + + // Create the keyring that determines how your data keys are protected. + final Keyring keyring = StandardKeyrings.awsKms(awsKmsCmk); + + // Create the classification requiring cryptographic materials manager using your keyring. + final CryptoMaterialsManager cmm = new ClassificationRequiringCryptoMaterialsManager(keyring); + + // Demonstrate that the classification requiring CMM will not let you encrypt without a classification identifier. + try { + awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + // The classification requiring CMM keeps this from happening. + throw new AssertionError("The classification requiring CMM does not let this happen!"); + } catch (MissingClassificationException ex) { + // Your encryption context did not contain a classification identifier. + // Reaching this point means everything is working as expected. + } + + // Create an encryption context with the required classification key. + final Map classifiedEncryptionContext = new HashMap<>(encryptionContext); + classifiedEncryptionContext.put("classification", "secret"); + + // Encrypt your plaintext data. + final AwsCryptoResult encryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .cryptoMaterialsManager(cmm) + .encryptionContext(classifiedEncryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] ciphertext = encryptResult.getResult(); + + // Demonstrate that the ciphertext and plaintext are different. + assert !Arrays.equals(ciphertext, sourcePlaintext); + + // Decrypt your encrypted data using the same cryptographic materials manager you used on encrypt. + // + // You do not need to specify the encryption context on decrypt because + // the header of the encrypted message includes the encryption context. + final AwsCryptoResult decryptResult = awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .cryptoMaterialsManager(cmm) + .ciphertext(ciphertext).build()); + final byte[] decrypted = decryptResult.getResult(); + + // Demonstrate that the decrypted plaintext is identical to the original plaintext. + assert Arrays.equals(decrypted, sourcePlaintext); + + // Verify that the encryption context used in the decrypt operation includes + // the encryption context that you specified when encrypting. + // The AWS Encryption SDK can add pairs, so don't require an exact match. + // + // In production, always use a meaningful encryption context. + encryptionContext.forEach((k, v) -> { + assert v.equals(decryptResult.getEncryptionContext().get(k)); + }); + + // Now demonstrate the decrypt path of the classification requiring cryptographic materials manager. + + // Encrypt your plaintext using the keyring and do not include a classification identifier. + final AwsCryptoResult unclassifiedEncryptResult = awsEncryptionSdk.encrypt( + EncryptRequest.builder() + .keyring(keyring) + .encryptionContext(encryptionContext) + .plaintext(sourcePlaintext).build()); + final byte[] unclassifiedCiphertext = unclassifiedEncryptResult.getResult(); + + assert !unclassifiedEncryptResult.getEncryptionContext().containsKey("classification"); + + // Demonstrate that the classification requiring CMM + // will not let you decrypt messages without classification identifiers. + try { + awsEncryptionSdk.decrypt( + DecryptRequest.builder() + .cryptoMaterialsManager(cmm) + .ciphertext(unclassifiedCiphertext).build()); + // The classification requiring CMM keeps this from happening. + throw new AssertionError("The classification requiring CMM does not let this happen!"); + } catch (MissingClassificationException ex) { + // Your encryption context did not contain a classification identifier. + // Reaching this point means everything is working as expected. + } + } +} From a9a6abd6101bbc5daed3e65e005ebc10721b3f4f Mon Sep 17 00:00:00 2001 From: Wesley Rosenblum <55108558+WesleyRosenblum@users.noreply.github.com> Date: Fri, 10 Apr 2020 15:39:24 -0700 Subject: [PATCH 18/18] Remove inline example code from readme and reset version number (#171) --- README.md | 88 ++----------------------------------------------------- 1 file changed, 3 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 1a4831a09..24858613d 100644 --- a/README.md +++ b/README.md @@ -54,95 +54,13 @@ You can get the latest release from Maven: com.amazonaws aws-encryption-sdk-java - 1.7.0 + 1.6.1 ``` -### Get Started - -The following code sample demonstrates how to get started: - -1. Instantiate the SDK. -2. Setup a KMS keyring. -3. Encrypt and decrypt data. - -```java -// This sample code encrypts and then decrypts data using an AWS Key Management Service (AWS KMS) customer master key (CMK). -package com.amazonaws.crypto.examples; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; - -import com.amazonaws.encryptionsdk.AwsCrypto; -import com.amazonaws.encryptionsdk.AwsCryptoResult; -import com.amazonaws.encryptionsdk.DecryptRequest; -import com.amazonaws.encryptionsdk.EncryptRequest; -import com.amazonaws.encryptionsdk.keyrings.Keyring; -import com.amazonaws.encryptionsdk.keyrings.StandardKeyrings; -import com.amazonaws.encryptionsdk.kms.AwsKmsCmkId; - -public class BasicEncryptionExample { - - private static final byte[] EXAMPLE_DATA = "Hello World".getBytes(StandardCharsets.UTF_8); - - public static void main(final String[] args) { - encryptAndDecrypt(AwsKmsCmkId.fromString(args[0])); - } - - static void encryptAndDecrypt(final AwsKmsCmkId keyArn) { - // 1. Instantiate the SDK - final AwsCrypto crypto = new AwsCrypto(); - - // 2. Instantiate a KMS keyring. Supply the key ARN for the generator key that generates a - // data key. While using a key ARN is a best practice, for encryption operations you can also - // use an alias name or alias ARN. - final Keyring keyring = StandardKeyrings.awsKms(keyArn); - - // 3. Create an encryption context - // - // Most encrypted data should have an associated encryption context - // to protect integrity. This sample uses placeholder values. - // - // For more information see: https://amzn.to/1nSbe9X (blogs.aws.amazon.com) - final Map encryptionContext = Collections.singletonMap("Example", "String"); - - // 4. Encrypt the data with the keyring and encryption context - final AwsCryptoResult encryptResult = crypto.encrypt( - EncryptRequest.builder() - .keyring(keyring) - .encryptionContext(encryptionContext) - .plaintext(EXAMPLE_DATA).build()); - final byte[] ciphertext = encryptResult.getResult(); - - // 5. Decrypt the data. You can use the same keyring to encrypt and decrypt, but for decryption - // the key IDs must be in the key ARN format. - final AwsCryptoResult decryptResult = crypto.decrypt( - DecryptRequest.builder() - .keyring(keyring) - .ciphertext(ciphertext).build()); - - // 6. To verify the CMK that was actually used in the decrypt operation, inspect the keyring trace. - if(!decryptResult.getKeyringTrace().getEntries().get(0).getKeyName().equals(keyArn.toString())) { - throw new IllegalStateException("Wrong key ID!"); - } - - // 7. To verify that the encryption context used to decrypt the data was the encryption context you expected, - // examine the encryption context in the result. This helps to ensure that you decrypted the ciphertext that - // you intended. - // - // When verifying, test that your expected encryption context is a subset of the actual encryption context, - // not an exact match. The Encryption SDK adds the signing key to the encryption context when appropriate. - assert decryptResult.getEncryptionContext().get("Example").equals("String"); - - // 8. Verify that the decrypted plaintext matches the original plaintext - assert Arrays.equals(decryptResult.getResult(), EXAMPLE_DATA); - } -} -``` +### Sample Code -You can find more examples in the [examples directory][examples]. +You can find sample code in the [examples directory][examples]. ## Public API