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

chore(dafny): KSA Add test coverage for creating a hv-2 branch key. #1400

Merged
merged 16 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

include "../src/Index.dfy"

// methods to validate a Get* Branch Key result from the Branch Key Store client
module {:options "/functionSyntax:4" } BranchKeyValidators {
import opened Wrappers
import opened StandardLibrary.UInt
import UTF8
import UUID
import Types = AwsCryptographyKeyStoreTypes
import Structure

method VerifyGetKeys(
identifier : string,
keyStore : Types.IKeyStoreClient,
storage : Types.IKeyStorageInterface,
nameonly versionUtf8Bytes?: Option<seq<uint8>> := None,
nameonly encryptionContext : Types.EncryptionContext := map[],
nameonly hierarchyVersion : Types.HierarchyVersion := Types.HierarchyVersion.v1
)
requires
keyStore.ValidState() && storage.ValidState()
modifies
keyStore.Modifies, storage.Modifies
ensures
keyStore.ValidState() && storage.ValidState()

{
var _ := testBeaconKeyHappyCase(
keyStore := keyStore,
branchKeyId := identifier,
encryptionContext := encryptionContext
);

var activeResult := testActiveBranchKeyHappyCase(
keyStore := keyStore,
branchKeyId := identifier,
encryptionContext := encryptionContext
);
var versionString :- expect UTF8.Decode(activeResult.branchKeyVersion);

var _ := testBranchKeyVersionHappyCase(
keyStore := keyStore,
branchKeyId := identifier,
branchKeyIdActiveVersion := versionString,
branchKeyIdActiveVersionUtf8Bytes := activeResult.branchKeyVersion,
encryptionContext := encryptionContext
);
VerifyGetKeysFromStorage(identifier, storage, hierarchyVersion:=hierarchyVersion);

//= aws-encryption-sdk-specification/framework/branch-key-store.md#branch-key-and-beacon-key-creation
//= type=test
//# This guid MUST be [version 4 UUID](https://www.ietf.org/rfc/rfc4122.txt)
var versionByteUUID :- expect UUID.ToByteArray(versionString);
var versionRoundTrip :- expect UUID.FromByteArray(versionByteUUID);
expect versionRoundTrip == versionString;
}


method VerifyGetKeysFromStorage(
identifier : string,
storage : Types.IKeyStorageInterface,
nameonly hierarchyVersion : Types.HierarchyVersion := Types.HierarchyVersion.v1
)
requires storage.ValidState()
modifies storage.Modifies
ensures storage.ValidState()
{
var active :- expect storage.GetEncryptedActiveBranchKey(
Types.GetEncryptedActiveBranchKeyInput(
Identifier := identifier
)
);
expect active.Item.Type.ActiveHierarchicalSymmetricVersion?;

var beacon :- expect storage.GetEncryptedBeaconKey(
Types.GetEncryptedBeaconKeyInput(
Identifier := identifier
)
);
expect beacon.Item.Type.ActiveHierarchicalSymmetricBeacon?;

var version :- expect storage.GetEncryptedBranchKeyVersion(
Types.GetEncryptedBranchKeyVersionInput(
Identifier := identifier,
Version := active.Item.Type.ActiveHierarchicalSymmetricVersion.Version
)
);
expect version.Item.Type.HierarchicalSymmetricVersion?;

var hvMatches? := match hierarchyVersion {
case v1
=>
&& branchKeyContextSaysHV1(active.Item.EncryptionContext)
&& branchKeyContextSaysHV1(beacon.Item.EncryptionContext)
&& branchKeyContextSaysHV1(version.Item.EncryptionContext)
case v2
=>
&& branchKeyContextSaysHV2(active.Item.EncryptionContext)
&& branchKeyContextSaysHV2(beacon.Item.EncryptionContext)
&& branchKeyContextSaysHV2(version.Item.EncryptionContext)
};
if (!hvMatches?) {
print "HV did not match expectation, did bkc have hv?: ", Structure.HIERARCHY_VERSION in active.Item.EncryptionContext;
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this print statement? Can we leave it as as commented code?

if (Structure.HIERARCHY_VERSION in active.Item.EncryptionContext) {
print " Actual HV: ", active.Item.EncryptionContext[Structure.HIERARCHY_VERSION];
}
}
expect hvMatches?, "Hierarchy Version did not match expectation.";

//= aws-encryption-sdk-specification/framework/branch-key-store.md#branch-key-and-beacon-key-creation
//= type=test
//# This timestamp MUST be in ISO 8601 format in UTC, to microsecond precision (e.g. “YYYY-MM-DDTHH:mm:ss.ssssssZ“)
expect ISO8601?(active.Item.CreateTime);
expect ISO8601?(beacon.Item.CreateTime);
expect ISO8601?(version.Item.CreateTime);
}

predicate branchKeyContextSaysHV1(bkc: Types.EncryptionContextString)
{
Structure.HIERARCHY_VERSION in bkc && bkc[Structure.HIERARCHY_VERSION] == Structure.HIERARCHY_VERSION_VALUE_1
}

predicate branchKeyContextSaysHV2(bkc: Types.EncryptionContextString)
{
Structure.HIERARCHY_VERSION in bkc && bkc[Structure.HIERARCHY_VERSION] == Structure.HIERARCHY_VERSION_VALUE_2
}

method testActiveBranchKeyHappyCase(
keyStore: Types.IKeyStoreClient,
branchKeyId: string,
nameonly versionUtf8Bytes?: Option<seq<uint8>> := None,
nameonly encryptionContext : Types.EncryptionContext := map[]
) returns (output: Types.BranchKeyMaterials)
requires keyStore.ValidState()
modifies keyStore.Modifies
ensures keyStore.ValidState()
{
var activeResult :- expect keyStore.GetActiveBranchKey(
Types.GetActiveBranchKeyInput(
branchKeyIdentifier := branchKeyId
));
expect isValidActiveBranchKeyResult?(activeResult, branchKeyId, encryptionContext, versionUtf8Bytes?);
return activeResult.branchKeyMaterials;
}

method testBranchKeyVersionHappyCase(
keyStore: Types.IKeyStoreClient,
branchKeyId: string,
branchKeyIdActiveVersion: string,
branchKeyIdActiveVersionUtf8Bytes: seq<uint8>,
nameonly encryptionContext : Types.EncryptionContext := map[]
) returns (output: Types.BranchKeyMaterials)
requires keyStore.ValidState()
modifies keyStore.Modifies
ensures keyStore.ValidState()
{
var versionResult :- expect keyStore.GetBranchKeyVersion(
Types.GetBranchKeyVersionInput(
branchKeyIdentifier := branchKeyId,
branchKeyVersion := branchKeyIdActiveVersion
));
expect isValidBranchKeyVersionResult?(versionResult, branchKeyId, encryptionContext, branchKeyIdActiveVersionUtf8Bytes);
return versionResult.branchKeyMaterials;
}

method testBeaconKeyHappyCase(
keyStore: Types.IKeyStoreClient,
branchKeyId: string,
nameonly encryptionContext : Types.EncryptionContext := map[]
) returns (output: Types.BeaconKeyMaterials)
requires keyStore.ValidState()
modifies keyStore.Modifies
ensures keyStore.ValidState()
{
var beaconKeyResult :- expect keyStore.GetBeaconKey(
Types.GetBeaconKeyInput(
branchKeyIdentifier := branchKeyId
));
expect beaconKeyResult.beaconKeyMaterials.beaconKeyIdentifier == branchKeyId, "Wrong Branch Key ID.";
expect beaconKeyResult.beaconKeyMaterials.beaconKey.Some?, "Beacon Key Materials MUST be present.";
expect |beaconKeyResult.beaconKeyMaterials.beaconKey.value| == 32, "Lenght of Beacon Key Materials MUST be 32 bytes.";
if (beaconKeyResult.beaconKeyMaterials.encryptionContext != encryptionContext) {
print "Beacon Key's encryption context is incorrect. Expected: ", encryptionContext, " but got: ", beaconKeyResult.beaconKeyMaterials.encryptionContext;
Copy link
Member

Choose a reason for hiding this comment

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

We can remove the print statement

expect false, "Beacon Key's encryption context is incorrect.";
}
expect isValidBeaconResult?(beaconKeyResult, branchKeyId, encryptionContext);
return beaconKeyResult.beaconKeyMaterials;
}

predicate isValidBeaconResult?(
beaconKeyResult: Types.GetBeaconKeyOutput,
branchKeyId: string,
encryptionContext : Types.EncryptionContext
) {
&& beaconKeyResult.beaconKeyMaterials.beaconKeyIdentifier == branchKeyId
&& beaconKeyResult.beaconKeyMaterials.beaconKey.Some?
&& |beaconKeyResult.beaconKeyMaterials.beaconKey.value| == 32
&& beaconKeyResult.beaconKeyMaterials.encryptionContext == encryptionContext
}

predicate isValidActiveBranchKeyResult?(
branchKeyResult: Types.GetActiveBranchKeyOutput,
branchKeyId: string,
encryptionContext : Types.EncryptionContext,
branchKeyIdActiveVersionUtf8Bytes: Option<seq<uint8>>
)
{
&& branchKeyResult.branchKeyMaterials.branchKeyIdentifier == branchKeyId
&& (branchKeyIdActiveVersionUtf8Bytes.None? ||
branchKeyResult.branchKeyMaterials.branchKeyVersion == branchKeyIdActiveVersionUtf8Bytes.value)
&& |branchKeyResult.branchKeyMaterials.branchKey| == 32
&& branchKeyResult.branchKeyMaterials.encryptionContext == encryptionContext
}

predicate isValidBranchKeyVersionResult?(
versionResult: Types.GetBranchKeyVersionOutput,
branchKeyId: string,
encryptionContext : Types.EncryptionContext,
branchKeyIdActiveVersionUtf8Bytes: seq<uint8>
)
{
&& versionResult.branchKeyMaterials.branchKeyIdentifier == branchKeyId
&& versionResult.branchKeyMaterials.branchKeyVersion == branchKeyIdActiveVersionUtf8Bytes
&& |versionResult.branchKeyMaterials.branchKey| == 32
&& versionResult.branchKeyMaterials.encryptionContext == encryptionContext
}

//= aws-encryption-sdk-specification/framework/branch-key-store.md#branch-key-and-beacon-key-creation
//= type=test
//# This timestamp MUST be in ISO 8601 format in UTC, to microsecond precision (e.g. “YYYY-MM-DDTHH:mm:ss.ssssssZ“)
lemma ISO8601Test()
{
assert ISO8601?("2024-08-06T17:23:25.000874Z");
}

//= aws-encryption-sdk-specification/framework/branch-key-store.md#branch-key-and-beacon-key-creation
//= type=test
//# This timestamp MUST be in ISO 8601 format in UTC, to microsecond precision (e.g. “YYYY-MM-DDTHH:mm:ss.ssssssZ“)
predicate ISO8601?(
CreateTime: string
)
{
// “YYYY-MM-DDTHH:mm:ss.ssssssZ“
&& |CreateTime| == 27
&& CreateTime[4] == '-'
&& CreateTime[7] == '-'
&& CreateTime[10] == 'T'
&& CreateTime[13] == ':'
&& CreateTime[16] == ':'
&& CreateTime[19] == '.'
&& CreateTime[26] == 'Z'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ module Fixtures {

const branchKeyStoreName := "KeyStoreDdbTable"
const logicalKeyStoreName := branchKeyStoreName
// hierarchy-version-1 branch key
const branchKeyId := "3f43a9af-08c5-4317-b694-3d3e883dcaef"
const branchKeyIdActiveVersion := "a4905627-4b7f-4272-a847-f50dae245737"
// This is branchKeyIdActiveVersion above, as utf8bytes
Expand All @@ -71,14 +72,16 @@ module Fixtures {
56, 52, 55, 45, 102, 53, 48, 100, 97, 101,
50, 52, 53, 55, 51, 55
]

const branchKeyIdWithEC := "4bb57643-07c1-419e-92ad-0df0df149d7c"
const hv2BranchKeyId := "test-hv2-branch-key-badaa332-29f2-4c72-8ad7-071eb48499c3"
const hv2BranchKeyVersion := "347fdc7d-e93f-4166-97c2-5f5e0053d335"
// hierarchy-version-2 branch key
const hv2BranchKeyId := "4a0c7b92-3703-4209-8961-24b07ab6562b"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated with new static branch key for hierarchy-version-2 as the old one is created with custom EC

const hv2BranchKeyVersion := "a0496b5c-e048-42bc-8b75-68a004851803"
// This is hv2BranchKeyVersion above, as utf8bytes
// https://cyberchef.infosec.amazon.dev/#recipe=Encode_text('UTF-8%20(65001)')To_Decimal('Comma',false)&input=YTA0OTZiNWMtZTA0OC00MmJjLThiNzUtNjhhMDA0ODUxODAz&oenc=65001
const hv2BranchKeyIdActiveVersionUtf8Bytes: seq<uint8> := [
51, 52, 55, 102, 100, 99, 55, 100, 45, 101, 57,
51, 102, 45, 52, 49, 54, 54, 45, 57, 55, 99, 50,
45, 53, 102, 53, 101, 48, 48, 53, 51, 100, 51, 51, 53
97, 48, 52, 57, 54, 98, 53, 99, 45, 101, 48, 52,
56, 45, 52, 50, 98, 99, 45, 56, 98, 55, 53, 45,
54, 56, 97, 48, 48, 52, 56, 53, 49, 56, 48, 51
]
// THESE ARE TESTING RESOURCES DO NOT USE IN A PRODUCTION ENVIRONMENT
const keyArn := "arn:aws:kms:us-west-2:370957321024:key/9d989aa2-2f9c-438c-a745-cc57d3ad0126"
Expand All @@ -99,6 +102,7 @@ module Fixtures {
const KmsSrkConfigWest : Types.KMSConfiguration := Types.KMSConfiguration.kmsKeyArn(MrkArnWest)
const KmsMrkConfigAP : Types.KMSConfiguration := Types.KMSConfiguration.kmsMRKeyArn(MrkArnAP)
const KmsMrkEC : Types.EncryptionContext := map[UTF8.EncodeAscii("abc") := UTF8.EncodeAscii("123")]
const RobbieEC : Types.EncryptionContext := map[UTF8.EncodeAscii("Robbie") := UTF8.EncodeAscii("is a dog.")]
const EastBranchKey : string := "MyEastBranch2"
const EastBranchKeyIdActiveVersion : string := "6f22825b-bd56-4434-83e2-2782e2160172"
const EastBranchKeyBranchKeyIdActiveVersionUtf8Bytes: seq<uint8> := [
Expand Down Expand Up @@ -255,19 +259,14 @@ module Fixtures {
return Success(keyStore);
}

method KeyStoreWithOptionalClient(
nameonly kmsId: string := keyArn,
method KeyStoreFromKMSConfig(
nameonly kmsConfig: Types.KMSConfiguration,
nameonly physicalName: string := branchKeyStoreName,
nameonly logicalName: string := logicalKeyStoreName,
nameonly ddbClient?: Option<DDB.Types.IDynamoDBClient> := None,
nameonly kmsClient?: Option<KMS.Types.IKMSClient> := None,
nameonly srkKey: bool := true,
nameonly mrkKey: bool := false
nameonly ddbClient?: Option<DDB.Types.IDynamoDBClient> := None
)
returns (output: Result<Types.IKeyStoreClient, Types.Error>)
requires DDB.Types.IsValid_TableName(physicalName)
requires KMS.Types.IsValid_KeyIdType(kmsId)
requires srkKey != mrkKey
ensures output.Success? ==> output.value.ValidState()
ensures output.Success?
==>
Expand All @@ -278,16 +277,6 @@ module Fixtures {
if ddbClient?.Some? {
assume {:axiom} fresh(ddbClient?.value) && fresh(ddbClient?.value.Modifies);
}
if kmsClient?.Some? {
assume {:axiom} fresh(kmsClient?.value) && fresh(kmsClient?.value.Modifies);
}
expect srkKey != mrkKey;
var kmsConfig := if srkKey then
createSrkKMSConfig(kmsId)
else
createMrkKMSConfig(kmsId);
expect srkKey ==> kmsConfig.kmsKeyArn?;
expect mrkKey ==> kmsConfig.kmsMRKeyArn?;
var keyStoreConfig := Types.KeyStoreConfig(
id := None,
kmsConfiguration := kmsConfig,
Expand All @@ -297,31 +286,12 @@ module Fixtures {
Types.DynamoDBTable(
ddbTableName := physicalName,
ddbClient := ddbClient?
))),
keyManagement := Some(
Types.kms(
Types.AwsKms(
kmsClient := kmsClient?
)))
);
var keyStore :- expect KeyStore.KeyStore(keyStoreConfig);
return Success(keyStore);
}

function method createSrkKMSConfig(kmsId: string) : (output: Types.KMSConfiguration)
requires KMS.Types.IsValid_KeyIdType(kmsId)
ensures output.kmsKeyArn?
{
Types.KMSConfiguration.kmsKeyArn(kmsId)
}

function method createMrkKMSConfig(kmsId: string) : (output: Types.KMSConfiguration)
requires KMS.Types.IsValid_KeyIdType(kmsId)
ensures output.kmsMRKeyArn?
{
Types.KMSConfiguration.kmsMRKeyArn(kmsId)
}

datatype allThree = | allThree (
active: Types.EncryptedHierarchicalKey,
beacon: Types.EncryptedHierarchicalKey,
Expand Down
Loading
Loading