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