-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
*Description of changes:* Defining the KMS Keyring. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. # Check any applicable: - [ ] Were any files moved? Moving files changes their URL, which breaks all hyperlinks to the files.
- Loading branch information
1 parent
36958b8
commit 1bef9f5
Showing
13 changed files
with
1,214 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/main/java/com/amazonaws/encryptionsdk/exception/MalformedArnException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except | ||
* in compliance with the License. A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package com.amazonaws.encryptionsdk.exception; | ||
|
||
/** | ||
* This exception is thrown when an Amazon Resource Name is provided that does not | ||
* match the CMK Alias or ARN format. | ||
*/ | ||
public class MalformedArnException extends AwsCryptoException { | ||
|
||
private static final long serialVersionUID = -1L; | ||
|
||
public MalformedArnException() { | ||
super(); | ||
} | ||
|
||
public MalformedArnException(final String message) { | ||
super(message); | ||
} | ||
|
||
public MalformedArnException(final Throwable cause) { | ||
super(cause); | ||
} | ||
|
||
public MalformedArnException(final String message, final Throwable cause) { | ||
super(message, cause); | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/main/java/com/amazonaws/encryptionsdk/exception/MismatchedDataKeyException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/* | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except | ||
* in compliance with the License. A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package com.amazonaws.encryptionsdk.exception; | ||
|
||
/** | ||
* This exception is thrown when the key used by KMS to decrypt a data key does not | ||
* match the provider information contained within the encrypted data key. | ||
*/ | ||
public class MismatchedDataKeyException extends IllegalStateException { | ||
|
||
private static final long serialVersionUID = -1L; | ||
|
||
public MismatchedDataKeyException() { | ||
super(); | ||
} | ||
|
||
public MismatchedDataKeyException(final String message) { | ||
super(message); | ||
} | ||
|
||
public MismatchedDataKeyException(final Throwable cause) { | ||
super(cause); | ||
} | ||
|
||
public MismatchedDataKeyException(final String message, final Throwable cause) { | ||
super(message, cause); | ||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
src/main/java/com/amazonaws/encryptionsdk/keyrings/KmsKeyring.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
/* | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except | ||
* in compliance with the License. A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package com.amazonaws.encryptionsdk.keyrings; | ||
|
||
import com.amazonaws.encryptionsdk.EncryptedDataKey; | ||
import com.amazonaws.encryptionsdk.exception.AwsCryptoException; | ||
import com.amazonaws.encryptionsdk.exception.CannotUnwrapDataKeyException; | ||
import com.amazonaws.encryptionsdk.exception.MalformedArnException; | ||
import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao; | ||
import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.DecryptDataKeyResult; | ||
import com.amazonaws.encryptionsdk.kms.DataKeyEncryptionDao.GenerateDataKeyResult; | ||
import com.amazonaws.encryptionsdk.kms.KmsUtils; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import static com.amazonaws.encryptionsdk.EncryptedDataKey.PROVIDER_ENCODING; | ||
import static com.amazonaws.encryptionsdk.kms.KmsUtils.KMS_PROVIDER_ID; | ||
import static com.amazonaws.encryptionsdk.kms.KmsUtils.isArnWellFormed; | ||
import static java.util.Collections.emptyList; | ||
import static java.util.Collections.unmodifiableList; | ||
import static java.util.Objects.requireNonNull; | ||
|
||
/** | ||
* A keyring which interacts with AWS Key Management Service (KMS) to create, | ||
* encrypt, and decrypt data keys using KMS defined Customer Master Keys (CMKs). | ||
*/ | ||
public class KmsKeyring implements Keyring { | ||
|
||
private final DataKeyEncryptionDao dataKeyEncryptionDao; | ||
private final List<String> keyIds; | ||
private final String generatorKeyId; | ||
private final boolean isDiscovery; | ||
|
||
KmsKeyring(DataKeyEncryptionDao dataKeyEncryptionDao, List<String> keyIds, String generatorKeyId) { | ||
requireNonNull(dataKeyEncryptionDao, "dataKeyEncryptionDao is required"); | ||
this.dataKeyEncryptionDao = dataKeyEncryptionDao; | ||
this.keyIds = keyIds == null ? emptyList() : unmodifiableList(keyIds); | ||
this.generatorKeyId = generatorKeyId; | ||
this.isDiscovery = this.generatorKeyId == null && this.keyIds.isEmpty(); | ||
|
||
if (!this.keyIds.stream().allMatch(KmsUtils::isArnWellFormed)) { | ||
throw new MalformedArnException("keyIds must contain only CMK aliases and well formed ARNs"); | ||
} | ||
|
||
if (generatorKeyId != null) { | ||
if (!isArnWellFormed(generatorKeyId)) { | ||
throw new MalformedArnException("generatorKeyId must be either a CMK alias or a well formed ARN"); | ||
} | ||
if (this.keyIds.contains(generatorKeyId)) { | ||
throw new IllegalArgumentException("KeyIds should not contain the generatorKeyId"); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void onEncrypt(EncryptionMaterials encryptionMaterials) { | ||
requireNonNull(encryptionMaterials, "encryptionMaterials are required"); | ||
|
||
// If this keyring is a discovery keyring, OnEncrypt MUST return the input encryption materials unmodified. | ||
if (isDiscovery) { | ||
return; | ||
} | ||
|
||
// If the input encryption materials do not contain a plaintext data key and this keyring does not | ||
// have a generator defined, OnEncrypt MUST not modify the encryption materials and MUST fail. | ||
if (!encryptionMaterials.hasPlaintextDataKey() && generatorKeyId == null) { | ||
throw new AwsCryptoException("Encryption materials must contain either a plaintext data key or a generator"); | ||
} | ||
|
||
final List<String> keyIdsToEncrypt = new ArrayList<>(keyIds); | ||
|
||
// If the input encryption materials do not contain a plaintext data key and a generator is defined onEncrypt | ||
// MUST attempt to generate a new plaintext data key and encrypt that data key by calling KMS GenerateDataKey. | ||
if (!encryptionMaterials.hasPlaintextDataKey()) { | ||
generateDataKey(encryptionMaterials); | ||
} else { | ||
// If this keyring's generator is defined and was not used to generate a data key, OnEncrypt | ||
// MUST also attempt to encrypt the plaintext data key using the CMK specified by the generator. | ||
keyIdsToEncrypt.add(generatorKeyId); | ||
} | ||
|
||
// Given a plaintext data key in the encryption materials, OnEncrypt MUST attempt | ||
// to encrypt the plaintext data key using each CMK specified in it's key IDs list. | ||
for (String keyId : keyIdsToEncrypt) { | ||
encryptDataKey(keyId, encryptionMaterials); | ||
} | ||
} | ||
|
||
private void generateDataKey(final EncryptionMaterials encryptionMaterials) { | ||
final GenerateDataKeyResult result = dataKeyEncryptionDao.generateDataKey(generatorKeyId, | ||
encryptionMaterials.getAlgorithmSuite(), encryptionMaterials.getEncryptionContext()); | ||
|
||
encryptionMaterials.setPlaintextDataKey(result.getPlaintextDataKey(), | ||
new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.GENERATED_DATA_KEY)); | ||
encryptionMaterials.addEncryptedDataKey(result.getEncryptedDataKey(), | ||
new KeyringTraceEntry(KMS_PROVIDER_ID, generatorKeyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); | ||
} | ||
|
||
private void encryptDataKey(final String keyId, final EncryptionMaterials encryptionMaterials) { | ||
final EncryptedDataKey encryptedDataKey = dataKeyEncryptionDao.encryptDataKey(keyId, | ||
encryptionMaterials.getPlaintextDataKey(), encryptionMaterials.getEncryptionContext()); | ||
|
||
encryptionMaterials.addEncryptedDataKey(encryptedDataKey, | ||
new KeyringTraceEntry(KMS_PROVIDER_ID, keyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT)); | ||
} | ||
|
||
@Override | ||
public void onDecrypt(DecryptionMaterials decryptionMaterials, List<? extends EncryptedDataKey> encryptedDataKeys) { | ||
requireNonNull(decryptionMaterials, "decryptionMaterials are required"); | ||
requireNonNull(encryptedDataKeys, "encryptedDataKeys are required"); | ||
|
||
if (decryptionMaterials.hasPlaintextDataKey() || encryptedDataKeys.isEmpty()) { | ||
return; | ||
} | ||
|
||
if (!encryptedDataKeys.stream() | ||
.filter(edk -> edk.getProviderId().equals(KMS_PROVIDER_ID)) | ||
.map(edk -> new String(edk.getProviderInformation(), PROVIDER_ENCODING)) | ||
.allMatch(KmsUtils::isArnWellFormed)) { | ||
throw new MalformedArnException("encryptedDataKeys contains a malformed ARN"); | ||
} | ||
|
||
for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) { | ||
if (okToDecrypt(encryptedDataKey)) { | ||
try { | ||
final DecryptDataKeyResult result = dataKeyEncryptionDao.decryptDataKey(encryptedDataKey, | ||
decryptionMaterials.getAlgorithmSuite(), decryptionMaterials.getEncryptionContext()); | ||
|
||
decryptionMaterials.setPlaintextDataKey(result.getPlaintextDataKey(), | ||
new KeyringTraceEntry(KMS_PROVIDER_ID, result.getKeyArn(), | ||
KeyringTraceFlag.DECRYPTED_DATA_KEY, KeyringTraceFlag.VERIFIED_ENCRYPTION_CONTEXT)); | ||
return; | ||
} catch (CannotUnwrapDataKeyException e) { | ||
continue; | ||
} | ||
} | ||
} | ||
} | ||
|
||
private boolean okToDecrypt(EncryptedDataKey encryptedDataKey) { | ||
// Only attempt to decrypt keys provided by KMS | ||
if (!encryptedDataKey.getProviderId().equals(KMS_PROVIDER_ID)) { | ||
return false; | ||
} | ||
|
||
// If this keyring is a discovery keyring, OnDecrypt MUST attempt to | ||
// decrypt every encrypted data key in the input encrypted data key list | ||
if (isDiscovery) { | ||
return true; | ||
} | ||
|
||
final String providerInfo = new String(encryptedDataKey.getProviderInformation(), PROVIDER_ENCODING); | ||
|
||
// 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 providerInfo.equals(generatorKeyId) || keyIds.stream().anyMatch(providerInfo::equals); | ||
} | ||
} |
104 changes: 104 additions & 0 deletions
104
src/main/java/com/amazonaws/encryptionsdk/kms/DataKeyEncryptionDao.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except | ||
* in compliance with the License. A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
*/ | ||
|
||
package com.amazonaws.encryptionsdk.kms; | ||
|
||
import com.amazonaws.encryptionsdk.CryptoAlgorithm; | ||
import com.amazonaws.encryptionsdk.EncryptedDataKey; | ||
|
||
import javax.crypto.SecretKey; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public interface DataKeyEncryptionDao { | ||
|
||
/** | ||
* Generates a unique data key, returning both the plaintext copy of the key and an encrypted copy encrypted using | ||
* the customer master key specified by the given keyId. | ||
* | ||
* @param keyId The customer master key to encrypt the generated key with. | ||
* @param algorithmSuite The algorithm suite associated with the key. | ||
* @param encryptionContext The encryption context. | ||
* @return GenerateDataKeyResult containing the plaintext data key and the encrypted data key. | ||
*/ | ||
GenerateDataKeyResult generateDataKey(String keyId, CryptoAlgorithm algorithmSuite, Map<String, String> encryptionContext); | ||
|
||
/** | ||
* Encrypts the given plaintext data key using the customer aster key specified by the given keyId. | ||
* | ||
* @param keyId The customer master key to encrypt the plaintext data key with. | ||
* @param plaintextDataKey The plaintext data key to encrypt. | ||
* @param encryptionContext The encryption context. | ||
* @return The encrypted data key. | ||
*/ | ||
EncryptedDataKey encryptDataKey(final String keyId, SecretKey plaintextDataKey, Map<String, String> encryptionContext); | ||
|
||
/** | ||
* Decrypted the given encrypted data key. | ||
* | ||
* @param encryptedDataKey The encrypted data key to decrypt. | ||
* @param algorithmSuite The algorithm suite associated with the key. | ||
* @param encryptionContext The encryption context. | ||
* @return DecryptDataKeyResult containing the plaintext data key and the ARN of the key that decrypted it. | ||
*/ | ||
DecryptDataKeyResult decryptDataKey(EncryptedDataKey encryptedDataKey, CryptoAlgorithm algorithmSuite, Map<String, String> encryptionContext); | ||
|
||
/** | ||
* Constructs an instance of DataKeyEncryptionDao that uses AWS Key Management Service (KMS) for | ||
* generation, encryption, and decryption of data keys. | ||
* | ||
* @param clientSupplier A supplier of AWSKMS clients | ||
* @param grantTokens A list of grant tokens to supply to KMS | ||
* @return The DataKeyEncryptionDao | ||
*/ | ||
static DataKeyEncryptionDao kms(KmsClientSupplier clientSupplier, List<String> grantTokens) { | ||
return new KmsDataKeyEncryptionDao(clientSupplier, grantTokens); | ||
} | ||
|
||
class GenerateDataKeyResult { | ||
private final SecretKey plaintextDataKey; | ||
private final EncryptedDataKey encryptedDataKey; | ||
|
||
public GenerateDataKeyResult(SecretKey plaintextDataKey, EncryptedDataKey encryptedDataKey) { | ||
this.plaintextDataKey = plaintextDataKey; | ||
this.encryptedDataKey = encryptedDataKey; | ||
} | ||
|
||
public SecretKey getPlaintextDataKey() { | ||
return plaintextDataKey; | ||
} | ||
|
||
public EncryptedDataKey getEncryptedDataKey() { | ||
return encryptedDataKey; | ||
} | ||
} | ||
|
||
class DecryptDataKeyResult { | ||
private final String keyArn; | ||
private final SecretKey plaintextDataKey; | ||
|
||
public DecryptDataKeyResult(String keyArn, SecretKey plaintextDataKey) { | ||
this.keyArn = keyArn; | ||
this.plaintextDataKey = plaintextDataKey; | ||
} | ||
|
||
public String getKeyArn() { | ||
return keyArn; | ||
} | ||
|
||
public SecretKey getPlaintextDataKey() { | ||
return plaintextDataKey; | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.