Skip to content

Defining the KMS Keyring #147

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 18, 2019
Merged

Defining the KMS Keyring #147

merged 11 commits into from
Dec 18, 2019

Conversation

WesleyRosenblum
Copy link
Contributor

Description of changes:

This change contains the initial implementation of the KMS Keyring, as well as a refactoring of the existing KMS logic into a DataKeyEncryptionDao that is shared between the KMS Keyring and the KMS Master Key.

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.

*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.
Copy link
Contributor

@lavaleri lavaleri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of questions, but otherwise the KMS keyring logic LGTM. Note that awslabs/aws-encryption-sdk-specification#50 is still open (specifies behavior with client supplier when a region can't be grabbed from the arn), and could impact this PR if more changes are suggested.

You should also get this reviewed by someone with more Java experience, and more familiar with the current MKP behavior.

result.getPlaintextDataKey(),
edk.getEncryptedDataKey(),
edk.getProviderInformation(), this);
} catch (final AwsCryptoException | MismatchedDataKeyException ex) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a unit test that ensures this behavior is maintained? (I can't seem to find one, or other unit tests for KMSMasterKey :()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KMSMasterKey is covered by LegacyKMSMasterKeyProviderTests. I've added a separate KmsMasterKeyTest to cover the case of a MismatchedDataKeyException.

}

private static boolean isKeyAlias(String arn) {
return arn.startsWith(ALIAS_PREFIX) && !arn.contains(":");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the !arn.contains(":") requirement come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking I wanted to return false for an alias ARN, that still contains the ALIAS_PREFIX (though not as a prefix). But since an alias ARN would not have that as a prefix, checking for : shouldn't be necessary.

encryptionMaterials.getPlaintextDataKey(), encryptionMaterials.getEncryptionContext());

encryptionMaterials.addEncryptedDataKey(encryptedDataKey,
new KeyringTraceEntry(KMS_PROVIDER_ID, keyId, KeyringTraceFlag.ENCRYPTED_DATA_KEY, KeyringTraceFlag.SIGNED_ENCRYPTION_CONTEXT));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we determine whether this should be the keyId the user inputs, or the keyId returned from the KMS result (and is it in the spec/do we have an issue cut to update the spec)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we were leaning toward the keyId the user inputs, which is what I went with, but it wasn't settled. Either way, the spec still just says "generator" which is wrong, I've created an issue: awslabs/aws-encryption-sdk-specification#60

@mattsb42-aws mattsb42-aws mentioned this pull request Dec 10, 2019
15 tasks
Copy link
Contributor

@SalusaSecondus SalusaSecondus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not review the tests. Minor issues found, but nothing major.

* 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this is technically an IllegalStateException. According to the documentation.

Signals that a method has been invoked at an illegal or inappropriate time. In other words, the Java environment or Java application is not in an appropriate state for the requested operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I wanted this to not be an AwsCryptoException so I could handle it differently, and I was thinking it indicated KMS was in a bad state, so IllegalStateException seemed ok. Now looking over the code again, I can just make it extend AwsCryptoException. I added another unit test for KmsKeyring to ensure the behavior I wanted is maintained for this exception.

.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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should a single malformed ARN cause decryption failure? So long as we can find a single valid ARN and decrypt it, we should succeed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that since decryption failures are ignored, as a user of this method I would like the method to fail fast if I am passing in bad inputs. Otherwise I would not understand why a particular key was not being used for decryption.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be flexible in what we accept (when it doesn't conflict with security). What if a newer version of the ESDK is delivered which supports a new form of ARNs? Older versions should still be able to decrypt data associated with the old-style of understood ARNs.

If no data keys can be decrypted, then this error should be propagated up. That's useful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. The question is, how did such a value get in the the EDK in the first place?
I guess the question is "what is bad input"? if someone were to create a new KMS Keyring, that is not quite right and sometimes puts the wrong thing in the ProviderID, is this an example of a bad input?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seebees which point are you disagreeing with?

throw new MalformedArnException("encryptedDataKeys contains a malformed ARN");
}

for (EncryptedDataKey encryptedDataKey : encryptedDataKeys) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a potential O(n^2) operation. We iterate over each EDK and then in okToDecrypt iterate over every keyId we have configured. This seems like a potentially inefficient way to do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll put all the configured keys into a HashSet to improve performance

* @return The proxy
*/
private AWSKMS newCachingProxy(AWSKMS client, String regionId) {
return (AWSKMS) Proxy.newProxyInstance(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's worth keeping an eye on this. Proxies can cause performance problems sometimes as they depend on Java reflection APIs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, though I hope any proxy overhead would be insignificant compared to the actual service call to KMS.

SalusaSecondus
SalusaSecondus previously approved these changes Dec 18, 2019
@WesleyRosenblum WesleyRosenblum dismissed seebees’s stale review December 18, 2019 21:57

Will address after decision is made on supporting raw key IDs

@WesleyRosenblum WesleyRosenblum merged commit 77f46c2 into keyring Dec 18, 2019
@WesleyRosenblum WesleyRosenblum deleted the kms branch December 18, 2019 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants