Skip to content
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

feat: AwsKmsMrkAwareSymmetricKeyring #383

Merged
merged 26 commits into from
Oct 29, 2021

Conversation

seebees
Copy link
Contributor

@seebees seebees commented Oct 21, 2021

Adding the keyring and verification

This is only the single keyring, and some helpers.
I need to also add a PR to the standard library for Closures,
but for now I will add the file to our code for review.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Adding the keyring and verification
@lavaleri
Copy link
Contributor

AWS CodeBuild CI Report

  • CodeBuild project: DafnyESDK
  • Commit ID: 0bb1514
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@lavaleri
Copy link
Contributor

AWS CodeBuild CI Report

  • CodeBuild project: DafnyESDK
  • Commit ID: 5478563
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

Copy link
Contributor

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

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

This is looking really nice!

method Invoke(a: A)
returns (r: R)
ensures Ensures(a, r)
predicate Ensures(a: A, r: R)
Copy link
Contributor

Choose a reason for hiding this comment

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

You'll need a Requires as well before adding this to the standard library, since adding it later will be a breaking change (and Dafny doesn't have a way to define a default trait method implementation yet).

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, we should consider adding default trait method implementations to Dafny, since once the reads-clauses-on-methods RFC is added (dafny-lang/rfcs#6) we'll want to add Reads as well.

src/StandardLibrary/Closures.dfy Outdated Show resolved Hide resolved
src/StandardLibrary/Closures.dfy Outdated Show resolved Hide resolved
src/StandardLibrary/Closures.dfy Outdated Show resolved Hide resolved
src/StandardLibrary/Closures.dfy Outdated Show resolved Hide resolved
src/SDK/Keyring/Defs.dfy Outdated Show resolved Hide resolved
client: IAmazonKeyManagementService,
awsKmsKey: string,
grantTokens: seq<KMSUtils.GrantToken>
)
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 interesting - you're missing ensures Valid() here, and yet you have requires Valid() on the class methods (as you have to since that's dictated by the extended trait). That makes this class effectively unusable!

This highlights the importance of test cases even in a Dafny code base: even a test that doesn't actually run, but is at least verified, would reveal this issue. For example:

method {:test} CanUseTheThing() {
  ...
  var kmsKeyring := new AwsKmsMrkAwareSymmetricKeyring(...);
  var materialsAfter := kmsKeyring.OnEncrypt(materialsBefore);  // pre-condition may not hold
}

Ideally you could have the trait assert that any extending class has to ensure Valid() on the constructor, but that's not supported and I'm not sure it's tractable.

As a last resort, the Polymorph-generated Dafny skeleton for the CreateAwsKmsMrkAwareSymmetricKeyring operation MUST include ensures result.Valid(), which will transitively force you to add the post-condition.

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 would like to +1 this into my lemma for method feature. If I could write such a lemma right there, then I could say things about my method and I would be happy.

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 did fix this. When I tried to do the very next MultiKeyring for these....
But my other comment still stands

Copy link
Contributor

Choose a reason for hiding this comment

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

I want to call out that the ensures result.Valid() that the Polymorph generated stuff can define will be a subset of what we might want to define as Valid. I'm not sure how it applies here as I'm not sure how the generated Polymorph should model Repr type things quite yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

Val and I talked about this offline, and the change she's working on to hook up the Polymorph-generated skeleton code will resolve this. We'll discuss it more when that PR lands, but the TL;DR is that everything exposed through Polymorph will be stateless according to Dafny and hence won't need any Reprs. Any shared mutable state like caching will be relegated to native code and out of Dafny's scope.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, and as for wanting additional post-conditions on top of what Polymorph generates: I would attach those somewhere else, either on the class method that implements the generated trait method, or on a separate helper method with a slightly different name. I worry about messing with the "spiritually generated" code because it would be too easy to add extra PRE-conditions, which would not be sound.

