-
Notifications
You must be signed in to change notification settings - Fork 20
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
feat: AwsKmsMrkAwareSymmetricKeyring #383
Conversation
Adding the keyring and verification
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
There was a problem hiding this 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!
src/StandardLibrary/Closures.dfy
Outdated
method Invoke(a: A) | ||
returns (r: R) | ||
ensures Ensures(a, r) | ||
predicate Ensures(a: A, r: R) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
client: IAmazonKeyManagementService, | ||
awsKmsKey: string, | ||
grantTokens: seq<KMSUtils.GrantToken> | ||
) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 Repr
s. Any shared mutable state like caching will be relegated to native code and out of Dafny's scope.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
import UTF8 | ||
|
||
//= compliance/framework/aws-kms/aws-kms-mrk-aware-symmetric-keyring.txt#2.5 | ||
//= type=implication |
There was a problem hiding this comment.
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. :)
//# API_Decrypt.html), the keyring MUST call with a request constructed | ||
//# as follows: | ||
DecryptRequest( | ||
awsKmsKey, |
There was a problem hiding this comment.
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.
requires UTF8.IsASCIIString(awsKmsKey) | ||
requires 0 < |awsKmsKey| <= MAX_AWS_KMS_IDENTIFIER_LENGTH |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
//= 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 |
//= 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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
if maybeGenerateResponse.Failure? { | ||
return Failure(maybeGenerateResponse.error); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
//# 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). |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
predicate Ensures(a: A, r: R) | ||
} | ||
|
||
trait {:termination false} ActionWithResult<A, R, E> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
|
||
type AwsKmsIdentifierString = s: string | | ||
&& ParseAwsKmsIdentifier(s).Success? | ||
&& UTF8.IsASCIIString(s) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even what I did is not exactly correct
https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/keyring-interface.md#ondecrypt
There was a problem hiding this comment.
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.
type EmptyEncryptionMaterials = i: EncryptionMaterials | i.Empty() witness * | ||
type UseableEncryptionMaterials = i: EncryptionMaterials | i.Useable() witness * |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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> | ||
) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
//# If this attempt | ||
//# results in an error, then these errors MUST be collected. |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
predicate Empty() { | ||
&& plaintextDataKey.None? | ||
&& |encryptedDataKeys| == 0 | ||
&& (algorithmSuiteID.SignatureType().Some? ==> signingKey.Some?) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
} | ||
} | ||
|
||
class DecryptEncryptedDataKey |
There was a problem hiding this comment.
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.
There was a problem hiding this 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. :)
There was a problem hiding this 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. :)
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.