//# the response "Plaintext" length matches the specification of the
//# algorithm suite (algorithm-suites.md)'s Key Derivation Input Length
//# field.
&& materials.algorithmSuiteID.ValidPlaintextDataKey(res.value.plaintextDataKey.value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't this be baked into the subset type? plaintextDataKey.Some? ==> algorithmSuiteID.ValidPlaintestDataKey(plaintextDataKey.value)

Feel free to fast-follow with that since you have this verifying now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Even if it's baked into the type, I'm wondering whether it's still worth it to spell out here for duvet's sake.

src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
import UTF8

//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.5
//= type=implication
Copy link
Contributor

Choose a reason for hiding this comment

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

I didn't validate all the citations line by line as it will be better for someone on the team to do so, but I LOVE how clearly and precisely the requirements are connected to the actual code here. :)

src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
//# API_Decrypt.html), the keyring MUST call with a request constructed
//# as follows:
DecryptRequest(
awsKmsKey,
Copy link
Contributor

Choose a reason for hiding this comment

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

@texastony will see if this cannot be awsKmsKeyId to better align with the variable name in the spec.

src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
texastony
texastony previously approved these changes Oct 22, 2021
Comment on lines +59 to +60
requires UTF8.IsASCIIString(awsKmsKey)
requires 0 < |awsKmsKey| <= MAX_AWS_KMS_IDENTIFIER_LENGTH
Copy link
Contributor

Choose a reason for hiding this comment

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

Should these requires be part of the ParseAwsKmsIdentifier function? Is it really a success if its not ASCII or too long?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So on the subject of ASCII no. That is a hack IMO.
On the length, I'm not sure. I.... tend to agree with you. But that length is not a length of ARN maybe only KMS?

How would you recommend changing it?

//# If the keyring calls AWS KMS GenerateDataKeys, it MUST use the
//# configured AWS KMS client to make the call.
client,
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
Copy link
Contributor

Choose a reason for hiding this comment

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

This may be a nitpick, but can you add a newline before this citation? I skipped over client several times before realizing there was code between two citations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like this? (just commit if you like it)

Suggested change
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7

Comment on lines +132 to +137
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.7
//= type=implication
//# If verified, OnEncrypt MUST do the following with the response
//# from AWS KMS GenerateDataKey
//# (https://docs.aws.amazon.com/kms/latest/APIReference/
//# API_GenerateDataKey.html):
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this is a limitation of duvet, but the breaking down of the MUST sections doesn't really lend itself to useful spec checking here since all the things it must do are (I assume) in a bulleted list afterwards.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this may result in an easy miss somewhere. And will be a pain to update later when duvet improves. I don't quite want to block on it now, but future me will probably grumble at 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.

So are we all on the verbosity train? (@texastony ) If so, we need PRs to update the spec. Once the spec it updated, this will just naturally fall out... So I don't want to block on it

Copy link
Contributor

@texastony texastony Oct 26, 2021

Choose a reason for hiding this comment

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

You have a ship it from me; I am all for the verbosity train.
I also now know that we will have to improve the extract tool if we are going to annotate the message parser.
What is done now is the affordable bridge to the other side.
Once there, we can build a strong bridge across the river
that will endure for multiple ESDK run times.

Comment on lines +203 to +205
if maybeGenerateResponse.Failure? {
return Failure(maybeGenerateResponse.error);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason the :- isn't used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, because I wanted to annotate it.
And the annotation reads a little funny (to me) with :-.
What do you think?

Comment on lines +292 to +295
//# If the decryption materials (structures.md#decryption-materials)
//# already contained a valid plaintext data key OnDecrypt MUST
//# immediately return the unmodified decryption materials
//# (structures.md#decryption-materials).
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 know if this ensure clause really captures the immediately portion of this 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.

It does not. And can not. :(
Maybe we should update the spec?

Or do you think that is super important?

Copy link
Contributor

Choose a reason for hiding this comment

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

The way I would interpret "immediately" in specs is "has no further observable effect". So in Dafny-speak it would be "in this particular case, returns X and modifies nothing". You might even want to assert that KMS is not called.

src/SDK/Keyring/KMSKeyring.dfy Show resolved Hide resolved
predicate Ensures(a: A, r: R)
}

trait {:termination false} ActionWithResult<A, R, E>
Copy link
Contributor

Choose a reason for hiding this comment

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

My 2¢, I think this type is less intuitive than Action<A, Result<R, E>> at usage sites.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is true, however,
(and this is stdlib land)
Dafny, AFAIK, can not do the type magic such that I can make this work.
I may be my failing, it may be that I have not tried hard enough.

action.Ensures(s[j], rs[j])
{
var r := action.Invoke(s[i]);
rs := rs + [r];
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the most efficient way to build an array? I'm asking in the context that this is a more standard library thing and if will be used in many places we should get this right once.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great question! This is a problem that Dafny => n MUST solve.
If you look at Seq.dfy you find this extensively.

As things are compiled into the target runtime
this like of lazy composition is so prevalent that it MUST be addressed.
Making sure? Yup, something that keeps me up nights.

Copy link
Contributor

Choose a reason for hiding this comment

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

@smswz Note that rs is a seq<R> rather than an array<R>. Sequences are immutable and don't live on the heap, so they are much simpler to work with in general. They are also much more amenable to optimization in the Dafny compilers, and in the C# and Java compilers this code will be reasonably efficient because the concatenation is evaluated lazily.

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// // SPDX-License-Identifier: Apache-2.0

Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we delete this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above. Delete when we re-implement the new keyrings

test/Keyring/KMSKeyring.dfy Show resolved Hide resolved

type AwsKmsIdentifierString = s: string |
&& ParseAwsKmsIdentifier(s).Success?
&& UTF8.IsASCIIString(s)
Copy link
Contributor

Choose a reason for hiding this comment

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

What part of the Dafny requires ASCII (I'm assuming it's probably UTF8Valid requires later on)? And what is the consequence if there is some ID out there that uses some non-ASCII UTF8 character?


method OnDecrypt(materials: Materials.ValidDecryptionMaterials,
encryptedDataKeys: seq<Materials.EncryptedDataKey>) returns (res: Result<Materials.ValidDecryptionMaterials, string>)
requires Valid()
ensures Valid()
ensures |encryptedDataKeys| == 0 ==> res.Success? && materials == res.value
Copy link
Contributor

Choose a reason for hiding this comment

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

Why leave this out of OnDecryptPure? I think it is directly implied by what you did carry down + the definition for Valid materials, but I'm just a human and would like Dafny to spell it out for me. We could also model this as a lemma somewhere if you like that organization better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it. Yeah we can leave it out for this change, and whenever we apply the keyring spec using duvet we can ensure our impls follow the spec for keyrings.

Comment on lines +108 to +109
type EmptyEncryptionMaterials = i: EncryptionMaterials | i.Empty() witness *
type UseableEncryptionMaterials = i: EncryptionMaterials | i.Useable() witness *
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't these be subtypes of ValidEncryptionMaterials?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hum... I don't know yet.
Your re-work may make all this irrelevant.
I don't care for the ambiguity in Valid so I think that making these two pure and distinct was better.

Copy link
Contributor

Choose a reason for hiding this comment

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

Your re-work may make all this irrelevant.

This is true, although we may want to turn these into predicate methods to check these properties runtime. Depends how it shakes out. I wouldn't block on this.

client: IAmazonKeyManagementService,
awsKmsKey: string,
grantTokens: seq<KMSUtils.GrantToken>
)
Copy link
Contributor

Choose a reason for hiding this comment

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

I want to call out that the ensures result.Valid() that the Polymorph generated stuff can define will be a subset of what we might want to define as Valid. I'm not sure how it applies here as I'm not sure how the generated Polymorph should model Repr type things quite yet.

//# length) specified by the algorithm suite (algorithm-suites.md)
//# included in the input decryption materials
//# (structures.md#decryption-materials).
&& materials.algorithmSuiteID.ValidPlaintextDataKey(res.value.plaintextDataKey.value)
Copy link
Contributor

Choose a reason for hiding this comment

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

^Valid concern. I'm also not quite sure how to balance this. Maybe there needs to be some documenting on ValidPlaintextDataKey to ensure that the length property is never changed?

Comment on lines +368 to +369
//# If this attempt
//# results in an error, then these errors MUST be collected.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, but move this down to ReduceToSuccess?

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 hope is that ReduceToSuccess is a stdLib function

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean move to L385

);

//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.8
//# If the response does not satisfies these requirements then an error
Copy link
Contributor

Choose a reason for hiding this comment

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

L537 is where we verify the keyID matches and the length is right. I would want some sort of bradcrumb there, or just move this there.

src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
predicate Empty() {
&& plaintextDataKey.None?
&& |encryptedDataKeys| == 0
&& (algorithmSuiteID.SignatureType().Some? ==> signingKey.Some?)
Copy link
Contributor

Choose a reason for hiding this comment

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

Intuitively it's not obvious to me why a predicate called "Empty" needs to check consistency between algorithm suite and signing key. Is there a specific reason we need this last check here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, this is a name problem per se.
The goal here is ValidEmptyMaterials.
As opposed to "just empty".
@lavaleri suggested composing with Valid and perhapses this what should be there?

Copy link
Contributor

Choose a reason for hiding this comment

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

@seebees I think you have at least three different people tripping over this now. :) I think Valid() && ... is the sweet spot personally.

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'm fine with that. I'm leaving it to the polymorph @lavaleri PR to fix all my sins.

src/KMS/KMSUtils.dfy Outdated Show resolved Hide resolved
src/SDK/Keyring/AwsKms/AwsKmsMrkAwareSymmetricKeyring.dfy Outdated Show resolved Hide resolved
}
}

class DecryptEncryptedDataKey
Copy link
Contributor

Choose a reason for hiding this comment

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

General point: some classes like this could use comments explaining what they're for. The name alone isn't getting me there.

robin-aws
robin-aws previously approved these changes Oct 28, 2021
Copy link
Contributor

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

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

I'm satisfied there are no blocking issues from me, and just relying on the fact that at least one other member who is looking closer at the duvet citations will need to approve as well. :)

robin-aws
robin-aws previously approved these changes Oct 28, 2021
Copy link
Contributor

@robin-aws robin-aws left a comment

Choose a reason for hiding this comment

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

Nice that it forced you to clean things up even more. :)

@seebees seebees merged commit 75a24a7 into aws:develop Oct 29, 2021
@seebees seebees deleted the aws-kms-mrk-aware-symmetric-keyring branch October 29, 2021 22:00
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.

6 participants