diff --git a/.github/workflows/ci_test_vector_net.yml b/.github/workflows/ci_test_vector_net.yml index 528e8e7ec..dcbd96967 100644 --- a/.github/workflows/ci_test_vector_net.yml +++ b/.github/workflows/ci_test_vector_net.yml @@ -58,7 +58,7 @@ jobs: - name: Test TestVectors on .NET 6.0 working-directory: ./TestVectors/runtimes/net run: | - cp ../java/decrypt_java.json ../java/decrypt_dotnet.json . + cp ../java/decrypt_java_*.json ../java/decrypt_dotnet_*.json . dotnet run cp ../java/*.json . dotnet run --framework net6.0 diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy index 812d451fb..9d2bbb34e 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy @@ -43,10 +43,10 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" nameonly keyStore: AwsCryptographyKeyStoreTypes.IKeyStoreClient , nameonly keySource: BeaconKeySource , nameonly standardBeacons: StandardBeaconList , - nameonly compoundBeacons: Option , - nameonly virtualFields: Option , - nameonly encryptedParts: Option , - nameonly signedParts: Option + nameonly compoundBeacons: Option := Option.None , + nameonly virtualFields: Option := Option.None , + nameonly encryptedParts: Option := Option.None , + nameonly signedParts: Option := Option.None ) type BeaconVersionList = x: seq | IsValid_BeaconVersionList(x) witness * predicate method IsValid_BeaconVersionList(x: seq) { @@ -59,9 +59,9 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype CompoundBeacon = | CompoundBeacon ( nameonly name: string , nameonly split: Char , - nameonly encrypted: Option , - nameonly signed: Option , - nameonly constructors: Option + nameonly encrypted: Option := Option.None , + nameonly signed: Option := Option.None , + nameonly constructors: Option := Option.None ) type CompoundBeaconList = x: seq | IsValid_CompoundBeaconList(x) witness * predicate method IsValid_CompoundBeaconList(x: seq) { @@ -217,16 +217,16 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype DynamoDbTableEncryptionConfig = | DynamoDbTableEncryptionConfig ( nameonly logicalTableName: string , nameonly partitionKeyName: ComAmazonawsDynamodbTypes.KeySchemaAttributeName , - nameonly sortKeyName: Option , - nameonly search: Option , + nameonly sortKeyName: Option := Option.None , + nameonly search: Option := Option.None , nameonly attributeActionsOnEncrypt: AttributeActions , - nameonly allowedUnsignedAttributes: Option , - nameonly allowedUnsignedAttributePrefix: Option , - nameonly algorithmSuiteId: Option , - nameonly keyring: Option , - nameonly cmm: Option , - nameonly legacyOverride: Option , - nameonly plaintextOverride: Option + nameonly allowedUnsignedAttributes: Option := Option.None , + nameonly allowedUnsignedAttributePrefix: Option := Option.None , + nameonly algorithmSuiteId: Option := Option.None , + nameonly keyring: Option := Option.None , + nameonly cmm: Option := Option.None , + nameonly legacyOverride: Option := Option.None , + nameonly plaintextOverride: Option := Option.None ) type DynamoDbTableEncryptionConfigList = map datatype DynamoDbTablesEncryptionConfig = | DynamoDbTablesEncryptionConfig ( @@ -307,7 +307,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" nameonly policy: LegacyPolicy , nameonly encryptor: ILegacyDynamoDbEncryptor , nameonly attributeActionsOnEncrypt: AttributeActions , - nameonly defaultAttributeFlag: Option + nameonly defaultAttributeFlag: Option := Option.None ) datatype LegacyPolicy = | FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT @@ -319,7 +319,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype MultiKeyStore = | MultiKeyStore ( nameonly keyFieldName: string , nameonly cacheTTL: int32 , - nameonly cache: Option + nameonly cache: Option := Option.None ) datatype PartOnly = | PartOnly ( @@ -345,7 +345,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype SignedPart = | SignedPart ( nameonly name: string , nameonly prefix: Prefix , - nameonly loc: Option + nameonly loc: Option := Option.None ) type SignedPartsList = x: seq | IsValid_SignedPartsList(x) witness * predicate method IsValid_SignedPartsList(x: seq) { @@ -358,8 +358,8 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype StandardBeacon = | StandardBeacon ( nameonly name: string , nameonly length: BeaconBitLength , - nameonly loc: Option , - nameonly style: Option + nameonly loc: Option := Option.None , + nameonly style: Option := Option.None ) type StandardBeaconList = x: seq | IsValid_StandardBeaconList(x) witness * predicate method IsValid_StandardBeaconList(x: seq) { @@ -386,7 +386,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" } datatype VirtualPart = | VirtualPart ( nameonly loc: TerminalLocation , - nameonly trans: Option + nameonly trans: Option := Option.None ) type VirtualPartList = x: seq | IsValid_VirtualPartList(x) witness * predicate method IsValid_VirtualPartList(x: seq) { @@ -453,13 +453,20 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" import Operations : AbstractAwsCryptographyDbEncryptionSdkDynamoDbOperations function method DefaultDynamoDbEncryptionConfig(): DynamoDbEncryptionConfig method DynamoDbEncryption(config: DynamoDbEncryptionConfig := DefaultDynamoDbEncryptionConfig()) - returns (res: Result) + returns (res: Result) ensures res.Success? ==> && fresh(res.value) && fresh(res.value.Modifies) && fresh(res.value.History) && res.value.ValidState() + // Helper function for the benefit of native code to create a Success(client) without referring to Dafny internals + function method CreateSuccessOfClient(client: IDynamoDbEncryptionClient): Result { + Success(client) + } // Helper function for the benefit of native code to create a Failure(error) without referring to Dafny internals + function method CreateFailureOfError(error: Error): Result { + Failure(error) + } class DynamoDbEncryptionClient extends IDynamoDbEncryptionClient { constructor(config: Operations.InternalConfig) diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy index d9ca15296..eac7192b1 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy @@ -762,6 +762,9 @@ operation GetBranchKeyIdFromDdbKey { output: GetBranchKeyIdFromDdbKeyOutput } +//= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#dynamodbkeybranchkeyidsupplier +//= type=implication +//# This operation MUST take in a DDB `Key` structure (and attribute map containing the partition and sort attributes) as input. @javadoc("Inputs for getting the Branch Key that should be used for wrapping and unwrapping data keys.") structure GetBranchKeyIdFromDdbKeyInput { @required @@ -769,6 +772,9 @@ structure GetBranchKeyIdFromDdbKeyInput { ddbKey: Key } +//= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#dynamodbkeybranchkeyidsupplier +//= type=implication +//# This operation MUST return a branch key id (string) as output. @javadoc("Outputs for getting the Branch Key that should be used for wrapping and unwrapping data keys.") structure GetBranchKeyIdFromDdbKeyOutput { @required @@ -776,12 +782,18 @@ structure GetBranchKeyIdFromDdbKeyOutput { branchKeyId: String } +//= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#operation +//= type=implication +//# The `CreateDynamoDbEncryptionBranchKeyIdSupplier` is an operation that MUST be vended alongside the DynamoDb Item Encryptor. @javadoc("Create a Branch Key Supplier for use with the Hierarchical Keyring that decides what Branch Key to use based on the primary key of the DynamoDB item being read or written.") operation CreateDynamoDbEncryptionBranchKeyIdSupplier { input: CreateDynamoDbEncryptionBranchKeyIdSupplierInput, output: CreateDynamoDbEncryptionBranchKeyIdSupplierOutput } +//= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#input +//= type=implication +//# This operation MUST take in a [DynamoDbKeyBranchKeyIdSupplier](#dynamodb-key-branch-key-id-supplier) as input. @javadoc("Inputs for creating a Branch Key Supplier from a DynamoDB Key Branch Key Id Supplier") structure CreateDynamoDbEncryptionBranchKeyIdSupplierInput { @required @@ -789,6 +801,9 @@ structure CreateDynamoDbEncryptionBranchKeyIdSupplierInput { ddbKeyBranchKeyIdSupplier: DynamoDbKeyBranchKeyIdSupplierReference, } +//= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#output +//= type=implication +//# This operation MUST output a BranchKeyIdSupplierReference. @javadoc("Outputs for creating a Branch Key Supplier from a DynamoDB Key Branch Key Id Supplier") structure CreateDynamoDbEncryptionBranchKeyIdSupplierOutput { @required diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/ConfigToInfo.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/ConfigToInfo.dfy index 69e80e25a..af965a325 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/ConfigToInfo.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/ConfigToInfo.dfy @@ -88,6 +88,7 @@ module SearchConfigToInfo { match outer.attributeActionsOnEncrypt[keyFieldName] { case DO_NOTHING => Success(true) case SIGN_ONLY => Success(false) + case SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT => Success(false) case ENCRYPT_AND_SIGN => Failure(E("Beacon key field name " + keyFieldName + " is configured as ENCRYPT_AND_SIGN which is not allowed.")) } } @@ -264,7 +265,10 @@ module SearchConfigToInfo { { && var name := loc[0].key; && name in outer.attributeActionsOnEncrypt - && outer.attributeActionsOnEncrypt[name] == SE.SIGN_ONLY + && ( + || outer.attributeActionsOnEncrypt[name] == SE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + || outer.attributeActionsOnEncrypt[name] == SE.SIGN_ONLY + ) } // is this terminal location encrypted diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy index 118f2276a..8e95530b9 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy @@ -1,26 +1,17 @@ // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -include "DynamoToStruct.dfy" -include "Util.dfy" +include "../../DynamoDbItemEncryptor/src/Util.dfy" module DynamoDbEncryptionBranchKeyIdSupplier { import opened AwsCryptographyDbEncryptionSdkDynamoDbTypes - import MPL = AwsCryptographyMaterialProvidersTypes - import DDB = ComAmazonawsDynamodbTypes - import opened Seq import opened Wrappers - import opened StandardLibrary.UInt - import DynamoToStruct - import Base64 - import DynamoDbEncryptionUtil - - const MPL_EC_PARTITION_NAME: UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-partition-name") - const MPL_EC_SORT_NAME: UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-sort-name") + import MPL = AwsCryptographyMaterialProvidersTypes + import DynamoDbItemEncryptorUtil class DynamoDbEncryptionBranchKeyIdSupplier extends MPL.IBranchKeyIdSupplier - { + { const ddbKeyBranchKeyIdSupplier: IDynamoDbKeyBranchKeyIdSupplier predicate ValidState() @@ -48,7 +39,7 @@ module DynamoDbEncryptionBranchKeyIdSupplier { {true} method GetBranchKeyId'(input: MPL.GetBranchKeyIdInput) - returns (output: Result) + returns (output: Result) requires ValidState() modifies Modifies - {History} decreases Modifies - {History} @@ -56,68 +47,23 @@ module DynamoDbEncryptionBranchKeyIdSupplier { ensures GetBranchKeyIdEnsuresPublicly(input, output) ensures unchanged(History) { - var context := input.encryptionContext; - var attrMap: DDB.AttributeMap := map[]; + var attrMapR := DynamoDbItemEncryptorUtil.ConvertContextForSelector(input.encryptionContext); + var attrMap :- attrMapR.MapFailure(e => MPL.AwsCryptographicMaterialProvidersException(message:=e)); - // Add partition key to map - :- Need(MPL_EC_PARTITION_NAME in context.Keys, - MPL.AwsCryptographicMaterialProvidersException( - message := "Invalid encryption context: Missing partition name")); - var partitionName := context[MPL_EC_PARTITION_NAME]; - var partitionValueKey := DynamoDbEncryptionUtil.DDBEC_EC_ATTR_PREFIX + partitionName; - :- Need(partitionValueKey in context.Keys, - MPL.AwsCryptographicMaterialProvidersException( - message := "Invalid encryption context: Missing partition value")); - attrMap :- AddAttributeToMap(partitionValueKey, context[partitionValueKey], attrMap); - - if MPL_EC_SORT_NAME in context.Keys { - var sortName := context[MPL_EC_SORT_NAME]; - var sortValueKey := DynamoDbEncryptionUtil.DDBEC_EC_ATTR_PREFIX + sortName; - :- Need(sortValueKey in context.Keys, - MPL.AwsCryptographicMaterialProvidersException( - message := "Invalid encryption context: Missing sort value")); - attrMap :- AddAttributeToMap(sortValueKey, context[sortValueKey], attrMap); - } - // Get branch key id from these DDB attributes var branchKeyIdR := ddbKeyBranchKeyIdSupplier.GetBranchKeyIdFromDdbKey( - GetBranchKeyIdFromDdbKeyInput(ddbKey := attrMap) - ); + GetBranchKeyIdFromDdbKeyInput(ddbKey := attrMap) + ); + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //# - Otherwise, this operation MUST fail. var branchKeyIdOut :- branchKeyIdR.MapFailure(ConvertToMplError); + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //# - If successful, the resulting string MUST be outputted by this operation. return Success(MPL.GetBranchKeyIdOutput(branchKeyId:=branchKeyIdOut.branchKeyId)); } } - function method AddAttributeToMap(ddbAttrKey: seq, encodedAttrValue: seq, attrMap: DDB.AttributeMap) - : (res: Result) - requires |ddbAttrKey| >= |DynamoDbEncryptionUtil.DDBEC_EC_ATTR_PREFIX| - { - // Obtain attribute name from EC kvPair key - var ddbAttrNameBytes := ddbAttrKey[|DynamoDbEncryptionUtil.DDBEC_EC_ATTR_PREFIX|..]; - var ddbAttrName :- UTF8.Decode(ddbAttrNameBytes) - .MapFailure(e => MPL.AwsCryptographicMaterialProvidersException(message:=e)); - :- Need(DDB.IsValid_AttributeName(ddbAttrName), - MPL.AwsCryptographicMaterialProvidersException( - message := "Invalid serialization of DDB Attribute in encryption context.")); - - // Obtain attribute value from EC kvPair value - var utf8DecodedVal :- UTF8.Decode(encodedAttrValue) - .MapFailure(e => MPL.AwsCryptographicMaterialProvidersException(message:=e)); - var base64DecodedVal :- Base64.Decode(utf8DecodedVal) - .MapFailure(e => MPL.AwsCryptographicMaterialProvidersException(message:=e)); - :- Need(|base64DecodedVal| >= 2, - MPL.AwsCryptographicMaterialProvidersException( - message := "Invalid serialization of DDB Attribute in encryption context.")); - var typeId := base64DecodedVal[..2]; - var serializedValue := base64DecodedVal[2..]; - var ddbAttrValue :- DynamoToStruct.BytesToAttr(serializedValue, typeId, false) - .MapFailure(e => MPL.AwsCryptographicMaterialProvidersException(message:=e)); - - // Add to our AttributeMap - Success(attrMap[ddbAttrName := ddbAttrValue.val]) - } - function method ConvertToMplError(err: Error) :(ret: MPL.Error) { diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy index a47fc0b67..38e852c4d 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy @@ -17,6 +17,7 @@ module DynamoToStruct { import SortedSets import Seq import Norm = DynamoDbNormalizeNumber + import SE = StructuredEncryptionUtil type Error = AwsCryptographyDbEncryptionSdkDynamoDbTypes.Error @@ -36,7 +37,7 @@ module DynamoToStruct { //= type=implication //# - MUST contain a [Structured Data Terminal](../structured-encryption/structures.md#structured-data-terminal) //# for each attribute on the DynamoDB Item, and no others. - ensures ret.Success? ==> ret.value.Keys == item.Keys; + ensures ret.Success? ==> ret.value.Keys == item.Keys //= specification/dynamodb-encryption-client/ddb-item-conversion.md#convert-ddb-item-to-structured-data //= type=implication @@ -78,7 +79,7 @@ module DynamoToStruct { //= type=implication //# - MUST contain an Attribute for every [Structured Data Terminal](../structured-encryption/structures.md#structured-data-terminal) //# on the Structured Data, and not other Attributes. - ensures ret.Success? ==> ret.value.Keys == s.Keys; + ensures ret.Success? ==> ret.value.Keys == s.Keys //= specification/dynamodb-encryption-client/ddb-item-conversion.md#convert-structured-data-to-ddb-item //= type=implication @@ -125,11 +126,11 @@ module DynamoToStruct { // Prove round trip. A work in progress lemma RoundTripFromStructured(s : StructuredData) - ensures StructuredToAttr(s).Success? && s.content.Terminal.typeId == BINARY ==> + ensures StructuredToAttr(s).Success? && s.content.Terminal.typeId == SE.BINARY ==> && AttrToStructured(StructuredToAttr(s).value).Success? - ensures StructuredToAttr(s).Success? && s.content.Terminal.typeId == BOOLEAN ==> + ensures StructuredToAttr(s).Success? && s.content.Terminal.typeId == SE.BOOLEAN ==> && AttrToStructured(StructuredToAttr(s).value).Success? - ensures StructuredToAttr(s).Success? && s.content.Terminal.typeId == NULL ==> + ensures StructuredToAttr(s).Success? && s.content.Terminal.typeId == SE.NULL ==> && AttrToStructured(StructuredToAttr(s).value).Success? { reveal AttrToStructured(); @@ -194,62 +195,24 @@ module DynamoToStruct { Success(attrValueAndLength.val) } - const BOOL_LEN : nat := 1; // number of bytes in an encoded boolean - const TYPEID_LEN : nat := 2; // number of bytes in a TerminalTypeId - const LENGTH_LEN : nat := 4; // number of bytes in an encoded count or length - const PREFIX_LEN : nat := 6; // number of bytes in a prefix, i.e. 2-byte type and 4-byte length - - - //= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#type-id - //= type=implication - //# Type ID indicates what type a DynamoDB Attribute Value MUST - //# be serialized and deserialized as. - //# | Attribute Value Data Type | Terminal Type ID | - //# | ------------------------- | ---------------- | - //# | Null (NULL) | 0x0000 | - //# | String (S) | 0x0001 | - //# | Number (N) | 0x0002 | - //# | Binary (B) | 0xFFFF | - //# | Boolean (BOOL) | 0x0004 | - //# | String Set (SS) | 0x0101 | - //# | Number Set (NS) | 0x0102 | - //# | Binary Set (BS) | 0x01FF | - //# | Map (M) | 0x0200 | - //# | List (L) | 0x0300 | - const TERM_T : uint8 := 0x00; - const SET_T : uint8 := 0x01; - const MAP_T : uint8 := 0x02; - const LIST_T : uint8 := 0x03; - const NULL_T : uint8 := 0x00; - const STRING_T : uint8 := 0x01; - const NUMBER_T : uint8 := 0x02; - const BINARY_T : uint8 := 0xFF; - const BOOLEAN_T : uint8 := 0x04; - - const NULL : TerminalTypeId := [TERM_T, NULL_T]; - const STRING : TerminalTypeId := [TERM_T, STRING_T]; - const NUMBER : TerminalTypeId := [TERM_T, NUMBER_T]; - const BINARY : TerminalTypeId := [0xFF, 0xFF]; - const BOOLEAN : TerminalTypeId := [TERM_T, BOOLEAN_T]; - const STRING_SET : TerminalTypeId := [SET_T, STRING_T]; - const NUMBER_SET : TerminalTypeId := [SET_T, NUMBER_T]; - const BINARY_SET : TerminalTypeId := [SET_T, BINARY_T]; - const MAP : TerminalTypeId := [MAP_T, NULL_T]; - const LIST : TerminalTypeId := [LIST_T, NULL_T]; + const BOOL_LEN : nat := 1 // number of bytes in an encoded boolean + const TYPEID_LEN : nat := 2 // number of bytes in a TerminalTypeId + const LENGTH_LEN : nat := 4 // number of bytes in an encoded count or length + const PREFIX_LEN : nat := 6 // number of bytes in a prefix, i.e. 2-byte type and 4-byte length function method AttrToTypeId(a : AttributeValue) : TerminalTypeId { match a { - case S(s) => STRING - case N(n) => NUMBER - case B(b) => BINARY - case SS(ss) => STRING_SET - case NS(ns) => NUMBER_SET - case BS(bs) => BINARY_SET - case M(m) => MAP - case L(l) => LIST - case NULL(n) => NULL - case BOOL(b) => BOOLEAN + case S(s) => SE.STRING + case N(n) => SE.NUMBER + case B(b) => SE.BINARY + case SS(ss) => SE.STRING_SET + case NS(ns) => SE.NUMBER_SET + case BS(bs) => SE.BINARY_SET + case M(m) => SE.MAP + case L(l) => SE.LIST + case NULL(n) => SE.NULL + case BOOL(b) => SE.BOOLEAN } } @@ -274,9 +237,9 @@ module DynamoToStruct { && (!a.BOOL ==> ret.Success? && |ret.value| == BOOL_LEN && ret.value[0] == 0) ensures a.BOOL? && prefix && depth <= MAX_STRUCTURE_DEPTH ==> && (a.BOOL ==> (ret.Success? && |ret.value| == PREFIX_LEN+BOOL_LEN && ret.value[PREFIX_LEN] == 1 - && ret.value[0..TYPEID_LEN] == BOOLEAN && ret.value[TYPEID_LEN..PREFIX_LEN] == [0,0,0,1])) + && ret.value[0..TYPEID_LEN] == SE.BOOLEAN && ret.value[TYPEID_LEN..PREFIX_LEN] == [0,0,0,1])) && (!a.BOOL ==> (ret.Success? && |ret.value| == PREFIX_LEN+BOOL_LEN && ret.value[PREFIX_LEN] == 0 - && ret.value[0..TYPEID_LEN] == BOOLEAN && ret.value[TYPEID_LEN..PREFIX_LEN] == [0,0,0,1])) + && ret.value[0..TYPEID_LEN] == SE.BOOLEAN && ret.value[TYPEID_LEN..PREFIX_LEN] == [0,0,0,1])) //= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#binary //= type=implication @@ -285,7 +248,7 @@ module DynamoToStruct { ensures a.B? && !prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret.Success? && ret.value == a.B ensures a.B? && prefix && ret.Success? && depth <= MAX_STRUCTURE_DEPTH ==> && ret.value[PREFIX_LEN..] == a.B - && ret.value[0..TYPEID_LEN] == BINARY + && ret.value[0..TYPEID_LEN] == SE.BINARY && U32ToBigEndian(|a.B|).Success? && ret.value[TYPEID_LEN..PREFIX_LEN] == U32ToBigEndian(|a.B|).value && BigEndianToU32(ret.value[TYPEID_LEN..PREFIX_LEN]).value == |a.B| @@ -294,7 +257,7 @@ module DynamoToStruct { //= type=implication //# Null MUST be serialized as a zero-length byte string. ensures a.NULL? && !prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret.Success? && |ret.value| == 0 - ensures a.NULL? && prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret.Success? && |ret.value| == PREFIX_LEN && ret.value[0..TYPEID_LEN] == NULL && ret.value[TYPEID_LEN..PREFIX_LEN] == [0,0,0,0] + ensures a.NULL? && prefix && depth <= MAX_STRUCTURE_DEPTH ==> ret.Success? && |ret.value| == PREFIX_LEN && ret.value[0..TYPEID_LEN] == SE.NULL && ret.value[TYPEID_LEN..PREFIX_LEN] == [0,0,0,0] //= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#string //= type=implication @@ -304,7 +267,7 @@ module DynamoToStruct { ensures a.S? && ret.Success? && prefix ==> && UTF8.Decode(ret.value[PREFIX_LEN..]).Success? && UTF8.Decode(ret.value[PREFIX_LEN..]).value == a.S - && ret.value[0..TYPEID_LEN] == STRING + && ret.value[0..TYPEID_LEN] == SE.STRING && UTF8.Encode(a.S).Success? && U32ToBigEndian(|UTF8.Encode(a.S).value|).Success? && ret.value[TYPEID_LEN..PREFIX_LEN] == U32ToBigEndian(|UTF8.Encode(a.S).value|).value @@ -325,7 +288,7 @@ module DynamoToStruct { && var nn := Norm.NormalizeNumber(a.N).value; && UTF8.Decode(ret.value[PREFIX_LEN..]).Success? && UTF8.Decode(ret.value[PREFIX_LEN..]).value == nn - && ret.value[0..TYPEID_LEN] == NUMBER + && ret.value[0..TYPEID_LEN] == SE.NUMBER && UTF8.Encode(nn).Success? && U32ToBigEndian(|UTF8.Encode(nn).value|).Success? && ret.value[TYPEID_LEN..PREFIX_LEN] == U32ToBigEndian(|UTF8.Encode(nn).value|).value @@ -363,7 +326,7 @@ module DynamoToStruct { ensures a.BS? && ret.Success? && prefix ==> && U32ToBigEndian(|a.BS|).Success? && |ret.value| >= PREFIX_LEN + LENGTH_LEN - && ret.value[0..TYPEID_LEN] == BINARY_SET + && ret.value[0..TYPEID_LEN] == SE.BINARY_SET && ret.value[PREFIX_LEN..PREFIX_LEN+LENGTH_LEN] == U32ToBigEndian(|a.BS|).value && (|a.BS| == 0 ==> |ret.value| == PREFIX_LEN + LENGTH_LEN) @@ -381,7 +344,7 @@ module DynamoToStruct { ensures a.SS? && ret.Success? && prefix ==> && U32ToBigEndian(|a.SS|).Success? && |ret.value| >= PREFIX_LEN + LENGTH_LEN - && ret.value[0..TYPEID_LEN] == STRING_SET + && ret.value[0..TYPEID_LEN] == SE.STRING_SET && ret.value[PREFIX_LEN..PREFIX_LEN+LENGTH_LEN] == U32ToBigEndian(|a.SS|).value //= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#set-entries @@ -397,7 +360,7 @@ module DynamoToStruct { ensures a.NS? && ret.Success? && prefix ==> && U32ToBigEndian(|a.NS|).Success? && |ret.value| >= PREFIX_LEN + LENGTH_LEN - && ret.value[0..TYPEID_LEN] == NUMBER_SET + && ret.value[0..TYPEID_LEN] == SE.NUMBER_SET && ret.value[PREFIX_LEN..PREFIX_LEN+LENGTH_LEN] == U32ToBigEndian(|a.NS|).value //= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#list @@ -427,7 +390,7 @@ module DynamoToStruct { ensures a.L? && ret.Success? && prefix ==> && U32ToBigEndian(|a.L|).Success? && |ret.value| >= PREFIX_LEN + LENGTH_LEN - && ret.value[0..TYPEID_LEN] == LIST + && ret.value[0..TYPEID_LEN] == SE.LIST && ret.value[PREFIX_LEN..PREFIX_LEN+LENGTH_LEN] == U32ToBigEndian(|a.L|).value && (|a.L| == 0 ==> |ret.value| == PREFIX_LEN + LENGTH_LEN) @@ -720,7 +683,7 @@ module DynamoToStruct { //# Map Key MUST be a [String Value](#string). ensures ret.Success? ==> && |ret.value| >= TYPEID_LEN - && ret.value[0..TYPEID_LEN] == STRING + && ret.value[0..TYPEID_LEN] == SE.STRING && UTF8.Encode(key).Success? && |ret.value| == TYPEID_LEN + LENGTH_LEN + |UTF8.Encode(key).value| + |value| && UTF8.Decode(ret.value[TYPEID_LEN+LENGTH_LEN..TYPEID_LEN+LENGTH_LEN+|UTF8.Encode(key).value|]).Success? @@ -751,7 +714,7 @@ module DynamoToStruct { //# | Value Length | 4 | //# | Map Value | Variable | - var serialized := STRING + len + name + value; + var serialized := SE.STRING + len + name + value; assert |serialized| == TYPEID_LEN + LENGTH_LEN + |name| + |value|; Success(serialized) } @@ -958,7 +921,7 @@ module DynamoToStruct { // get typeId of key :- Need(6 <= |serialized|, "Out of bytes reading Map Key"); var TerminalTypeId_key := serialized[0..2]; - :- Need(TerminalTypeId_key == STRING, "Key of Map is not String"); + :- Need(TerminalTypeId_key == SE.STRING, "Key of Map is not String"); var serialized := serialized[2..]; // get key @@ -1019,7 +982,7 @@ module DynamoToStruct { if |value| < len then Failure("Structured Data has too few bytes") - else if typeId == NULL then + else if typeId == SE.NULL then if len != 0 then Failure("NULL type did not have length zero") else @@ -1029,18 +992,18 @@ module DynamoToStruct { // and so round-trip identity is NOT preserved Success(AttrValueAndLength(AttributeValue.NULL(true), lengthBytes)) - else if typeId == STRING then + else if typeId == SE.STRING then var str :- UTF8.Decode(value[..len]); Success(AttrValueAndLength(AttributeValue.S(str), len+lengthBytes)) - else if typeId == NUMBER then + else if typeId == SE.NUMBER then var str :- UTF8.Decode(value[..len]); Success(AttrValueAndLength(AttributeValue.N(str), len+lengthBytes)) - else if typeId == BINARY then + else if typeId == SE.BINARY then Success(AttrValueAndLength(AttributeValue.B(value[..len]), len+lengthBytes)) - else if typeId == BOOLEAN then + else if typeId == SE.BOOLEAN then if len != BOOL_LEN then Failure("Boolean Structured Data has more than one byte") else if value[0] == 0x00 then @@ -1050,7 +1013,7 @@ module DynamoToStruct { else Failure("Boolean Structured Data had inappropriate value") - else if typeId == STRING_SET then + else if typeId == SE.STRING_SET then if |value| < LENGTH_LEN then Failure("String Set Structured Data has less than LENGTH_LEN bytes") else @@ -1058,7 +1021,7 @@ module DynamoToStruct { var value := value[LENGTH_LEN..]; DeserializeStringSet(value, len, |value| + LENGTH_LEN + lengthBytes, AttrValueAndLength(AttributeValue.SS([]), LENGTH_LEN+lengthBytes)) - else if typeId == NUMBER_SET then + else if typeId == SE.NUMBER_SET then if |value| < LENGTH_LEN then Failure("Number Set Structured Data has less than 4 bytes") else @@ -1066,7 +1029,7 @@ module DynamoToStruct { var value := value[LENGTH_LEN..]; DeserializeNumberSet(value, len, |value| + LENGTH_LEN + lengthBytes, AttrValueAndLength(AttributeValue.NS([]), LENGTH_LEN + lengthBytes)) - else if typeId == BINARY_SET then + else if typeId == SE.BINARY_SET then if |value| < LENGTH_LEN then Failure("Binary Set Structured Data has less than LENGTH_LEN bytes") else @@ -1074,7 +1037,7 @@ module DynamoToStruct { var value := value[LENGTH_LEN..]; DeserializeBinarySet(value, len, |value| + LENGTH_LEN + lengthBytes, AttrValueAndLength(AttributeValue.BS([]), LENGTH_LEN + lengthBytes)) - else if typeId == MAP then + else if typeId == SE.MAP then if |value| < LENGTH_LEN then Failure("List Structured Data has less than 4 bytes") else @@ -1082,7 +1045,7 @@ module DynamoToStruct { var value := value[LENGTH_LEN..]; DeserializeMap(value, len, |value| + LENGTH_LEN + lengthBytes, depth, AttrValueAndLength(AttributeValue.M(map[]), LENGTH_LEN + lengthBytes)) - else if typeId == LIST then + else if typeId == SE.LIST then if |value| < LENGTH_LEN then Failure("List Structured Data has less than 4 bytes") else @@ -1103,7 +1066,7 @@ module DynamoToStruct { set k <- m | m[k].Failure? :: m[k].error } - lemma OneBadResult(m : map>) + lemma OneBadResult(m : map>) requires ! forall k <- m :: m[k].Success? ensures exists k <- m :: m[k].Failure? ensures |FlattenErrors(m)| > 0 @@ -1113,20 +1076,20 @@ module DynamoToStruct { assert exists k :: k in m && m[k].Failure? && (m[k].error in errors); } - lemma MapKeysMatchItems(m : map) + lemma MapKeysMatchItems(m : map) ensures forall k :: k in m.Keys ==> (k, m[k]) in m.Items {} lemma OneBadKey(s : map, bad : set, f : X -> bool) requires !forall k <- s.Keys :: f(k) - requires bad == set k <- s.Keys | !f(k) :: k; + requires bad == set k <- s.Keys | !f(k) :: k ensures exists k <- s.Keys :: !f(k) ensures |bad| > 0 { assert exists v :: v in bad && !f(v) && (v in bad); } - lemma SimplifyMapValueSuccess(m : map>) + lemma SimplifyMapValueSuccess(m : map>) ensures SimplifyMapValue(m).Success? <==> forall k <- m :: m[k].Success? ensures SimplifyMapValue(m).Success? ==> forall kv <- m.Items :: kv.1.Success? ensures SimplifyMapValue(m).Failure? <==> exists k : X | k in m.Keys :: m[k].Failure? diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Index.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Index.dfy index 2703b9023..814be59c6 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Index.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Index.dfy @@ -28,7 +28,8 @@ module } method DynamoDbEncryption(config: DynamoDbEncryptionConfig) - returns (res: Result) + returns (res: Result) + ensures res.Success? ==> res.value is DynamoDbEncryptionClient { var internalConfig := Operations.Config(); var client := new DynamoDbEncryptionClient(internalConfig); diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Util.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Util.dfy index 6534cb393..f839c551d 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Util.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Util.dfy @@ -46,9 +46,6 @@ module DynamoDbEncryptionUtil { DontUseKeyId } - const DDBEC_ATTR_PREFIX := "aws-crypto-attr." - const DDBEC_EC_ATTR_PREFIX: UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(DDBEC_ATTR_PREFIX) - // string to Error function method E(s : string) : Error { DynamoDbEncryptionException(message := s) diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy index fe463a91d..a8f34146d 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy @@ -353,7 +353,6 @@ module TestBaseBeacon { var src := GetLiteralSource([1,2,3,4,5], version); var bv := C.ConvertVersionWithSource(newConfig, version, src); expect bv.Failure?; - print "\n", bv.error, "\n"; expect bv.error == E("Beacon shareBeacon is shared to itself."); } diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoToStruct.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoToStruct.dfy index ffecf051e..40687b911 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoToStruct.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoToStruct.dfy @@ -12,6 +12,7 @@ module DynamoToStructTest { import opened AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes import opened StandardLibrary.UInt import opened DynamoDbEncryptionUtil + import SE = StructuredEncryptionUtil method DoFail(data : seq, typeId : TerminalTypeId) { @@ -35,41 +36,41 @@ module DynamoToStructTest { } method {:test} TestZeroBytes() { - DoSucceed([], DynamoToStruct.NULL, 1); - DoSucceed([], STRING, 2); - DoSucceed([], NUMBER, 3); - DoSucceed([], BINARY, 4); - DoFail([], BOOLEAN); - DoFail([], STRING_SET); - DoFail([], NUMBER_SET); - DoFail([], BINARY_SET); - DoFail([], MAP); - DoFail([], LIST); + DoSucceed([], SE.NULL, 1); + DoSucceed([], SE.STRING, 2); + DoSucceed([], SE.NUMBER, 3); + DoSucceed([], SE.BINARY, 4); + DoFail([], SE.BOOLEAN); + DoFail([], SE.STRING_SET); + DoFail([], SE.NUMBER_SET); + DoFail([], SE.BINARY_SET); + DoFail([], SE.MAP); + DoFail([], SE.LIST); } - const k := 'k' as uint8; - const e := 'e' as uint8; - const y := 'y' as uint8; - const A := 'A' as uint8; - const B := 'B' as uint8; - const C := 'C' as uint8; - const D := 'D' as uint8; + const k := 'k' as uint8 + const e := 'e' as uint8 + const y := 'y' as uint8 + const A := 'A' as uint8 + const B := 'B' as uint8 + const C := 'C' as uint8 + const D := 'D' as uint8 method {:test} TestBadType() { - DoSucceed([0,0,0,1, 0,0, 0,0,0,0], LIST, 5); - DoFail ([0,0,0,1, 3,1, 0,0,0,0], LIST); + DoSucceed([0,0,0,1, 0,0, 0,0,0,0], SE.LIST, 5); + DoFail ([0,0,0,1, 3,1, 0,0,0,0], SE.LIST); } method {:test} TestBadLengthList() { - DoFail ([0,0,0,1, 0,3, 0,0,0,2, 1], LIST); - DoSucceed([0,0,0,1, 0xff,0xff, 0,0,0,2, 1,2], LIST, 6); - DoFail ([0,0,0,1, 0,3, 0,0,0,2, 1,2,3], LIST); + DoFail ([0,0,0,1, 0,3, 0,0,0,2, 1], SE.LIST); + DoSucceed([0,0,0,1, 0xff,0xff, 0,0,0,2, 1,2], SE.LIST, 6); + DoFail ([0,0,0,1, 0,3, 0,0,0,2, 1,2,3], SE.LIST); } method {:test} TestBadLengthMap() { - DoFail([0,0,0,1, 0,1, 0,0,0,4, k,e,y,A, 0,3, 0,0,0,5, 1,2,3,4], MAP); - DoSucceed([0,0,0,1, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5], MAP, 7); - DoFail([0,0,0,1, 0,1, 0,0,0,4, k,e,y,A, 0,3, 0,0,0,5, 1,2,3,4,5,6], MAP); + DoFail([0,0,0,1, 0,1, 0,0,0,4, k,e,y,A, 0,3, 0,0,0,5, 1,2,3,4], SE.MAP); + DoSucceed([0,0,0,1, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5], SE.MAP, 7); + DoFail([0,0,0,1, 0,1, 0,0,0,4, k,e,y,A, 0,3, 0,0,0,5, 1,2,3,4,5,6], SE.MAP); } method {:test} TestBadDupKeys() { @@ -80,17 +81,17 @@ module DynamoToStructTest { //= specification/dynamodb-encryption-client/ddb-item-conversion.md#duplicates //= type=test //# - Conversion from a Structured Data Map MUST fail if it has duplicate keys - DoSucceed([0,0,0,2, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5, 0,1, 0,0,0,4, k,e,y,B, 0xff,0xff, 0,0,0,5, 1,2,3,4,5], MAP, 8); - DoFail ([0,0,0,2, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5], MAP); + DoSucceed([0,0,0,2, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5, 0,1, 0,0,0,4, k,e,y,B, 0xff,0xff, 0,0,0,5, 1,2,3,4,5], SE.MAP, 8); + DoFail ([0,0,0,2, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5, 0,1, 0,0,0,4, k,e,y,A, 0xff,0xff, 0,0,0,5, 1,2,3,4,5], SE.MAP); - DoSucceed([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 52,53,54], BINARY_SET, 9); - DoFail ([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 49,50,51], BINARY_SET); + DoSucceed([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 52,53,54], SE.BINARY_SET, 9); + DoFail ([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 49,50,51], SE.BINARY_SET); - DoSucceed([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 52,53,54], NUMBER_SET, 10); - DoFail ([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 49,50,51], NUMBER_SET); + DoSucceed([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 52,53,54], SE.NUMBER_SET, 10); + DoFail ([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 49,50,51], SE.NUMBER_SET); - DoSucceed([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 52,53,54], STRING_SET, 11); - DoFail ([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 49,50,51], STRING_SET); + DoSucceed([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 52,53,54], SE.STRING_SET, 11); + DoFail ([0,0,0,2, 0,0,0,3, 49,50,51, 0,0,0,3, 49,50,51], SE.STRING_SET); } // Split because verification timed out diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy index 8d9f2b539..87da7669b 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/Model/AwsCryptographyDbEncryptionSdkDynamoDbTransformsTypes.dfy @@ -633,7 +633,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype ResolveAttributesInput = | ResolveAttributesInput ( nameonly TableName: ComAmazonawsDynamodbTypes.TableName , nameonly Item: ComAmazonawsDynamodbTypes.AttributeMap , - nameonly Version: Option + nameonly Version: Option := Option.None ) datatype ResolveAttributesOutput = | ResolveAttributesOutput ( nameonly VirtualFields: StringMap , @@ -740,7 +740,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" import Operations : AbstractAwsCryptographyDbEncryptionSdkDynamoDbTransformsOperations function method DefaultDynamoDbTablesEncryptionConfig(): AwsCryptographyDbEncryptionSdkDynamoDbTypes.DynamoDbTablesEncryptionConfig method DynamoDbEncryptionTransforms(config: AwsCryptographyDbEncryptionSdkDynamoDbTypes.DynamoDbTablesEncryptionConfig := DefaultDynamoDbTablesEncryptionConfig()) - returns (res: Result) + returns (res: Result) // BEGIN MANUAL EDIT requires var tmps0 := set t0 | t0 in config.tableEncryptionConfigs.Values; forall tmp0 :: tmp0 in tmps0 ==> @@ -858,6 +858,13 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" tmp27.keyStore.ValidState() // END MANUAL EDIT + // Helper function for the benefit of native code to create a Success(client) without referring to Dafny internals + function method CreateSuccessOfClient(client: IDynamoDbEncryptionTransformsClient): Result { + Success(client) + } // Helper function for the benefit of native code to create a Failure(error) without referring to Dafny internals + function method CreateFailureOfError(error: Error): Result { + Failure(error) + } class DynamoDbEncryptionTransformsClient extends IDynamoDbEncryptionTransformsClient { constructor(config: Operations.InternalConfig) diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy index 62a76dd86..8684982c0 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy @@ -11,7 +11,7 @@ module { import opened DdbMiddlewareConfig import opened StandardLibrary - import AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes + import IE_Types = AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes import Operations = AwsCryptographyDbEncryptionSdkDynamoDbTransformsOperations import DynamoDbItemEncryptor import SearchConfigToInfo @@ -102,13 +102,15 @@ module } method {:vcs_split_on_every_assert} DynamoDbEncryptionTransforms(config: AwsCryptographyDbEncryptionSdkDynamoDbTypes.DynamoDbTablesEncryptionConfig) - returns (res: Result) + returns (res: Result) //= specification/dynamodb-encryption-client/ddb-table-encryption-config.md#logical-table-name //= type=implication //# When mapping [DynamoDB Table Names](#dynamodb-table-name) to [logical table name](#logical-table-name) //# there MUST a one to one mapping between the two. ensures res.Success? ==> - && DdbMiddlewareConfig.ValidConfig?(res.value.config) + && res.value is DynamoDbEncryptionTransformsClient + && var config := (res.value as DynamoDbEncryptionTransformsClient).config; + && DdbMiddlewareConfig.ValidConfig?(config) { var internalConfigs: map := map[]; assert ValidWholeSearchConfig(config); @@ -167,7 +169,7 @@ module //# `NAME` MUST be automatically configured with an attribute action of SIGN_ONLY. var newActions := AddSignedBeaconActions(signedBeacons, inputConfig.attributeActionsOnEncrypt); - var encryptorConfig := AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.DynamoDbItemEncryptorConfig( + var encryptorConfig := IE_Types.DynamoDbItemEncryptorConfig( logicalTableName := inputConfig.logicalTableName, partitionKeyName := inputConfig.partitionKeyName, sortKeyName := inputConfig.sortKeyName, @@ -181,8 +183,15 @@ module plaintextOverride := inputConfig.plaintextOverride ); var itemEncryptorRes := DynamoDbItemEncryptor.DynamoDbItemEncryptor(encryptorConfig); - var itemEncryptor :- itemEncryptorRes + var itemEncryptorX : IE_Types.IDynamoDbItemEncryptorClient :- itemEncryptorRes .MapFailure(e => AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptor(e)); + assert itemEncryptorX is DynamoDbItemEncryptor.DynamoDbItemEncryptorClient; + var itemEncryptor := itemEncryptorX as DynamoDbItemEncryptor.DynamoDbItemEncryptorClient; + assert itemEncryptor.ValidState(); + var encConfig := itemEncryptor.config; + assert inputConfig.logicalTableName == encConfig.logicalTableName; + assert inputConfig.partitionKeyName == encConfig.partitionKeyName; + assert inputConfig.sortKeyName == encConfig.sortKeyName; var internalConfig: DdbMiddlewareConfig.ValidTableConfig := DdbMiddlewareConfig.TableConfig( physicalTableName := tableName, @@ -192,7 +201,7 @@ module itemEncryptor := itemEncryptor, search := search ); - + internalConfigs := internalConfigs[tableName := internalConfig]; allLogicalTableNames := allLogicalTableNames + {internalConfig.logicalTableName}; diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/test/TestFixtures.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/test/TestFixtures.dfy index 1ef80c06e..f8a13e598 100644 --- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/test/TestFixtures.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/test/TestFixtures.dfy @@ -73,31 +73,34 @@ module TestFixtures { return s; } - const PUBLIC_US_WEST_2_KMS_TEST_KEY := "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f"; + const PUBLIC_US_WEST_2_KMS_TEST_KEY := "arn:aws:kms:us-west-2:658956600833:key/b3537ef1-d8dc-4780-9f5a-55776cbb2f7f" function method GetAttributeActions() : AttributeActions { map["bar" := CSE.SIGN_ONLY, "encrypt" := CSE.ENCRYPT_AND_SIGN, "sign" := CSE.SIGN_ONLY, "nothing" := CSE.DO_NOTHING] } + function method GetV2AttributeActions() : AttributeActions { + map["bar" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, "encrypt" := CSE.ENCRYPT_AND_SIGN, "sign" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, "nothing" := CSE.DO_NOTHING] + } function method GetSignedAttributeActions() : AttributeActions { map["bar" := CSE.SIGN_ONLY, "encrypt" := CSE.ENCRYPT_AND_SIGN, "sign" := CSE.SIGN_ONLY] } - method GetEncryptorConfigFromActions(actions : AttributeActions) returns (output : DynamoDbItemEncryptorConfig) { + method GetEncryptorConfigFromActions(actions : AttributeActions, sortKeyName : Option := None) returns (output : DynamoDbItemEncryptorConfig) { var keyring := GetKmsKeyring(); var logicalTableName := GetTableName("foo"); output := DynamoDbItemEncryptorConfig( logicalTableName := logicalTableName, partitionKeyName := "bar", - sortKeyName := None(), + sortKeyName := sortKeyName, attributeActionsOnEncrypt := actions, allowedUnsignedAttributes := Some(["nothing"]), - allowedUnsignedAttributePrefix := None(), + allowedUnsignedAttributePrefix := None, keyring := Some(keyring), - cmm := None(), - algorithmSuiteId := None(), - plaintextOverride := None(), - legacyOverride := None() + cmm := None, + algorithmSuiteId := None, + plaintextOverride := None, + legacyOverride := None ); } @@ -125,7 +128,9 @@ module TestFixtures { legacyOverride := None(), plaintextOverride := None() ); - encryptor :- expect DynamoDbItemEncryptor.DynamoDbItemEncryptor(encryptorConfig); + var encryptor2 : IDynamoDbItemEncryptorClient :- expect DynamoDbItemEncryptor.DynamoDbItemEncryptor(encryptorConfig); + assert encryptor2 is DynamoDbItemEncryptor.DynamoDbItemEncryptorClient; + encryptor := encryptor2 as DynamoDbItemEncryptor.DynamoDbItemEncryptorClient; } method GetDynamoDbItemEncryptor() @@ -211,7 +216,7 @@ module TestFixtures { ensures fresh(encryption.Modifies) { var keyring := GetKmsKeyring(); - encryption :- expect DynamoDbEncryptionTransforms.DynamoDbEncryptionTransforms( + var encryption2 : IDynamoDbEncryptionTransformsClient :- expect DynamoDbEncryptionTransforms.DynamoDbEncryptionTransforms( DynamoDbTablesEncryptionConfig( tableEncryptionConfigs := map[ "foo" := DynamoDbTableEncryptionConfig( @@ -236,6 +241,8 @@ module TestFixtures { ] ) ); + assert encryption2 is DynamoDbEncryptionTransforms.DynamoDbEncryptionTransformsClient; + encryption := encryption2 as DynamoDbEncryptionTransforms.DynamoDbEncryptionTransformsClient; assume {:axiom} fresh(encryption.Modifies); } } diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy index 22e6c6737..f51ac0792 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.dfy @@ -27,7 +27,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" ) datatype DecryptItemOutput = | DecryptItemOutput ( nameonly plaintextItem: ComAmazonawsDynamodbTypes.AttributeMap , - nameonly parsedHeader: Option + nameonly parsedHeader: Option := Option.None ) class IDynamoDbItemEncryptorClientCallHistory { ghost constructor() { @@ -98,28 +98,30 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" datatype DynamoDbItemEncryptorConfig = | DynamoDbItemEncryptorConfig ( nameonly logicalTableName: string , nameonly partitionKeyName: ComAmazonawsDynamodbTypes.KeySchemaAttributeName , - nameonly sortKeyName: Option , + nameonly sortKeyName: Option := Option.None , nameonly attributeActionsOnEncrypt: AwsCryptographyDbEncryptionSdkDynamoDbTypes.AttributeActions , - nameonly allowedUnsignedAttributes: Option , - nameonly allowedUnsignedAttributePrefix: Option , - nameonly algorithmSuiteId: Option , - nameonly keyring: Option , - nameonly cmm: Option , - nameonly legacyOverride: Option , - nameonly plaintextOverride: Option + nameonly allowedUnsignedAttributes: Option := Option.None , + nameonly allowedUnsignedAttributePrefix: Option := Option.None , + nameonly algorithmSuiteId: Option := Option.None , + nameonly keyring: Option := Option.None , + nameonly cmm: Option := Option.None , + nameonly legacyOverride: Option := Option.None , + nameonly plaintextOverride: Option := Option.None ) datatype EncryptItemInput = | EncryptItemInput ( nameonly plaintextItem: ComAmazonawsDynamodbTypes.AttributeMap ) datatype EncryptItemOutput = | EncryptItemOutput ( nameonly encryptedItem: ComAmazonawsDynamodbTypes.AttributeMap , - nameonly parsedHeader: Option + nameonly parsedHeader: Option := Option.None ) datatype ParsedHeader = | ParsedHeader ( nameonly attributeActionsOnEncrypt: AwsCryptographyDbEncryptionSdkDynamoDbTypes.AttributeActions , nameonly algorithmSuiteId: AwsCryptographyMaterialProvidersTypes.DBEAlgorithmSuiteId , nameonly encryptedDataKeys: AwsCryptographyMaterialProvidersTypes.EncryptedDataKeyList , - nameonly storedEncryptionContext: AwsCryptographyMaterialProvidersTypes.EncryptionContext + nameonly storedEncryptionContext: AwsCryptographyMaterialProvidersTypes.EncryptionContext , + nameonly encryptionContext: AwsCryptographyMaterialProvidersTypes.EncryptionContext , + nameonly selectorContext: ComAmazonawsDynamodbTypes.Key ) datatype Error = // Local Error structures are listed here @@ -169,7 +171,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" import Operations : AbstractAwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations function method DefaultDynamoDbItemEncryptorConfig(): DynamoDbItemEncryptorConfig method DynamoDbItemEncryptor(config: DynamoDbItemEncryptorConfig := DefaultDynamoDbItemEncryptorConfig()) - returns (res: Result) + returns (res: Result) requires config.keyring.Some? ==> config.keyring.value.ValidState() requires config.cmm.Some? ==> @@ -207,6 +209,13 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" ensures config.legacyOverride.Some? ==> config.legacyOverride.value.encryptor.ValidState() + // Helper function for the benefit of native code to create a Success(client) without referring to Dafny internals + function method CreateSuccessOfClient(client: IDynamoDbItemEncryptorClient): Result { + Success(client) + } // Helper function for the benefit of native code to create a Failure(error) without referring to Dafny internals + function method CreateFailureOfError(error: Error): Result { + Failure(error) + } class DynamoDbItemEncryptorClient extends IDynamoDbItemEncryptorClient { constructor(config: Operations.InternalConfig) diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/DynamoDbItemEncryptor.smithy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/DynamoDbItemEncryptor.smithy index eea12cbdb..9d3ad647e 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/DynamoDbItemEncryptor.smithy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/Model/DynamoDbItemEncryptor.smithy @@ -10,18 +10,21 @@ use com.amazonaws.dynamodb#AttributeMap use com.amazonaws.dynamodb#AttributeNameList use com.amazonaws.dynamodb#TableName use com.amazonaws.dynamodb#KeySchemaAttributeName +use com.amazonaws.dynamodb#Key + +use aws.cryptography.primitives#AwsCryptographicPrimitives + use aws.cryptography.materialProviders#KeyringReference use aws.cryptography.materialProviders#CryptographicMaterialsManagerReference use aws.cryptography.materialProviders#DBEAlgorithmSuiteId use aws.cryptography.materialProviders#EncryptedDataKeyList use aws.cryptography.materialProviders#EncryptionContext +use aws.cryptography.materialProviders#AwsCryptographicMaterialProviders + use aws.cryptography.dbEncryptionSdk.dynamoDb#AttributeActions use aws.cryptography.dbEncryptionSdk.dynamoDb#LegacyOverride use aws.cryptography.dbEncryptionSdk.structuredEncryption#Version use aws.cryptography.dbEncryptionSdk.dynamoDb#PlaintextOverride - -use aws.cryptography.materialProviders#AwsCryptographicMaterialProviders -use aws.cryptography.primitives#AwsCryptographicPrimitives use aws.cryptography.dbEncryptionSdk.dynamoDb#DynamoDbEncryption use aws.cryptography.dbEncryptionSdk.structuredEncryption#StructuredEncryption @@ -123,6 +126,9 @@ structure DynamoDbItemEncryptorConfig { //# calculated using the Crypto Legend in the header, the signature scope used for decryption, and the data in the structure, //# converted into Attribute Actions. //# - [Encrypted Data Keys](./header.md#encrypted-data-keys): The Encrypted Data Keys stored in the header. +//# - [Stored Encryption Context](../structured-encryption/header.md#encryption-context): The Encryption Context stored in the header. +//# - [Encryption Context](../structured-encryption/decrypt-structure#encryption-context): The full Encryption Context used. +//# - Selector Context : the AttributeMap as passed to the [Branch Key Supplier](./ddb-encryption-branch-key-id-supplier.md) @javadoc("A parsed version of the header that was written with or read on an encrypted DynamoDB item.") structure ParsedHeader { @required @@ -136,7 +142,13 @@ structure ParsedHeader { encryptedDataKeys: EncryptedDataKeyList, @required @javadoc("The portion of the encryption context that was stored in the header of this item.") - storedEncryptionContext: EncryptionContext + storedEncryptionContext: EncryptionContext, + @required + @javadoc("The full encryption context.") + encryptionContext: EncryptionContext, + @required + @javadoc("The encryption context as presented to the branch key selector.") + selectorContext: Key } //= specification/dynamodb-encryption-client/ddb-item-encryptor.md#encryptitem diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations.dfy index 5ea299388..83494101a 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations.dfy @@ -27,10 +27,11 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs import SET = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes import DDBE = AwsCryptographyDbEncryptionSdkDynamoDbTypes import DynamoDbEncryptionUtil - import StructuredEncryptionUtil import StandardLibrary.String + import StructuredEncryptionHeader datatype Config = Config( + nameonly version : StructuredEncryptionHeader.Version, nameonly cmpClient : MaterialProviders.MaterialProvidersClient, nameonly logicalTableName: string, nameonly partitionKeyName: ComAmazonawsDynamodbTypes.KeySchemaAttributeName, @@ -53,11 +54,6 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs const DoSign := CSE.AuthenticateSchema(content := CSE.AuthenticateSchemaContent.Action(CSE.AuthenticateAction.SIGN), attributes := None) - // constant attribute names for the encryption context - const TABLE_NAME : ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-table-name"); - const PARTITION_NAME : ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-partition-name"); - const SORT_NAME : ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-sort-name"); - // Is the attribute name an allowed unauthenticated name? predicate method AllowedUnsigned( unauthenticatedAttributes: Option, @@ -95,11 +91,22 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs function method CryptoActionString(action: CSE.CryptoAction) : string { - match action { - case DO_NOTHING => "DO_NOTHING" - case SIGN_ONLY => "SIGN_ONLY" - case ENCRYPT_AND_SIGN => "ENCRYPT_AND_SIGN" - } + if action == CSE.DO_NOTHING then + "DO_NOTHING" + else if action == CSE.SIGN_ONLY then + "SIGN_ONLY" + else if action == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT then + "SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT" + else if action == CSE.ENCRYPT_AND_SIGN then + "ENCRYPT_AND_SIGN" + else + assert false by { + assert action != CSE.DO_NOTHING; + assert action != CSE.SIGN_ONLY; + assert action != CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT; + assert action != CSE.ENCRYPT_AND_SIGN; + } + "internal error" } function method ExplainNotForwardCompatible( @@ -123,7 +130,6 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs "it also begins with the reserved prefix." } - // Is this attribute unknown to the config? predicate method UnknownAttribute(config : InternalConfig, attr : ComAmazonawsDynamodbTypes.AttributeName) { @@ -134,12 +140,6 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs // and that's why it's an error } - // Is the attribute SIGN_ONLY? - predicate method IsSignOnly(config : InternalConfig, attr : ComAmazonawsDynamodbTypes.AttributeName) - { - attr in config.attributeActionsOnEncrypt && config.attributeActionsOnEncrypt[attr] == CSE.SIGN_ONLY - } - // Is the attribute name in signature scope? predicate method InSignatureScope(config : InternalConfig, attr : ComAmazonawsDynamodbTypes.AttributeName) { @@ -150,42 +150,47 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs } function method EncodeName(k : string) : (ret : Result) - //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value + //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value-version-1 //= type=implication //# The key MUST be the following concatenation, //# where `attributeName` is the name of the attribute: //# "aws-crypto-attr." + `attributeName`. - ensures ret == DDBEncode(DynamoDbEncryptionUtil.DDBEC_ATTR_PREFIX + k) + ensures ret == DDBEncode(SE.ATTR_PREFIX + k) { - DDBEncode(DynamoDbEncryptionUtil.DDBEC_ATTR_PREFIX + k) + DDBEncode(SE.ATTR_PREFIX + k) } - function method EncodeValue(t : SET.StructuredDataTerminal) : (ret : UTF8.ValidUTF8Bytes) - //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value + function method MakeEncryptionContext( + config : InternalConfig, + item : DynamoToStruct.TerminalDataMap) + : (ret : Result) + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context //= type=implication - //# The value MUST be the UTF8 Encoding of the - //# [Base 64 encoded](https://www.rfc-editor.org/rfc/rfc4648), - //# of the concatenation of the bytes `typeID + serializedValue` - //# where `typeId` is the attribute's [type ID](./ddb-attribute-serialization.md#type-id) - //# and `serializedValue` is the attribute's value serialized according to - //# [Attribute Value Serialization](./ddb-attribute-serialization.md#attribute-value-serialization). - ensures ret == EncodeAscii(Base64.Encode(t.typeId + t.value)) + //# If the [Configuration Version](./ddb-table-encryption-config.md#configuration-version) is 2, + //# then the base context MUST be the [version 2](#dynamodb-item-base-context-version-2) context; + //# otherwise, the base context MUST be the [version 1](#dynamodb-item-base-context-version-1) context. + ensures config.version == 2 ==> ret == MakeEncryptionContextV2(config, item) + ensures config.version == 1 ==> ret == MakeEncryptionContextV1(config, item) + ensures (config.version == 1) || (config.version == 2) { - EncodeAscii(Base64.Encode(t.typeId + t.value)) + if config.version == 2 then + MakeEncryptionContextV2(config, item) + else + MakeEncryptionContextV1(config, item) } - function method {:opaque} {:vcs_split_on_every_assert} MakeEncryptionContext( + function method {:opaque} {:vcs_split_on_every_assert} MakeEncryptionContextV1( config : InternalConfig, item : DynamoToStruct.TerminalDataMap) : (ret : Result) - //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context-version-1 //= type=implication //# The DynamoDB Item Base Context MUST contain: - //# - the key "aws-crypto-table-name" with a value equal to the DynamoDB Table Name of the DynamoDB Table - //# this item is stored in (or will be stored in). + //# - the key "aws-crypto-table-name" with a value equal to the configured + //# [logical table name](./ddb-table-encryption-config.md#logical-table-name). //# - the key "aws-crypto-partition-name" with a value equal to the name of the Partition Key on this item. - //# - the [value](#base-context-value) of the Partition Key. + //# - the [value](#base-context-value-version-1) of the Partition Key. ensures ret.Success? ==> && config.partitionKeyName in item && TABLE_NAME in ret.value @@ -200,15 +205,15 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs && EncodeName(config.partitionKeyName).Success? && var partitionKeyName : ValidUTF8Bytes := EncodeName(config.partitionKeyName).value; - && var partitionKeyValue : ValidUTF8Bytes := EncodeValue(item[config.partitionKeyName].content.Terminal); + && var partitionKeyValue : ValidUTF8Bytes := SE.EncodeTerminal(item[config.partitionKeyName].content.Terminal); && partitionKeyName in ret.value && ret.value[partitionKeyName] == partitionKeyValue - //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context-version-1 //= type=implication //# If this item has a Sort Key attribute, the DynamoDB Item Base Context MUST contain: //# - the key "aws-crypto-sort-name" with a value equal to the [DynamoDB Sort Key Name](#dynamodb-sort-key-name). - //# - the [value](#base-context-value) of the Sort Key. + //# - the [value](#base-context-value-version-1) of the Sort Key. ensures ret.Success? && config.sortKeyName.Some? ==> && config.sortKeyName.value in item && SORT_NAME in ret.value @@ -219,10 +224,10 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs && EncodeName(config.sortKeyName.value).Success? && var sortKeyName : ValidUTF8Bytes := EncodeName(config.sortKeyName.value).value; && sortKeyName in ret.value - && var sortKeyValue : ValidUTF8Bytes := EncodeValue(item[config.sortKeyName.value].content.Terminal); + && var sortKeyValue : ValidUTF8Bytes := SE.EncodeTerminal(item[config.sortKeyName.value].content.Terminal); && ret.value[sortKeyName] == sortKeyValue - //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context-version-1 //= type=implication //# If this item does not have a sort key attribute, //# the DynamoDB Item Context MUST NOT contain the key `aws-crypto-sort-name`. @@ -234,7 +239,7 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs var logicalTableName : ValidUTF8Bytes :- DDBEncode(config.logicalTableName); var partitionName : ValidUTF8Bytes :- DDBEncode(config.partitionKeyName); var partitionKeyName : ValidUTF8Bytes :- EncodeName(config.partitionKeyName); - var partitionKeyValue : ValidUTF8Bytes := EncodeValue(item[config.partitionKeyName].content.Terminal); + var partitionKeyValue : ValidUTF8Bytes := SE.EncodeTerminal(item[config.partitionKeyName].content.Terminal); if (config.sortKeyName.None?) then :- Need(|{TABLE_NAME, PARTITION_NAME, SORT_NAME, partitionKeyName}| == 4, E("Internal Error")); var ec : CMP.EncryptionContext := @@ -255,7 +260,7 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs :- Need(config.sortKeyName.value in item, DDBError("Sort key " + config.sortKeyName.value + " not found in Item to be encrypted or decrypted")); var sortName :- DDBEncode(config.sortKeyName.value); var sortKeyName : ValidUTF8Bytes :- EncodeName(config.sortKeyName.value); - var sortKeyValue : ValidUTF8Bytes := EncodeValue(item[config.sortKeyName.value].content.Terminal); + var sortKeyValue : ValidUTF8Bytes := SE.EncodeTerminal(item[config.sortKeyName.value].content.Terminal); :- Need(|{TABLE_NAME, PARTITION_NAME, partitionKeyName, SORT_NAME, sortKeyName}| == 5, E("Internal Error")); var ec : CMP.EncryptionContext := map[ @@ -278,6 +283,85 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs Success(ec) } + function method {:opaque} {:vcs_split_on_every_assert} MakeEncryptionContextV2( + config : InternalConfig, + item : DynamoToStruct.TerminalDataMap) + : (ret : Result) + + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context-version-2 + //= type=implication + //# The DynamoDB Item Base Context MUST contain: + //# - the key "aws-crypto-table-name" with a value equal to the DynamoDB Table Name of the DynamoDB Table + //# this item is stored in (or will be stored in). + //# - the key "aws-crypto-partition-name" with a value equal to the name of the Partition Key on this item. + ensures ret.Success? ==> + && config.partitionKeyName in item + && TABLE_NAME in ret.value + && DDBEncode(config.logicalTableName).Success? + && var logicalTableName : ValidUTF8Bytes := DDBEncode(config.logicalTableName).value; + && ret.value[TABLE_NAME] == logicalTableName + + && PARTITION_NAME in ret.value + && DDBEncode(config.partitionKeyName).Success? + && var partitionName : ValidUTF8Bytes := DDBEncode(config.partitionKeyName).value; + && ret.value[PARTITION_NAME] == partitionName + + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context-version-2 + //= type=implication + //# If this item has a Sort Key attribute, the DynamoDB Item Base Context MUST contain: + //# - the key "aws-crypto-sort-name" with a value equal to the [DynamoDB Sort Key Name](#dynamodb-sort-key-name). + ensures ret.Success? && config.sortKeyName.Some? ==> + && config.sortKeyName.value in item + && SORT_NAME in ret.value + && DDBEncode(config.sortKeyName.value).Success? + && var sortName := DDBEncode(config.sortKeyName.value).value; + && ret.value[SORT_NAME] == sortName + + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item-base-context-version-2 + //= type=implication + //# If this item does not have a sort key attribute, + //# the DynamoDB Item Context MUST NOT contain the key `aws-crypto-sort-name`. + ensures ret.Success? && config.sortKeyName.None? ==> + SORT_NAME !in ret.value + { + UTF8.EncodeAsciiUnique(); + :- Need(config.partitionKeyName in item, DDBError("Partition key " + config.partitionKeyName + " not found in Item to be encrypted or decrypted")); + var logicalTableName : ValidUTF8Bytes :- DDBEncode(config.logicalTableName); + var partitionName : ValidUTF8Bytes :- DDBEncode(config.partitionKeyName); + var partitionKeyName : ValidUTF8Bytes :- EncodeName(config.partitionKeyName); + if (config.sortKeyName.None?) then + assert |{TABLE_NAME, PARTITION_NAME, SORT_NAME}| == 3; + var ec : CMP.EncryptionContext := + map[ + TABLE_NAME := logicalTableName, + PARTITION_NAME := partitionName + ]; + assert TABLE_NAME in ec; + assert PARTITION_NAME in ec; + assert SORT_NAME !in ec; + assert ec[TABLE_NAME] == logicalTableName; + assert ec[PARTITION_NAME] == partitionName; + Success(ec) + else + :- Need(config.sortKeyName.value in item, DDBError("Sort key " + config.sortKeyName.value + " not found in Item to be encrypted or decrypted")); + var sortName :- DDBEncode(config.sortKeyName.value); + var sortKeyName : ValidUTF8Bytes :- EncodeName(config.sortKeyName.value); + assert |{TABLE_NAME, PARTITION_NAME, SORT_NAME}| == 3; + var ec : CMP.EncryptionContext := + map[ + TABLE_NAME := logicalTableName, + PARTITION_NAME := partitionName, + SORT_NAME := sortName + ]; + assert TABLE_NAME in ec; + assert PARTITION_NAME in ec; + assert SORT_NAME in ec; + assert ec[TABLE_NAME] == logicalTableName; + assert ec[PARTITION_NAME] == partitionName; + assert ec[SORT_NAME] == sortName; + Success(ec) + } + // string to Error function method DDBError(s : string) : Error { Error.DynamoDbItemEncryptorException(message := s) @@ -289,6 +373,47 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs UTF8.Encode(s).MapFailure(e => DDBError(e)) } + predicate method IsVersion2Schema(actions : DDBE.AttributeActions) + { + exists x <- actions :: actions[x] == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + } + function method VersionFromActions(actions : DDBE.AttributeActions) : (ret : StructuredEncryptionHeader.Version) + //= specification/dynamodb-encryption-client/ddb-table-encryption-config.md#configuration-version + //= type=implication + //# If any of the [Attribute Actions](#attribute-actions) are configured as + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT](../structured-encryption/structures.md#contextandsign) + //# then the configuration version MUST be 2; otherwise, + //# the configuration version MUST be 1. + ensures (exists x <- actions :: actions[x] == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT) <==> ret == 2 + ensures !(exists x <- actions :: actions[x] == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT) <==> ret == 1 + { + if IsVersion2Schema(actions) then + 2 + else + 1 + } + function method KeyActionFromVersion(version : StructuredEncryptionHeader.Version) : (ret : CSE.CryptoAction) + //= specification/dynamodb-encryption-client/ddb-table-encryption-config.md#key-action + //= type=implication + //# if the [configuration version](#configuration-version) is 2, then + //# the key action MUST be [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT](../structured-encryption/structures.md#contextandsign); + //# otherwise, the key action MUST be [SIGN_ONLY](../structured-encryption/structures.md#signonly). + ensures version == 1 <==> ret == CSE.SIGN_ONLY + ensures version == 2 <==> ret == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + { + if version == 2 then + CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + else + CSE.SIGN_ONLY + } + function method KeyActionStringFromVersion(version : StructuredEncryptionHeader.Version) : string + { + if version == 2 then + "SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT" + else + "SIGN_ONLY" + } + predicate ValidInternalConfig?(config: InternalConfig) { && config.cmm.ValidState() @@ -299,11 +424,11 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs // The partition key MUST be CSE.SIGN_ONLY && config.partitionKeyName in config.attributeActionsOnEncrypt - && config.attributeActionsOnEncrypt[config.partitionKeyName] == CSE.SIGN_ONLY + && config.attributeActionsOnEncrypt[config.partitionKeyName] == KeyActionFromVersion(config.version) // The sort key MUST be CSE.SIGN_ONLY && (config.sortKeyName.Some? ==> && config.sortKeyName.value in config.attributeActionsOnEncrypt - && config.attributeActionsOnEncrypt[config.sortKeyName.value] == CSE.SIGN_ONLY) + && config.attributeActionsOnEncrypt[config.sortKeyName.value] == KeyActionFromVersion(config.version)) // attributeActionsOnEncrypt only apply on Encrypt. // The config on Encrypt MAY NOT be the same as the config on Decrypt. @@ -500,15 +625,15 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs //# An item MUST be determined to be plaintext if it does not contain //# attributes with the names "aws_dbe_head" and "aws_dbe_foot". predicate method IsPlaintextItem(ddbItem: ComAmazonawsDynamodbTypes.AttributeMap) { - && StructuredEncryptionUtil.HeaderField !in ddbItem - && StructuredEncryptionUtil.FooterField !in ddbItem + && SE.HeaderField !in ddbItem + && SE.FooterField !in ddbItem } function method ConvertCryptoSchemaToAttributeActions(config: ValidConfig, schema: CSE.CryptoSchema) : (ret: Result, Error>) - requires schema.content.SchemaMap?; + requires schema.content.SchemaMap? requires forall k <- schema.content.SchemaMap :: schema.content.SchemaMap[k].content.Action? - requires forall v <- schema.content.SchemaMap.Values :: v.content.Action.SIGN_ONLY? || v.content.Action.ENCRYPT_AND_SIGN? + requires forall v <- schema.content.SchemaMap.Values :: SE.IsAuthAttr(v.content.Action) ensures ret.Success? ==> forall k <- ret.value.Keys :: InSignatureScope(config, k) ensures ret.Success? ==> forall k <- ret.value.Keys :: !ret.value[k].DO_NOTHING? { @@ -556,6 +681,25 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs + GetItemNames(item) + "." } + predicate method ContextAttrsExist(actions : DDBE.AttributeActions, item : DDB.AttributeMap) + { + forall k <- actions :: (actions[k] == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT) ==> (k in item) + } + function method ContextMissingMsg(actions : DDBE.AttributeActions, item : DDB.AttributeMap) : string + { + var s := set k <- actions | + && actions[k] == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + && k !in item; + var missing := SortedSets.ComputeSetToOrderedSequence2(s, CharLess); + if |missing| == 0 then + "No missing SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attributes." + else if |missing| == 1 then + "Attribute " + missing[0] + " was configured with SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT but was not present in item to be encrypted." + else + "These attributes were configured with SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT but were not present in item to be encrypted." + + Join(missing, ",") + } + // public Encrypt method method {:vcs_split_on_every_assert} EncryptItem(config: InternalConfig, input: EncryptItemInput) returns (output: Result) @@ -581,6 +725,16 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs // Otherwise this operation MUST yield an error. ensures config.sortKeyName.Some? && config.sortKeyName.value !in input.plaintextItem ==> output.Failure? + //= specification/dynamodb-encryption-client/encrypt-item.md#dynamodb-item + //= type=implication + //# If the [DynamoDB Item Encryptor](./ddb-item-encryptor.md) + //# has any attribute configured as + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT](../structured-encryption/structures.md#contextandsign) + //# then this item MUST include an Attribute with that name. + ensures output.Success? ==> + forall k <- config.attributeActionsOnEncrypt :: (config.attributeActionsOnEncrypt[k] == CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT) ==> (k in input.plaintextItem) + // ContextAttrsExist(config.attributeActionsOnEncrypt, input.plaintextItem) + ensures && output.Success? && !(config.internalLegacyOverride.Some? && config.internalLegacyOverride.value.policy.FORCE_LEGACY_ENCRYPT_ALLOW_LEGACY_DECRYPT?) @@ -626,14 +780,18 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs && var parsedHeaderMap := structuredEncParsed.cryptoSchema.content.SchemaMap; && (forall k <- parsedHeaderMap :: && parsedHeaderMap[k].content.Action? - && (parsedHeaderMap[k].content.Action.ENCRYPT_AND_SIGN? || parsedHeaderMap[k].content.Action.SIGN_ONLY?)) + && SE.IsAuthAttr(parsedHeaderMap[k].content.Action)) && var maybeCryptoSchema := ConvertCryptoSchemaToAttributeActions(config, structuredEncParsed.cryptoSchema); && maybeCryptoSchema.Success? + && ConvertContextForSelector(structuredEncParsed.encryptionContext).Success? + && var selectorContext := ConvertContextForSelector(structuredEncParsed.encryptionContext).value; && output.value.parsedHeader.value == ParsedHeader( attributeActionsOnEncrypt := maybeCryptoSchema.value, algorithmSuiteId := structuredEncParsed.algorithmSuiteId, storedEncryptionContext := structuredEncParsed.storedEncryptionContext, - encryptedDataKeys := structuredEncParsed.encryptedDataKeys + encryptedDataKeys := structuredEncParsed.encryptedDataKeys, + encryptionContext := structuredEncParsed.encryptionContext, + selectorContext := selectorContext ) //= specification/dynamodb-encryption-client/encrypt-item.md#behavior @@ -656,6 +814,9 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs && (config.sortKeyName.None? || config.sortKeyName.value in input.plaintextItem) , E(KeyMissingMsg(config, input.plaintextItem, "Encrypt"))); + :- Need(ContextAttrsExist(config.attributeActionsOnEncrypt, input.plaintextItem), + E(ContextMissingMsg(config.attributeActionsOnEncrypt, input.plaintextItem))); + if |input.plaintextItem| > MAX_ATTRIBUTE_COUNT { var actCount := String.Base10Int2String(|input.plaintextItem|); var maxCount := String.Base10Int2String(MAX_ATTRIBUTE_COUNT); @@ -739,11 +900,15 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs .MapFailure(e => Error.AwsCryptographyDbEncryptionSdkDynamoDb(e)); var parsedActions :- ConvertCryptoSchemaToAttributeActions(config, encryptVal.parsedHeader.cryptoSchema); + var selectorContextR := ConvertContextForSelector(encryptVal.parsedHeader.encryptionContext); + var selectorContext :- selectorContextR.MapFailure(e => E(e)); var parsedHeader := ParsedHeader( attributeActionsOnEncrypt := parsedActions, algorithmSuiteId := encryptVal.parsedHeader.algorithmSuiteId, storedEncryptionContext := encryptVal.parsedHeader.storedEncryptionContext, - encryptedDataKeys := encryptVal.parsedHeader.encryptedDataKeys + encryptedDataKeys := encryptVal.parsedHeader.encryptedDataKeys, + encryptionContext := encryptVal.parsedHeader.encryptionContext, + selectorContext := selectorContext ); output := Success(EncryptItemOutput( @@ -836,14 +1001,18 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs && structuredEncParsed.cryptoSchema.content.SchemaMap? && (forall k <- structuredEncParsed.cryptoSchema.content.SchemaMap :: && structuredEncParsed.cryptoSchema.content.SchemaMap[k].content.Action? - && (structuredEncParsed.cryptoSchema.content.SchemaMap[k].content.Action.ENCRYPT_AND_SIGN? || structuredEncParsed.cryptoSchema.content.SchemaMap[k].content.Action.SIGN_ONLY?)) + && SE.IsAuthAttr(structuredEncParsed.cryptoSchema.content.SchemaMap[k].content.Action)) && var maybeCryptoSchema := ConvertCryptoSchemaToAttributeActions(config, structuredEncParsed.cryptoSchema); && maybeCryptoSchema.Success? + && ConvertContextForSelector(structuredEncParsed.encryptionContext).Success? + && var selectorContext := ConvertContextForSelector(structuredEncParsed.encryptionContext).value; && output.value.parsedHeader.value == ParsedHeader( attributeActionsOnEncrypt := maybeCryptoSchema.value, algorithmSuiteId := structuredEncParsed.algorithmSuiteId, storedEncryptionContext := structuredEncParsed.storedEncryptionContext, - encryptedDataKeys := structuredEncParsed.encryptedDataKeys + encryptedDataKeys := structuredEncParsed.encryptedDataKeys, + encryptionContext := structuredEncParsed.encryptionContext, + selectorContext := selectorContext ) //= specification/dynamodb-encryption-client/decrypt-item.md#behavior @@ -951,12 +1120,16 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs var schemaToConvert := decryptVal.parsedHeader.cryptoSchema; var parsedAuthActions :- ConvertCryptoSchemaToAttributeActions(config, schemaToConvert); + var selectorContextR := ConvertContextForSelector(decryptVal.parsedHeader.encryptionContext); + var selectorContext :- selectorContextR.MapFailure(e => E(e)); var parsedHeader := ParsedHeader( attributeActionsOnEncrypt := parsedAuthActions, algorithmSuiteId := decryptVal.parsedHeader.algorithmSuiteId, storedEncryptionContext := decryptVal.parsedHeader.storedEncryptionContext, - encryptedDataKeys := decryptVal.parsedHeader.encryptedDataKeys + encryptedDataKeys := decryptVal.parsedHeader.encryptedDataKeys, + encryptionContext := decryptVal.parsedHeader.encryptionContext, + selectorContext := selectorContext ); output := Success( diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Index.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Index.dfy index 9f88ab83e..a63a9f44c 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Index.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Index.dfy @@ -10,6 +10,7 @@ module { import opened DynamoDbItemEncryptorUtil import StructuredEncryption + import StructuredEncryptionHeader import CSE = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes import DDBE = AwsCryptographyDbEncryptionSdkDynamoDbTypes import MaterialProviders @@ -19,7 +20,6 @@ module import SortedSets import DDB = ComAmazonawsDynamodbTypes - // There is no sensible default, so construct something simple but invalid at runtime. function method DefaultDynamoDbItemEncryptorConfig(): DynamoDbItemEncryptorConfig { @@ -38,32 +38,35 @@ module ) } - // because an inline "!(ReservedPrefix <= attr)" is too hard for Dafny predicate method UnreservedPrefix(attr : string) { - !(ReservedPrefix <= attr) + && !(ReservedPrefix <= attr) + && !(SE.ReservedCryptoContextPrefixString <= attr) } method {:vcs_split_on_every_assert} DynamoDbItemEncryptor(config: DynamoDbItemEncryptorConfig) - returns (res: Result) + returns (res: Result) ensures res.Success? ==> - && res.value.config.logicalTableName == config.logicalTableName - && res.value.config.partitionKeyName == config.partitionKeyName - && res.value.config.sortKeyName == config.sortKeyName - && res.value.config.attributeActionsOnEncrypt == config.attributeActionsOnEncrypt - && res.value.config.allowedUnsignedAttributes == config.allowedUnsignedAttributes - && res.value.config.allowedUnsignedAttributePrefix == config.allowedUnsignedAttributePrefix - && res.value.config.algorithmSuiteId == config.algorithmSuiteId + && res.value is DynamoDbItemEncryptorClient + && var rconfig := (res.value as DynamoDbItemEncryptorClient).config; + && rconfig.logicalTableName == config.logicalTableName + && rconfig.partitionKeyName == config.partitionKeyName + && rconfig.sortKeyName == config.sortKeyName + && rconfig.attributeActionsOnEncrypt == config.attributeActionsOnEncrypt + && rconfig.allowedUnsignedAttributes == config.allowedUnsignedAttributes + && rconfig.allowedUnsignedAttributePrefix == config.allowedUnsignedAttributePrefix + && rconfig.algorithmSuiteId == config.algorithmSuiteId //= specification/dynamodb-encryption-client/ddb-table-encryption-config.md#attribute-actions //= type=implication - //# The [SIGN_ONLY](../structured-encryption/structures.md#signonly) Crypto Action + //# The [Key Action](#key-action) //# MUST be configured to the partition attribute and, if present, sort attribute. + && rconfig.version == Operations.VersionFromActions(config.attributeActionsOnEncrypt) && config.partitionKeyName in config.attributeActionsOnEncrypt - && config.attributeActionsOnEncrypt[config.partitionKeyName] == CSE.SIGN_ONLY + && config.attributeActionsOnEncrypt[config.partitionKeyName] == Operations.KeyActionFromVersion(rconfig.version) && (config.sortKeyName.Some? ==> && config.sortKeyName.value in config.attributeActionsOnEncrypt - && config.attributeActionsOnEncrypt[config.sortKeyName.value] == CSE.SIGN_ONLY) + && config.attributeActionsOnEncrypt[config.sortKeyName.value] == Operations.KeyActionFromVersion(rconfig.version)) //= specification/dynamodb-encryption-client/ddb-table-encryption-config.md#plaintext-policy //# If not specified, encryption and decryption MUST behave according to `FORBID_PLAINTEXT_WRITE_FORBID_PLAINTEXT_READ`. @@ -71,7 +74,8 @@ module && res.Success? && config.plaintextOverride.None? ==> - res.value.config.plaintextOverride.FORBID_PLAINTEXT_WRITE_FORBID_PLAINTEXT_READ? + && var config := (res.value as DynamoDbItemEncryptorClient).config; + && config.plaintextOverride.FORBID_PLAINTEXT_WRITE_FORBID_PLAINTEXT_READ? { :- Need(config.keyring.None? || config.cmm.None?, DynamoDbItemEncryptorException( message := "Cannot provide both a keyring and a CMM" @@ -79,18 +83,21 @@ module :- Need(config.keyring.Some? || config.cmm.Some?, DynamoDbItemEncryptorException( message := "Must provide either a keyring or a CMM" )); + var version := Operations.VersionFromActions(config.attributeActionsOnEncrypt); + var keyAction := Operations.KeyActionFromVersion(version); + var keyActionStr := Operations.KeyActionStringFromVersion(version); :- Need( && config.partitionKeyName in config.attributeActionsOnEncrypt - && config.attributeActionsOnEncrypt[config.partitionKeyName] == CSE.SIGN_ONLY, + && config.attributeActionsOnEncrypt[config.partitionKeyName] == keyAction, DynamoDbItemEncryptorException( - message := "Partition key attribute action MUST be SIGN_ONLY" + message := "Partition key attribute action MUST be " + keyActionStr )); :- Need( (config.sortKeyName.Some? ==> && config.sortKeyName.value in config.attributeActionsOnEncrypt - && config.attributeActionsOnEncrypt[config.sortKeyName.value] == CSE.SIGN_ONLY), + && config.attributeActionsOnEncrypt[config.sortKeyName.value] == keyAction), DynamoDbItemEncryptorException( - message := "Sort key attribute action MUST be SIGN_ONLY" + message := "Sort key attribute action MUST be " + keyActionStr )); // We happen to order these values, but this ordering MUST NOT be relied upon. @@ -132,8 +139,10 @@ module // Create the structured encryption client var structuredEncryptionRes := StructuredEncryption.StructuredEncryption(); - var structuredEncryption :- structuredEncryptionRes + var structuredEncryptionX : CSE.IStructuredEncryptionClient :- structuredEncryptionRes .MapFailure(e => AwsCryptographyDbEncryptionSdkDynamoDb(DDBE.AwsCryptographyDbEncryptionSdkStructuredEncryption(e))); + assert structuredEncryptionX is StructuredEncryption.StructuredEncryptionClient; + var structuredEncryption := structuredEncryptionX as StructuredEncryption.StructuredEncryptionClient; var cmm; if (config.cmm.Some?) { @@ -180,6 +189,7 @@ module DDBE.PlaintextOverride.FORBID_PLAINTEXT_WRITE_FORBID_PLAINTEXT_READ; var internalConfig := Operations.Config( + version := version, cmpClient := cmpClient, logicalTableName := config.logicalTableName, partitionKeyName := config.partitionKeyName, @@ -192,7 +202,7 @@ module //# The [algorithm suite](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/algorithm-suites.md) that SHOULD be used for encryption. algorithmSuiteId := config.algorithmSuiteId, cmm := cmm, - structuredEncryption := structuredEncryption, + structuredEncryption := (structuredEncryption as StructuredEncryption.StructuredEncryptionClient), internalLegacyOverride := internalLegacyOverride, plaintextOverride := plaintextOverride ); diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy index 1e711d74b..1c1233d71 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy @@ -8,6 +8,12 @@ module DynamoDbItemEncryptorUtil { import opened Wrappers import opened StandardLibrary import opened StandardLibrary.UInt + import UTF8 + import MPL = AwsCryptographyMaterialProvidersTypes + import DDB = ComAmazonawsDynamodbTypes + import SortedSets + import SE = StructuredEncryptionUtil + import DynamoToStruct const ReservedPrefix := "aws_dbe_" const BeaconPrefix := ReservedPrefix + "b_" @@ -29,4 +35,195 @@ module DynamoDbItemEncryptorUtil { x < y } + const TABLE_NAME : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-table-name") + const PARTITION_NAME : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-partition-name") + const SORT_NAME : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-sort-name") + + const SELECTOR_TABLE_NAME : DDB.AttributeName := "aws_dbe_table_name" + const SELECTOR_PARTITION_NAME : DDB.AttributeName := "aws_dbe_partition_name" + const SELECTOR_SORT_NAME : DDB.AttributeName := "aws_dbe_sort_name" + + function method GetLiteralValue(x : seq) : (ret : Result) + { + var str :- UTF8.Decode(x); + if str == SE.TRUE_STR then + Success(DDB.AttributeValue.BOOL(true)) + else if str == SE.FALSE_STR then + Success(DDB.AttributeValue.BOOL(false)) + else if str == SE.NULL_STR then + Success(DDB.AttributeValue.NULL(true)) + else + Failure("Encryption Context literal value has unexpected value : '" + str + "'.") + } + + function method GetSortKey(context : MPL.EncryptionContext) : Result, string> + { + if SORT_NAME in context.Keys then + var sortName := SE.EC_ATTR_PREFIX + context[SORT_NAME]; + :- Need(UTF8.ValidUTF8Seq(sortName), "Internal Error : bad utf8 in sortName"); + Success(Some(sortName)) + else + Success(None) + } + + function method ConvertContextForSelector(context : MPL.EncryptionContext) + : (output: Result) + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - If the partition name does not exist in the encryption context, this operation MUST fail. + ensures PARTITION_NAME !in context.Keys ==> output.Failure? + + ensures output.Success? ==> + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - It MUST deserialize the "aws-crypto-partition-name" value in the input encryption context to determine the partition name. + && PARTITION_NAME in context.Keys + && var partitionName := context[PARTITION_NAME]; + && var partitionValueKey := SE.EC_ATTR_PREFIX + partitionName; + && partitionValueKey in context.Keys + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - It MUST get the serialized partition value by grabbing the `aws-crypto-attr.` from the encryption context. + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - If the partition value does not exist in the encryption context, this operation MUST fail. + ensures PARTITION_NAME in context.Keys ==> + && var partitionName := context[PARTITION_NAME]; + && var partitionValueKey := SE.EC_ATTR_PREFIX + partitionName; + && (partitionValueKey !in context.Keys) ==> output.Failure? + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - It MUST check for the existence of "aws-crypto-sort-name" in the input encryption context. + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - If this key exists, it MUST get the serialized sort value by grabbing the `aws-crypto.attr:` from the encryption context. + ensures output.Success? && SORT_NAME in context.Keys ==> + && var sortName := context[SORT_NAME]; + && var sortValueKey := SE.EC_ATTR_PREFIX + sortName; + && sortValueKey in context.Keys + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //= type=implication + //# - If the sort value does not exist in the context, this operation MUST fail. + ensures SORT_NAME in context.Keys ==> + && var sortName := context[SORT_NAME]; + && var sortValueKey := SE.EC_ATTR_PREFIX + sortName; + && (sortValueKey !in context.Keys ==> output.Failure?) + { + // Add partition key to map + :- Need(PARTITION_NAME in context.Keys, "Invalid encryption context: Missing partition name"); + var partitionName := context[PARTITION_NAME]; + var partitionValueKey := SE.EC_ATTR_PREFIX + partitionName; + :- Need(partitionValueKey in context.Keys, "Invalid encryption context: Missing partition value"); + + var sortValueKey :- GetSortKey(context); + :- Need(sortValueKey.None? || sortValueKey.value in context, "Invalid encryption context: Missing sort value"); + + var keys : seq := SortedSets.ComputeSetToOrderedSequence2(context.Keys, SE.ByteLess); + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //# - If the field "aws-crypto-legend" exists in the encryption context, + //# it MUST [deserialize](./ddb-attribute-serialization.md), all values with keys beginning "aws-crypto-attr.", + //# and create a Key with these values, using names with the "aws-crypto-attr." removed. + if SE.LEGEND_UTF8 in context then + var legend :- UTF8.Decode(context[SE.LEGEND_UTF8]); + var attrMap :- GetV2AttrMap(keys, context, legend); + + :- Need(TABLE_NAME in context, "Internal error, table name not in encryption context."); + var tableName :- UTF8.Decode(context[TABLE_NAME]); + var attrMap2 := attrMap[SELECTOR_TABLE_NAME := DDB.AttributeValue.S(tableName)]; + + :- Need(PARTITION_NAME in context, "Internal error, table name not in encryption context."); + var partitionName :- UTF8.Decode(context[PARTITION_NAME]); + var attrMap3 := attrMap2[SELECTOR_PARTITION_NAME := DDB.AttributeValue.S(partitionName)]; + + if SORT_NAME in context then + var sortName :- UTF8.Decode(context[SORT_NAME]); + Success(attrMap3[SELECTOR_SORT_NAME := DDB.AttributeValue.S(sortName)]) + else + Success(attrMap3) + + //= specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md#behavior + //# - If the field "aws-crypto-legend" does not exist in the encryption context, it MUST [deserialize the partition (and optionally sort) value](./ddb-attribute-serialization.md), and create a Key with these values. + else if sortValueKey.None? then + AddAttributeToMap(partitionValueKey, context[partitionValueKey], map[]) + else + var attrMap :- AddAttributeToMap(partitionValueKey, context[partitionValueKey], map[]); + AddAttributeToMap(sortValueKey.value, context[sortValueKey.value], attrMap) + } + + function method GetAttrValue(ecValue : UTF8.ValidUTF8Bytes, legend : char) + : Result + { + if legend == SE.LEGEND_STRING then + var value :- UTF8.Decode(ecValue); + Success(DDB.AttributeValue.S(value)) + else if legend == SE.LEGEND_NUMBER then + var value :- UTF8.Decode(ecValue); + Success(DDB.AttributeValue.N(value)) + else if legend == SE.LEGEND_LITERAL then + var value :- GetLiteralValue(ecValue); + Success(value) + else if legend == SE.LEGEND_BINARY then + var terminal :- SE.DecodeTerminal(ecValue); + var ddbAttrValue :- DynamoToStruct.BytesToAttr(terminal.value, terminal.typeId, false); + Success(ddbAttrValue.val) + else + Failure("Encryption Context Legend has unexpected character : '" + [legend] + "'.") + } + + function method GetV2AttrMap( + keys : seq, + context : MPL.EncryptionContext, + legend : string, + attrMap : DDB.AttributeMap := map[] + ) + : Result + requires forall k <- keys :: k in context + { + if |keys| == 0 then + if |legend| == 0 then + Success(attrMap) + else + Failure("Encryption Context Legend is too long.") + else + var key : UTF8.ValidUTF8Bytes := keys[0]; + if SE.EC_ATTR_PREFIX < key then + :- Need(0 < |legend|, "Encryption Context Legend is too short."); + var attrName :- GetAttributeName(key); + var attrValue :- GetAttrValue(context[key], legend[0]); + GetV2AttrMap(keys[1..], context, legend[1..], attrMap[attrName := attrValue]) + else + GetV2AttrMap(keys[1..], context, legend, attrMap) + } + + function method GetAttributeName(ddbAttrKey: UTF8.ValidUTF8Bytes) + : (res: Result) + requires |ddbAttrKey| >= |SE.EC_ATTR_PREFIX| + { + // Obtain attribute name from EC kvPair key + var ddbAttrNameBytes := ddbAttrKey[|SE.EC_ATTR_PREFIX|..]; + var ddbAttrName :- UTF8.Decode(ddbAttrNameBytes); + :- Need(DDB.IsValid_AttributeName(ddbAttrName), "Invalid serialization of DDB Attribute in encryption context."); + Success(ddbAttrName) + } + + function method AddAttributeToMap(ddbAttrKey: UTF8.ValidUTF8Bytes, encodedAttrValue: UTF8.ValidUTF8Bytes, attrMap: DDB.AttributeMap) + : (res: Result) + requires |ddbAttrKey| >= |SE.EC_ATTR_PREFIX| + { + var ddbAttrName :- GetAttributeName(ddbAttrKey); + + // Obtain attribute value from EC kvPair value + var terminal :- SE.DecodeTerminal(encodedAttrValue); + var ddbAttrValue :- DynamoToStruct.BytesToAttr(terminal.value, terminal.typeId, false); + + // Add to our AttributeMap + Success(attrMap[ddbAttrName := ddbAttrValue.val]) + } + } diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy index 351517712..f54c7b46f 100644 --- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy +++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy @@ -25,6 +25,8 @@ module DynamoDbItemEncryptorTest { // encrypt => encrypted fields changed, others did not // various errors + const PublicKeyUtf8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-public-key") + function method DDBS(x : string) : DDB.AttributeValue { DDB.AttributeValue.S(x) } @@ -61,6 +63,349 @@ module DynamoDbItemEncryptorTest { ); } + function method {:opaque} GetAttrName(s : string) : DDB.AttributeName + { + if DDB.IsValid_AttributeName(s) then + s + else + "spoo" + } + + const Actions1 : DDBE.AttributeActions := map[ + "bar" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "sortKey" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "encrypt" := CSE.ENCRYPT_AND_SIGN, + "sign" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "sign2" := CSE.SIGN_ONLY, + "sign3" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "sign4" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "nothing" := CSE.DO_NOTHING + ] + + const Actions2 : DDBE.AttributeActions := map[ + GetAttrName("bar") := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + GetAttrName("sortKey") := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + GetAttrName("encrypt") := CSE.ENCRYPT_AND_SIGN, + GetAttrName("sign") := CSE.SIGN_ONLY, + GetAttrName("sign2") := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + GetAttrName("sign3") := CSE.SIGN_ONLY, + GetAttrName("sign4") := CSE.SIGN_ONLY, + GetAttrName("nothing") := CSE.DO_NOTHING + ] + + method {:test} TestV2RoundTripComplexSwitch() { + var config := TestFixtures.GetEncryptorConfigFromActions(Actions1, Some("sortKey")); + var encryptor := TestFixtures.GetDynamoDbItemEncryptorFrom(config); + + var inputItem : map := map[ + "bar" := DDB.AttributeValue.N("00001234"), + "sortKey" := DDB.AttributeValue.B([1,2,3,4]), + "encrypt" := DDBS("text"), + "sign" := DDB.AttributeValue.BOOL(true), + "sign3" := DDB.AttributeValue.BOOL(false), + "sign4" := DDB.AttributeValue.NULL(true), + "nothing" := DDBS("baz") + ]; + var expectedOutputItem := inputItem["bar" := DDB.AttributeValue.N("1234")]; + + var encryptRes := encryptor.EncryptItem( + Types.EncryptItemInput( + plaintextItem:=inputItem + ) + ); + + if encryptRes.Failure? { + print "\n\n", encryptRes, "\n\n"; + } + expect encryptRes.Success?; + expect encryptRes.value.encryptedItem.Keys == inputItem.Keys + {SE.HeaderField, SE.FooterField}; + expect encryptRes.value.encryptedItem["bar"] == expectedOutputItem["bar"]; // because normalized + expect encryptRes.value.encryptedItem["encrypt"] != inputItem["encrypt"]; + expect encryptRes.value.encryptedItem["sign"] == inputItem["sign"]; + expect encryptRes.value.encryptedItem["sign3"] == inputItem["sign3"]; + expect encryptRes.value.encryptedItem["sign4"] == inputItem["sign4"]; + expect encryptRes.value.encryptedItem["nothing"] == inputItem["nothing"]; + + var config2 := TestFixtures.GetEncryptorConfigFromActions(Actions2, Some("sortKey")); + var encryptor2 := TestFixtures.GetDynamoDbItemEncryptorFrom(config2); + + var decryptRes := encryptor2.DecryptItem( + Types.DecryptItemInput( + encryptedItem:=encryptRes.value.encryptedItem + ) + ); + + if decryptRes.Failure? { + print "\n", decryptRes.error, "\n"; + } + expect decryptRes.Success?; + if decryptRes.value.plaintextItem != inputItem { + print "\nInput Item :\n", inputItem, "\n"; + print "\nOutput Item :\n", decryptRes.value.plaintextItem, "\n"; + } + expect decryptRes.value.plaintextItem == expectedOutputItem; + + var parsedHeader := decryptRes.value.parsedHeader; + expect parsedHeader.Some?; + expect parsedHeader.value.algorithmSuiteId == AlgorithmSuites.DBE_ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384.id.DBE; + expect parsedHeader.value.attributeActionsOnEncrypt == Actions1 - {"nothing", "sign2"}; + // Expect the verification key in the context + expect |parsedHeader.value.storedEncryptionContext| == 1; + expect PublicKeyUtf8 in parsedHeader.value.storedEncryptionContext.Keys; + expect |parsedHeader.value.encryptedDataKeys| == 1; + + var strEC := SE.EcAsString(parsedHeader.value.encryptionContext); + expect "aws-crypto-public-key" in strEC.Keys; + strEC := strEC - {"aws-crypto-public-key"}; + expect strEC == map[ + "aws-crypto-legend" := "NLLLB", + "aws-crypto-attr.bar" := "1234", + "aws-crypto-attr.sortKey" := "//8BAgME", + "aws-crypto-attr.sign" := "true", + "aws-crypto-attr.sign3" := "false", + "aws-crypto-attr.sign4" := "null", + "aws-crypto-partition-name" := "bar", + "aws-crypto-sort-name" := "sortKey", + "aws-crypto-table-name" := "foo" + ]; + expect parsedHeader.value.selectorContext == + map[ + "bar" := DDB.AttributeValue.N("1234"), + "sortKey" := DDB.AttributeValue.B([1,2,3,4]), + "sign" := DDB.AttributeValue.BOOL(true), + "sign3" := DDB.AttributeValue.BOOL(false), + "sign4" := DDB.AttributeValue.NULL(true), + + "aws_dbe_table_name" := DDB.AttributeValue.S("foo"), + "aws_dbe_partition_name" := DDB.AttributeValue.S("bar"), + "aws_dbe_sort_name" := DDB.AttributeValue.S("sortKey") + ]; + } + + + method {:test} TestV2RoundTripComplex() { + var actions := map[ + "bar" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "sortKey" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "encrypt" := CSE.ENCRYPT_AND_SIGN, + "sign" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "sign2" := CSE.SIGN_ONLY, + "sign3" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "sign4" := CSE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, + "nothing" := CSE.DO_NOTHING + ]; + var config := TestFixtures.GetEncryptorConfigFromActions(actions, Some("sortKey")); + var encryptor := TestFixtures.GetDynamoDbItemEncryptorFrom(config); + + var inputItem := map[ + "bar" := DDB.AttributeValue.N("1234"), + "sortKey" := DDB.AttributeValue.B([1,2,3,4]), + "encrypt" := DDBS("text"), + "sign" := DDB.AttributeValue.BOOL(true), + "sign3" := DDB.AttributeValue.BOOL(false), + "sign4" := DDB.AttributeValue.NULL(true), + "nothing" := DDBS("baz") + ]; + + var encryptRes := encryptor.EncryptItem( + Types.EncryptItemInput( + plaintextItem:=inputItem + ) + ); + + if encryptRes.Failure? { + print "\n\n", encryptRes, "\n\n"; + } + expect encryptRes.Success?; + expect encryptRes.value.encryptedItem.Keys == inputItem.Keys + {SE.HeaderField, SE.FooterField}; + expect encryptRes.value.encryptedItem["bar"] == inputItem["bar"]; + expect encryptRes.value.encryptedItem["encrypt"] != inputItem["encrypt"]; + expect encryptRes.value.encryptedItem["sign"] == inputItem["sign"]; + expect encryptRes.value.encryptedItem["sign3"] == inputItem["sign3"]; + expect encryptRes.value.encryptedItem["sign4"] == inputItem["sign4"]; + expect encryptRes.value.encryptedItem["nothing"] == inputItem["nothing"]; + + var decryptRes := encryptor.DecryptItem( + Types.DecryptItemInput( + encryptedItem:=encryptRes.value.encryptedItem + ) + ); + + if decryptRes.Failure? { + print "\n", decryptRes.error, "\n"; + } + expect decryptRes.Success?; + if decryptRes.value.plaintextItem != inputItem { + print "\nInput Item :\n", inputItem, "\n"; + print "\nOutput Item :\n", decryptRes.value.plaintextItem, "\n"; + } + expect decryptRes.value.plaintextItem == inputItem; + + var parsedHeader := decryptRes.value.parsedHeader; + expect parsedHeader.Some?; + expect parsedHeader.value.algorithmSuiteId == AlgorithmSuites.DBE_ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384.id.DBE; + expect parsedHeader.value.attributeActionsOnEncrypt == actions - {"nothing", "sign2"}; + // Expect the verification key in the context + // only one item in the stored context shows that the CMM was properly constructed + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //= type=test + //# Then, this operation MUST create a [Required Encryption Context CMM](https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/required-encryption-context-cmm.md) + //# with the following inputs: + expect |parsedHeader.value.storedEncryptionContext| == 1; + expect PublicKeyUtf8 in parsedHeader.value.storedEncryptionContext.Keys; + expect |parsedHeader.value.encryptedDataKeys| == 1; + + var strEC := SE.EcAsString(parsedHeader.value.encryptionContext); + expect "aws-crypto-public-key" in strEC.Keys; + strEC := strEC - {"aws-crypto-public-key"}; + + //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2 + //= type=test + //# The key MUST be the following concatenation, + //# where `attributeName` is the name of the attribute: + //# "aws-crypto-attr." + `attributeName`. + + //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2 + //= type=test + //# The value MUST be : + //# - If the type is Number or String, the unaltered (already utf8) bytes of the value + //# - If the type if Null, the string "null" + //# - If the type is Boolean, then the string "true" for true and the string "false" for false. + //# - Else, the value as defined in [Base Context Value Version 1](#base-context-value-version-1) + + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //= type=test + //# Otherwise, this operation MUST add an [entry](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2) to the encryption context for every + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) + //# [Terminal Data](./structures.md#terminal-data) + //# in the input record, plus the Legend. + + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //= type=test + //# The Legend MUST be named "aws-crypto-legend" and be a string with one character per attribute added above, + //# with a one-to-one correspondence with the attributes sorted by their UTF8 encoding, + //# each character designating the original type of the attribute, + //# to allow reversing of the [encoding](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2). + //# - 'S' if the attribute was of type String + //# - 'N' if the attribute was of type Number + //# - 'L' if the attribute was of type Null or Boolean + //# - 'B' otherwise + expect strEC == map[ + "aws-crypto-legend" := "NLLLB", + "aws-crypto-attr.bar" := "1234", + "aws-crypto-attr.sortKey" := "//8BAgME", + "aws-crypto-attr.sign" := "true", + "aws-crypto-attr.sign3" := "false", + "aws-crypto-attr.sign4" := "null", + "aws-crypto-partition-name" := "bar", + "aws-crypto-sort-name" := "sortKey", + "aws-crypto-table-name" := "foo" + ]; + expect parsedHeader.value.selectorContext == + map[ + "bar" := DDB.AttributeValue.N("1234"), + "sortKey" := DDB.AttributeValue.B([1,2,3,4]), + "sign" := DDB.AttributeValue.BOOL(true), + "sign3" := DDB.AttributeValue.BOOL(false), + "sign4" := DDB.AttributeValue.NULL(true), + + "aws_dbe_table_name" := DDB.AttributeValue.S("foo"), + "aws_dbe_partition_name" := DDB.AttributeValue.S("bar"), + "aws_dbe_sort_name" := DDB.AttributeValue.S("sortKey") + ]; + } + + method {:test} TestMissingContext() { + var actions := TestFixtures.GetV2AttributeActions(); + var config := TestFixtures.GetEncryptorConfigFromActions(actions); + var encryptor := TestFixtures.GetDynamoDbItemEncryptorFrom(config); + + var inputItem := map[ + "bar" := DDBS("key"), + "encrypt" := DDBS("text"), + "nothing" := DDBS("baz") + ]; + + var encryptRes := encryptor.EncryptItem( + Types.EncryptItemInput( + plaintextItem:=inputItem + ) + ); + expect encryptRes.Failure?; + expect encryptRes.error == E("Attribute sign was configured with SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT but was not present in item to be encrypted."); + } + + method {:test} TestV2RoundTrip() { + var actions := TestFixtures.GetV2AttributeActions(); + var config := TestFixtures.GetEncryptorConfigFromActions(actions); + var encryptor := TestFixtures.GetDynamoDbItemEncryptorFrom(config); + + var inputItem := map[ + "bar" := DDBS("key"), + "encrypt" := DDBS("text"), + "sign" := DDBS("barsoom"), + "nothing" := DDBS("baz") + ]; + + var encryptRes := encryptor.EncryptItem( + Types.EncryptItemInput( + plaintextItem:=inputItem + ) + ); + + if encryptRes.Failure? { + print "\n\n", encryptRes, "\n\n"; + } + expect encryptRes.Success?; + expect encryptRes.value.encryptedItem.Keys == inputItem.Keys + {SE.HeaderField, SE.FooterField}; + expect encryptRes.value.encryptedItem["bar"] == inputItem["bar"]; + expect encryptRes.value.encryptedItem["encrypt"] != inputItem["encrypt"]; + expect encryptRes.value.encryptedItem["sign"] == inputItem["sign"]; + expect encryptRes.value.encryptedItem["nothing"] == inputItem["nothing"]; + + var decryptRes := encryptor.DecryptItem( + Types.DecryptItemInput( + encryptedItem:=encryptRes.value.encryptedItem + ) + ); + + if decryptRes.Failure? { + print "\n", decryptRes.error, "\n"; + } + expect decryptRes.Success?; + if decryptRes.value.plaintextItem != inputItem { + print "\nInput Item :\n", inputItem, "\n"; + print "\nOutput Item :\n", decryptRes.value.plaintextItem, "\n"; + } + expect decryptRes.value.plaintextItem == inputItem; + + var parsedHeader := decryptRes.value.parsedHeader; + expect parsedHeader.Some?; + expect parsedHeader.value.algorithmSuiteId == AlgorithmSuites.DBE_ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384.id.DBE; + expect parsedHeader.value.attributeActionsOnEncrypt == actions - {"nothing"}; + // Expect the verification key in the context + expect |parsedHeader.value.storedEncryptionContext| == 1; + expect PublicKeyUtf8 in parsedHeader.value.storedEncryptionContext.Keys; + expect |parsedHeader.value.encryptedDataKeys| == 1; + + var strEC := SE.EcAsString(parsedHeader.value.encryptionContext); + expect "aws-crypto-public-key" in strEC.Keys; + strEC := strEC - {"aws-crypto-public-key"}; + expect strEC == map[ + "aws-crypto-legend" := "SS", + "aws-crypto-attr.bar" := "key", + "aws-crypto-attr.sign" := "barsoom", + "aws-crypto-partition-name" := "bar", + "aws-crypto-table-name" := "foo" + ]; + expect parsedHeader.value.selectorContext == map[ + "bar" := DDB.AttributeValue.S("key"), + "sign" := DDB.AttributeValue.S("barsoom"), + "aws_dbe_table_name" := DDB.AttributeValue.S("foo"), + "aws_dbe_partition_name" := DDB.AttributeValue.S("bar") + ]; + } + method {:test} TestRoundTrip() { var encryptor := TestFixtures.GetDynamoDbItemEncryptor(); var inputItem := map[ @@ -101,8 +446,23 @@ module DynamoDbItemEncryptorTest { expect parsedHeader.value.attributeActionsOnEncrypt == TestFixtures.GetSignedAttributeActions(); // Expect the verification key in the context expect |parsedHeader.value.storedEncryptionContext| == 1; - expect UTF8.EncodeAscii("aws-crypto-public-key") in parsedHeader.value.storedEncryptionContext.Keys; + expect PublicKeyUtf8 in parsedHeader.value.storedEncryptionContext.Keys; expect |parsedHeader.value.encryptedDataKeys| == 1; + + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //= type=test + //# If no [Crypto Action](./structures.md#crypto-action) is configured to be + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) + //# then the input cmm and encryption context MUST be used unchanged. + var strEC := SE.EcAsString(parsedHeader.value.encryptionContext); + expect "aws-crypto-public-key" in strEC.Keys; + strEC := strEC - {"aws-crypto-public-key"}; + expect strEC == map[ + "aws-crypto-attr.bar" := "AAFrZXk=", + "aws-crypto-partition-name" := "bar", + "aws-crypto-table-name" := "foo" + ]; + expect parsedHeader.value.selectorContext == map["bar" := DDB.AttributeValue.S("key")]; } method {:test} TestMaxRoundTrip() { @@ -146,7 +506,7 @@ module DynamoDbItemEncryptorTest { expect parsedHeader.value.algorithmSuiteId == AlgorithmSuites.DBE_ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384.id.DBE; // Expect the verification key in the context expect |parsedHeader.value.storedEncryptionContext| == 1; - expect UTF8.EncodeAscii("aws-crypto-public-key") in parsedHeader.value.storedEncryptionContext.Keys; + expect PublicKeyUtf8 in parsedHeader.value.storedEncryptionContext.Keys; expect |parsedHeader.value.encryptedDataKeys| == 1; } diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy index ec84abffa..089a96ab8 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy @@ -21,7 +21,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" | DO_NOT_SIGN datatype AuthenticateSchema = | AuthenticateSchema ( nameonly content: AuthenticateSchemaContent , - nameonly attributes: Option + nameonly attributes: Option := Option.None ) type AuthenticateSchemaAttributes = map datatype AuthenticateSchemaContent = @@ -32,11 +32,12 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" type AuthenticateSchemaMap = map datatype CryptoAction = | ENCRYPT_AND_SIGN + | SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT | SIGN_ONLY | DO_NOTHING datatype CryptoSchema = | CryptoSchema ( nameonly content: CryptoSchemaContent , - nameonly attributes: Option + nameonly attributes: Option := Option.None ) type CryptoSchemaAttributes = map datatype CryptoSchemaContent = @@ -50,7 +51,7 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" nameonly encryptedStructure: StructuredData , nameonly authenticateSchema: AuthenticateSchema , nameonly cmm: AwsCryptographyMaterialProvidersTypes.ICryptographicMaterialsManager , - nameonly encryptionContext: Option + nameonly encryptionContext: Option := Option.None ) datatype DecryptStructureOutput = | DecryptStructureOutput ( nameonly plaintextStructure: StructuredData , @@ -61,8 +62,8 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" nameonly plaintextStructure: StructuredData , nameonly cryptoSchema: CryptoSchema , nameonly cmm: AwsCryptographyMaterialProvidersTypes.ICryptographicMaterialsManager , - nameonly algorithmSuiteId: Option , - nameonly encryptionContext: Option + nameonly algorithmSuiteId: Option := Option.None , + nameonly encryptionContext: Option := Option.None ) datatype EncryptStructureOutput = | EncryptStructureOutput ( nameonly encryptedStructure: StructuredData , @@ -72,11 +73,12 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" nameonly cryptoSchema: CryptoSchema , nameonly algorithmSuiteId: AwsCryptographyMaterialProvidersTypes.DBEAlgorithmSuiteId , nameonly encryptedDataKeys: AwsCryptographyMaterialProvidersTypes.EncryptedDataKeyList , - nameonly storedEncryptionContext: AwsCryptographyMaterialProvidersTypes.EncryptionContext + nameonly storedEncryptionContext: AwsCryptographyMaterialProvidersTypes.EncryptionContext , + nameonly encryptionContext: AwsCryptographyMaterialProvidersTypes.EncryptionContext ) datatype StructuredData = | StructuredData ( nameonly content: StructuredDataContent , - nameonly attributes: Option + nameonly attributes: Option := Option.None ) type StructuredDataAttributes = map datatype StructuredDataContent = @@ -220,13 +222,20 @@ include "../../../../submodules/MaterialProviders/StandardLibrary/src/Index.dfy" import Operations : AbstractAwsCryptographyDbEncryptionSdkStructuredEncryptionOperations function method DefaultStructuredEncryptionConfig(): StructuredEncryptionConfig method StructuredEncryption(config: StructuredEncryptionConfig := DefaultStructuredEncryptionConfig()) - returns (res: Result) + returns (res: Result) ensures res.Success? ==> && fresh(res.value) && fresh(res.value.Modifies) && fresh(res.value.History) && res.value.ValidState() + // Helper function for the benefit of native code to create a Success(client) without referring to Dafny internals + function method CreateSuccessOfClient(client: IStructuredEncryptionClient): Result { + Success(client) + } // Helper function for the benefit of native code to create a Failure(error) without referring to Dafny internals + function method CreateFailureOfError(error: Error): Result { + Failure(error) + } class StructuredEncryptionClient extends IStructuredEncryptionClient { constructor(config: Operations.InternalConfig) diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/Model/StructuredEncryption.smithy b/DynamoDbEncryption/dafny/StructuredEncryption/Model/StructuredEncryption.smithy index 1a19b869e..d380868a4 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/Model/StructuredEncryption.smithy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/Model/StructuredEncryption.smithy @@ -45,6 +45,17 @@ operation DecryptStructure { @range(min:1, max:1) integer Version +//= specification/structured-encryption/decrypt-structure.md#parsed-header +//= type=implication +//# This structure MUST contain the following values, +//# representing the deserialized form of the header of the input encrypted structure: +//# - [Algorithm Suite ID](./header.md#format-flavor): The Algorithm Suite ID associated with the Format Flavor on the header. +//# - [Crypto Schema](./header.md#encrypt-legend): The Crypto Schema for each signed Terminal, +//# calculated using the Crypto Legend in the header, the signature scope used for decryption, and the data in the input structure. +//# - [Stored Encryption Context](./header.md#encryption-context): The Encryption Context stored in the header. +//# - [Encrypted Data Keys](./header.md#encrypted-data-keys): The Encrypted Data Keys stored in the header. +//# - [Encryption Context](#encryption-context): The full Encryption Context used. + structure ParsedHeader { @required cryptoSchema: CryptoSchema, @@ -53,7 +64,9 @@ structure ParsedHeader { @required encryptedDataKeys: EncryptedDataKeyList, @required - storedEncryptionContext: EncryptionContext + storedEncryptionContext: EncryptionContext, + @required + encryptionContext: EncryptionContext } structure EncryptStructureInput { @@ -88,6 +101,11 @@ structure EncryptStructureInput { encryptionContext: EncryptionContext } +//= specification/structured-encryption/encrypt-structure.md#output +//= type=implication +//# This operation MUST output the following: +//# - [Encrypted Structured Data](#encrypted-structured-data) +//# - [Parsed Header](./decrypt-structure.md#parsed-header) structure EncryptStructureOutput { @required encryptedStructure: StructuredData, @@ -238,6 +256,10 @@ union CryptoSchemaContent { "name": "ENCRYPT_AND_SIGN", "value": "ENCRYPT_AND_SIGN", }, + { + "name": "SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT", + "value": "SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT", + }, { "name": "SIGN_ONLY", "value": "SIGN_ONLY", diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy index 6dd501fe9..8661e3712 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy @@ -16,6 +16,7 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst import CMP = AwsCryptographyMaterialProvidersTypes import CSE = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes import Prim = AwsCryptographyPrimitivesTypes + import StructuredEncryptionHeader import Random import Aws.Cryptography.Primitives import Header = StructuredEncryptionHeader @@ -53,23 +54,22 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst predicate EncryptStructureEnsuresPublicly( input: EncryptStructureInput, output: Result) { - // Input and output types must be the same, and this constraint is useful to Dafny users + // Input and output types must be the same, and this constraint is useful to Dafny users && (output.Success? && input.plaintextStructure.content.DataMap? ==> output.value.encryptedStructure.content.DataMap?) && (output.Success? && input.plaintextStructure.content.DataList? ==> output.value.encryptedStructure.content.DataList?) && (output.Success? && input.plaintextStructure.content.Terminal? ==> output.value.encryptedStructure.content.Terminal?) - // Ensure the CryptoSchema in the ParsedHeader matches the input crypto Schema, minus any DO_NOTHING terminals + // Ensure the CryptoSchema in the ParsedHeader matches the input crypto Schema, minus any DO_NOTHING terminals && (output.Success? ==> - // For now we only support encrypting flat maps - && output.value.parsedHeader.cryptoSchema.content.SchemaMap? - && var headerSchema := output.value.parsedHeader.cryptoSchema.content.SchemaMap; - && CryptoSchemaMapIsFlat(headerSchema) - && input.cryptoSchema.content.SchemaMap? - && var inputSchema := input.cryptoSchema.content.SchemaMap; - && CryptoSchemaMapIsFlat(inputSchema) - && (forall k :: k in headerSchema ==> k in inputSchema && inputSchema[k] == headerSchema[k]) - && (forall v :: v in headerSchema.Values ==> - (v.content.Action.ENCRYPT_AND_SIGN? || v.content.Action.SIGN_ONLY?)) - ) + // For now we only support encrypting flat maps + && output.value.parsedHeader.cryptoSchema.content.SchemaMap? + && var headerSchema := output.value.parsedHeader.cryptoSchema.content.SchemaMap; + && CryptoSchemaMapIsFlat(headerSchema) + && input.cryptoSchema.content.SchemaMap? + && var inputSchema := input.cryptoSchema.content.SchemaMap; + && CryptoSchemaMapIsFlat(inputSchema) + && (forall k :: k in headerSchema ==> k in inputSchema && inputSchema[k] == headerSchema[k]) + && (forall v :: v in headerSchema.Values ==> IsAuthAttr(v.content.Action)) + ) } // given a list of fields, return only those that should be encrypted, according to the legend @@ -86,6 +86,22 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst FilterEncrypted(fields[1..], legend[1..]) } + // given a list of fields, return only those that should be added to the encryption context, according to the legend + function method {:tailrecursion} {:opaque} FilterContext(fieldMap : CanonMap, fields : seq, legend : Header.Legend, ghost data: StructuredDataPlain) + : (ret : seq) + requires |fields| == |legend| + requires forall k <- fieldMap :: fieldMap[k] in data + requires forall k <- fields :: k in fieldMap + ensures forall k <- ret :: k in data + { + if |fields| == 0 then + [] + else if legend[0] == Header.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT_LEGEND then + [fieldMap[fields[0]]] + FilterContext(fieldMap, fields[1..], legend[1..], data) + else + FilterContext(fieldMap, fields[1..], legend[1..], data) + } + // Fail unless the field exists, and is a binary terminal function method {:opaque} NeedBinary(data : StructuredDataMap, field : string): (result: Outcome) { @@ -137,43 +153,46 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst ) returns (ret : Result) ensures ret.Success? ==> - && var mat := ret.value; - && Materials.EncryptionMaterialsHasPlaintextDataKey(mat) - && ValidSuite(mat.algorithmSuite) - - //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials - //= type=implication - //# This operation MUST obtain a set of encryption materials by calling - //# [Get Encryption Materials](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cmm-interface.md#get-encryption-materials) - //# on the input [CMM](#cmm). - && (|cmm.History.GetEncryptionMaterials| == |old(cmm.History.GetEncryptionMaterials)| + 1) - && Seq.Last(cmm.History.GetEncryptionMaterials).output.Success? - && var getEncIn := Seq.Last(cmm.History.GetEncryptionMaterials).input; - //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials - //= type=implication - //# - Encryption Context: If provided, this MUST be the [input encryption context](#encryption-context); - //# otherwise, this is an empty encryption context. - && (|| (encryptionContext.None? && getEncIn.encryptionContext == map[]) - || (encryptionContext.Some? && getEncIn.encryptionContext == encryptionContext.value)) - - //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials - //= type=implication - //# - Commitment Policy: This MUST be - //# [REQUIRE_ENCRYPT_REQUIRE_DECRYPT](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/commitment-policy.md#esdkrequire_encrypt_require_decrypt). - && getEncIn.commitmentPolicy == DBE_COMMITMENT_POLICY - - //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials - //= type=implication - //# - Max Plaintext Length: This field MUST be the result of the calculation `encryptedTerminalDataNum * 2 + totalEncryptedTerminalValuesSize` - // - `encryptedTerminalDataNum` is the number of [Terminal Data](./structures.md#terminal-data) - // in the [input Structured Data](#structured-data) being encrypted, - // as defined by the [input Crypto Schema](#crypto-schema). - // - `totalEncryptedTerminalValuesSize` is the sum of the length of all [Terminal Values](./structures.md#terminal-value) - // in the [input Structured Data](#structured-data) being encrypted, - // as defined by the [input Crypto Schema](#crypto-schema). - && var maxLength := encryptedTerminalDataNum * 2 + totalEncryptedTerminalValuesSize; - && maxLength < INT64_MAX_LIMIT - && (getEncIn.maxPlaintextLength == Some(maxLength as int64)) + && var mat := ret.value; + && Materials.EncryptionMaterialsHasPlaintextDataKey(mat) + && ValidSuite(mat.algorithmSuite) + + //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials + //= type=implication + //# This operation MUST obtain a set of encryption materials by calling + //# [Get Encryption Materials](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cmm-interface.md#get-encryption-materials) + //# on the [CMM](#cmm) calculated above. + + //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials + //= type=implication + //# This operation MUST call Get Encryption Materials on the CMM as follows. + && (|cmm.History.GetEncryptionMaterials| == |old(cmm.History.GetEncryptionMaterials)| + 1) + && Seq.Last(cmm.History.GetEncryptionMaterials).output.Success? + && var getEncIn := Seq.Last(cmm.History.GetEncryptionMaterials).input; + //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials + //= type=implication + //# - Encryption Context: This MUST be the encryption context calculated above. + && (|| (encryptionContext.None? && getEncIn.encryptionContext == map[]) + || (encryptionContext.Some? && getEncIn.encryptionContext == encryptionContext.value)) + + //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials + //= type=implication + //# - Commitment Policy: This MUST be + //# [REQUIRE_ENCRYPT_REQUIRE_DECRYPT](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/commitment-policy.md#esdkrequire_encrypt_require_decrypt). + && getEncIn.commitmentPolicy == DBE_COMMITMENT_POLICY + + //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials + //= type=implication + //# - Max Plaintext Length: This field MUST be the result of the calculation `encryptedTerminalDataNum * 2 + totalEncryptedTerminalValuesSize` + // - `encryptedTerminalDataNum` is the number of [Terminal Data](./structures.md#terminal-data) + // in the [input Structured Data](#structured-data) being encrypted, + // as defined by the [input Crypto Schema](#crypto-schema). + // - `totalEncryptedTerminalValuesSize` is the sum of the length of all [Terminal Values](./structures.md#terminal-value) + // in the [input Structured Data](#structured-data) being encrypted, + // as defined by the [input Crypto Schema](#crypto-schema). + && var maxLength := encryptedTerminalDataNum * 2 + totalEncryptedTerminalValuesSize; + && maxLength < INT64_MAX_LIMIT + && (getEncIn.maxPlaintextLength == Some(maxLength as int64)) modifies cmm.Modifies requires cmm.ValidState() @@ -208,7 +227,7 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst } type EncryptCanon = c: EncryptCanonData | ValidEncryptCanon?(c) - witness * + witness * // for Encrypt, the data necessary to construct the Intermediate Encrypted Structured Data datatype EncryptCanonData = EncryptCanonData ( @@ -230,12 +249,11 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst && var headerSchema := c.cryptoSchema.content.SchemaMap; && |c.data_c| == |headerSchema| && (exists tableName :: (forall k :: k in headerSchema ==> Paths.SimpleCanon(tableName, k) in c.data_c)) - && (forall v :: v in headerSchema.Values ==> - v.content.Action? && (v.content.Action.ENCRYPT_AND_SIGN? || v.content.Action.SIGN_ONLY?)) + && (forall v :: v in headerSchema.Values ==> v.content.Action? && IsAuthAttr(v.content.Action)) } type DecryptCanon = c: DecryptCanonData | ValidDecryptCanon?(c) - witness * + witness * // for Decrypt, the data necessary to construct the Intermediate Encrypted Structured Data datatype DecryptCanonData = DecryptCanonData ( @@ -245,8 +263,9 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst // i.e. an Authenticate Action of SIGN data_c : StructuredDataCanon, // All signed fields with canonized paths // i.e. the Intermediate Encrypted Structured Data, properly encrypted - cryptoSchema : CryptoSchema // The crypto schema calculated from the crypto legend. + cryptoSchema : CryptoSchema, // The crypto schema calculated from the crypto legend. // This value is returned as part of the Parsed Header. + contextFields : seq // These fields have action SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT ) predicate ValidDecryptCanon?(c: DecryptCanonData) { @@ -293,43 +312,49 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst : (ret : Result) requires schema.Keys == data.Keys ensures ret.Success? ==> - && var r := ret.value; - - //= specification/structured-encryption/encrypt-structure.md#calculate-intermediate-encrypted-structured-data - //= type=implication - //# For every [input Terminal Data](./structures.md#terminal-data) - //# in the [input Structured Data](#structured-data), - //# a Terminal Data MUST exist with the same [canonical path](./header.md#canonical-path) - //# in Intermediate Encrypted Structured Data, - //# if the [Crypto Schema](#crypto-schema) - //# indicates a [Crypto Action](./structures.md#crypto-action) - //# other than [DO_NOTHING](./structures.md#DO_NOTHING). - && (forall k <- data :: schema[k].content.Action == DO_NOTHING || Paths.SimpleCanon(tableName, k) in ret.value.data_c) - - //= specification/structured-encryption/encrypt-structure.md#calculate-intermediate-encrypted-structured-data - //= type=implication - //# For every [Terminal Data](./structures.md#terminal-data) - //# in the Intermediate Encrypted Structured Data - //# a Terminal Data MUST exist with the same [canonical path](./header.md#canonical-path) - //# in the [input Structured Data](#structured-data). - && (forall k <- ret.value.data_c :: (exists x :: x in data && k == Paths.SimpleCanon(tableName, x))) - - && ret.value.cryptoSchema.content.SchemaMap? - && var trimmedSchema := ret.value.cryptoSchema.content.SchemaMap; - && (forall k :: k in trimmedSchema ==> k in schema && trimmedSchema[k] == schema[k]); + && var r := ret.value; + + //= specification/structured-encryption/encrypt-structure.md#calculate-intermediate-encrypted-structured-data + //= type=implication + //# For every [input Terminal Data](./structures.md#terminal-data) + //# in the [input Structured Data](#structured-data), + //# a Terminal Data MUST exist with the same [canonical path](./header.md#canonical-path) + //# in Intermediate Encrypted Structured Data, + //# if the [Crypto Schema](#crypto-schema) + //# indicates a [Crypto Action](./structures.md#crypto-action) + //# other than [DO_NOTHING](./structures.md#DO_NOTHING). + && (forall k <- data :: schema[k].content.Action == DO_NOTHING || Paths.SimpleCanon(tableName, k) in ret.value.data_c) + + //= specification/structured-encryption/encrypt-structure.md#calculate-intermediate-encrypted-structured-data + //= type=implication + //# For every [Terminal Data](./structures.md#terminal-data) + //# in the Intermediate Encrypted Structured Data + //# a Terminal Data MUST exist with the same [canonical path](./header.md#canonical-path) + //# in the [input Structured Data](#structured-data). + && (forall k <- ret.value.data_c :: (exists x :: x in data && k == Paths.SimpleCanon(tableName, x))) + + && ret.value.cryptoSchema.content.SchemaMap? + && var trimmedSchema := ret.value.cryptoSchema.content.SchemaMap; + && (forall k :: k in trimmedSchema ==> k in schema && trimmedSchema[k] == schema[k]) { var fieldMap := GetFieldMap(tableName, data, schema); - var data_c := map k <- fieldMap :: k := data[fieldMap[k]]; - var signedFields_c := SortedSets.ComputeSetToOrderedSequence2(data_c.Keys, ByteLess); - var encFields_c := FilterEncrypt(signedFields_c, fieldMap, schema); + var data_c : StructuredDataCanon := map k <- fieldMap :: k := data[fieldMap[k]]; + var signedFields_c : seq := SortedSets.ComputeSetToOrderedSequence2(data_c.Keys, ByteLess); + var encFields_c : seq := FilterEncrypt(signedFields_c, fieldMap, schema); var trimmedSchema := map k <- fieldMap :: fieldMap[k] := schema[fieldMap[k]]; assert |data_c| == |trimmedSchema| by { assert data_c.Keys == fieldMap.Keys; assert trimmedSchema.Keys == fieldMap.Values; LemmaInjectiveImpliesUniqueValues(fieldMap); - } + } // with all extraneous DO_NOTHING actions removed + + assert forall k :: k in encFields_c ==> k in signedFields_c; + assert forall k :: k in signedFields_c ==> k in data_c; + assert forall k :: k in data_c ==> k in signedFields_c; + var newSchema := CryptoSchemaContent.SchemaMap(trimmedSchema); + assert |data_c| == |newSchema.SchemaMap|; Success( EncryptCanonData( @@ -337,7 +362,7 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst signedFields_c, data_c, CryptoSchema( - content := CryptoSchemaContent.SchemaMap(trimmedSchema), + content := newSchema, attributes := None ) ) @@ -353,16 +378,17 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst ) : (ret : Result) requires authSchema.Keys == data.Keys ensures ret.Success? ==> - && |ret.value.signedFields_c| == |legend| + && |ret.value.signedFields_c| == |legend| ensures ret.Success? ==> - && (forall k :: k in data.Keys && authSchema[k].content.Action.SIGN? ==> Paths.SimpleCanon(tableName, k) in ret.value.data_c.Keys) + && (forall k :: k in data.Keys && authSchema[k].content.Action.SIGN? ==> Paths.SimpleCanon(tableName, k) in ret.value.data_c.Keys) ensures ret.Success? ==> - && (forall v :: v in ret.value.data_c.Values ==> v in data.Values) + && (forall v :: v in ret.value.data_c.Values ==> v in data.Values) ensures ret.Success? ==> - && ret.value.cryptoSchema.content.SchemaMap? - && CryptoSchemaMapIsFlat(ret.value.cryptoSchema.content.SchemaMap) - && AuthSchemaIsFlat(authSchema) - && ValidParsedCryptoSchema(ret.value.cryptoSchema.content.SchemaMap, authSchema, tableName) + && ret.value.cryptoSchema.content.SchemaMap? + && CryptoSchemaMapIsFlat(ret.value.cryptoSchema.content.SchemaMap) + && AuthSchemaIsFlat(authSchema) + && ValidParsedCryptoSchema(ret.value.cryptoSchema.content.SchemaMap, authSchema, tableName) + ensures ret.Success? ==> forall k <- ret.value.contextFields :: k in data { //= specification/structured-encryption/decrypt-structure.md#calculate-signed-and-encrypted-field-lists //# The `signed field list` MUST be all fields for which @@ -374,107 +400,212 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst reveal Maps.Injective(); Paths.SimpleCanonUnique(tableName); var fieldMap := map k <- data | authSchema[k].content.Action == SIGN :: - Paths.SimpleCanon(tableName, k) := k; + Paths.SimpleCanon(tableName, k) := k; assert Maps.Injective(fieldMap); + assert forall k <- fieldMap :: fieldMap[k] in data; var data_c := map k <- fieldMap :: k := data[fieldMap[k]]; var signedFields_c := SortedSets.ComputeSetToOrderedSequence2(data_c.Keys, ByteLess); if |legend| < |signedFields_c| then Failure(E("Schema changed : something that was unsigned is now signed.")) - else + else if |legend| > |signedFields_c| then Failure(E("Schema changed : something that was signed is now unsigned.")) else - //= specification/structured-encryption/decrypt-structure.md#calculate-signed-and-encrypted-field-lists - //# The `encrypted field list` MUST be all fields in the `signed field list` - //# for which the corresponding byte in the [Encrypt Legend](header.md.#encrypt-legend) - //# is `0x65` indicating [Encrypt and Sign](header.md.#encrypt-legend-bytes), - //# sorted by the field's [canonical path](./header.md#canonical-path). - var encFields_c : seq := FilterEncrypted(signedFields_c, legend); - :- Need(|encFields_c| < (UINT32_LIMIT / 3), E("Too many encrypted fields.")); - - var actionMap := map k <- fieldMap :: - fieldMap[k] := if Paths.SimpleCanon(tableName, fieldMap[k]) in encFields_c then - CryptoSchema( - content := CryptoSchemaContent.Action(ENCRYPT_AND_SIGN), - attributes := None - ) - else - CryptoSchema( - content := CryptoSchemaContent.Action(SIGN_ONLY), - attributes := None - ); - var cryptoSchema := CryptoSchema( - content := CryptoSchemaContent.SchemaMap(actionMap), - attributes := None - ); + //= specification/structured-encryption/decrypt-structure.md#calculate-signed-and-encrypted-field-lists + //# The `encrypted field list` MUST be all fields in the `signed field list` + //# for which the corresponding byte in the [Encrypt Legend](header.md.#encrypt-legend) + //# is `0x65` indicating [Encrypt and Sign](header.md.#encrypt-legend-bytes), + //# sorted by the field's [canonical path](./header.md#canonical-path). + var encFields_c : seq := FilterEncrypted(signedFields_c, legend); + :- Need(|encFields_c| < (UINT32_LIMIT / 3), E("Too many encrypted fields.")); + + var contextFields : seq := FilterContext(fieldMap, signedFields_c, legend, data); + assert forall k <- contextFields :: k in data; + + var actionMap := map k <- fieldMap :: + fieldMap[k] := if Paths.SimpleCanon(tableName, fieldMap[k]) in encFields_c then + CryptoSchema( + content := CryptoSchemaContent.Action(ENCRYPT_AND_SIGN), + attributes := None + ) + else if fieldMap[k] in contextFields then + CryptoSchema( + content := CryptoSchemaContent.Action(SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT), + attributes := None + ) + else + CryptoSchema( + content := CryptoSchemaContent.Action(SIGN_ONLY), + attributes := None + ); + var cryptoSchema := CryptoSchema( + content := CryptoSchemaContent.SchemaMap(actionMap), + attributes := None + ); + + var c := DecryptCanonData( + encFields_c, + signedFields_c, + data_c, + cryptoSchema, + contextFields + ); + + assert |data_c| == |actionMap| by { + assert data_c.Keys == fieldMap.Keys; + assert actionMap.Keys == fieldMap.Values; + LemmaInjectiveImpliesUniqueValues(fieldMap); + } + + assert exists tableName :: + (forall k :: k in c.cryptoSchema.content.SchemaMap ==> Paths.SimpleCanon(tableName, k) in c.data_c); + + Success(c) + } - var c := DecryptCanonData( - encFields_c, - signedFields_c, - data_c, - cryptoSchema - ); + method GetV2EncryptionContext(schema : FlatSchemaMap, record : FlatDataMap) + returns (output : Result) + requires (forall x <- schema :: x in record) + { + var contextAttrs := set k <- schema | schema[k].content.Action == SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT :: k; + var contextFields := SortedSets.ComputeSetToOrderedSequence2(contextAttrs, CharLess); + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //# Otherwise, this operation MUST add an [entry](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2) to the encryption context for every + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) + //# [Terminal Data](./structures.md#terminal-data) + //# in the input record, plus the Legend. + output := GetV2EncryptionContext2(contextFields, record); + } - assert |data_c| == |actionMap| by { - assert data_c.Keys == fieldMap.Keys; - assert actionMap.Keys == fieldMap.Values; - LemmaInjectiveImpliesUniqueValues(fieldMap); + method {:vcs_split_on_every_assert} GetV2EncryptionContext2(fields : seq, record : FlatDataMap) + returns (output : Result) + requires forall k <- fields :: k in record + { + //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2 + //# The key MUST be the following concatenation, + //# where `attributeName` is the name of the attribute: + //# "aws-crypto-attr." + `attributeName`. + var fieldMap : map := map[]; + for i := 0 to |fields| + invariant forall k <- fieldMap :: fieldMap[k] in record + { + var utf8Value :- UTF8.Encode(ATTR_PREFIX + fields[i]).MapFailure(e =>E(e)); + fieldMap := fieldMap[utf8Value := fields[i]]; } + var keys : seq := SortedSets.ComputeSetToOrderedSequence2(fieldMap.Keys, ByteLess); + var newContext : CMP.EncryptionContext := map[]; + var legend : string := ""; + + //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2 + //# The value MUST be : + //# - If the type is Number or String, the unaltered (already utf8) bytes of the value + //# - If the type if Null, the string "null" + //# - If the type is Boolean, then the string "true" for true and the string "false" for false. + //# - Else, the value as defined in [Base Context Value Version 1](#base-context-value-version-1) + + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //# The Legend MUST be named "aws-crypto-legend" and be a string with one character per attribute added above, + //# with a one-to-one correspondence with the attributes sorted by their UTF8 encoding, + //# each character designating the original type of the attribute, + //# to allow reversing of the [encoding](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2). + //# - 'S' if the attribute was of type String + //# - 'N' if the attribute was of type Number + //# - 'L' if the attribute was of type Null or Boolean + //# - 'B' otherwise + for i := 0 to |keys| + invariant forall j | 0 <= j < i :: keys[j] in newContext + invariant forall k <- newContext :: k in keys[..i] + invariant |legend| == |newContext| == i + { + assert keys[i] !in newContext by { + reveal Seq.HasNoDuplicates(); + } + var fieldUtf8 := keys[i]; + var fieldStr := fieldMap[fieldUtf8]; + var attr : StructuredDataTerminal := record[fieldStr].content.Terminal; + var attrStr : ValidUTF8Bytes; + var legendChar : char; + if attr.typeId == NULL { + legendChar := LEGEND_LITERAL; + attrStr := NULL_UTF8; + } else if attr.typeId == STRING { + legendChar := LEGEND_STRING; + :- Need(ValidUTF8Seq(attr.value), E("Internal Error : string was not UTF8.")); + attrStr := attr.value; + var yy :- expect UTF8.Decode(attrStr); + } else if attr.typeId == NUMBER { + legendChar := LEGEND_NUMBER; + :- Need(ValidUTF8Seq(attr.value), E("Internal Error : number was not UTF8.")); + attrStr := attr.value; + } else if attr.typeId == BOOLEAN { + legendChar := LEGEND_LITERAL; + :- Need(|attr.value| == 1, E("Internal Error : boolean was not of length 1.")); + attrStr := if attr.value[0] == 0 then FALSE_UTF8 else TRUE_UTF8; + } else { + legendChar := LEGEND_BINARY; + attrStr := EncodeTerminal(attr); + } + assert fieldUtf8 !in newContext; + newContext := newContext[fieldUtf8 := attrStr]; + legend := legend + [legendChar]; + assert forall j | 0 <= j < i+1 :: keys[j] in newContext by { + assert keys[i] in newContext; + } + } + var utf8Legend :- UTF8.Encode(legend).MapFailure(e =>E(e)); + newContext := newContext[LEGEND_UTF8 := utf8Legend]; - assert exists tableName :: - (forall k :: k in c.cryptoSchema.content.SchemaMap ==> Paths.SimpleCanon(tableName, k) in c.data_c); - - Success(c) + return Success(newContext); } method {:vcs_split_on_every_assert} EncryptStructure(config: InternalConfig, input: EncryptStructureInput) returns (output: Result) ensures output.Success? ==> - //= specification/structured-encryption/encrypt-structure.md#structured-data - //= type=implication - //# This Structured Data MUST be a [Structured Data Map](./structures.md#structured-data-map). - && input.plaintextStructure.content.DataMap? - - //= specification/structured-encryption/encrypt-structure.md#structured-data - //= type=implication - //# This Structured Data MUST NOT already contain data located at the [header index](./header.md#header-index) - //# or the [footer index](./footer.md#footer-index). - && var plainRecord := input.plaintextStructure.content.DataMap; - && DataMapIsFlat(plainRecord) - && HeaderField !in plainRecord - && FooterField !in plainRecord - - //= specification/structured-encryption/encrypt-structure.md#crypto-schema - //= type=implication - //# The Crypto Schema MUST explicitly configure a [Crypto Action](./structures.md#crypto-action) for every - //# [Terminal Data](./structures.md#terminal-data) that exists on the [input Structured Data](#structured-data), - //# and MUST NOT describe Crypto Actions for locations within the input Structured Data that either - //# do not exist, or contain non-Terminal Data structures; - //# otherwise, this operation operation MUST yield an error. - && input.cryptoSchema.content.SchemaMap? - && var cryptoSchema := input.cryptoSchema.content.SchemaMap; - && CryptoSchemaMapIsFlat(cryptoSchema) - && plainRecord.Keys == cryptoSchema.Keys - - //= specification/structured-encryption/encrypt-structure.md#crypto-schema - //= type=implication - //# The Crypto Schema MUST include at least one [ENCRYPT_AND_SIGN Crypto Action](./structures.md#encryptandsign) or - //# [SIGN_ONLY Crypto Action](./structures.md#signonly); - //# otherwise, this operation MUST yield an error. - && (exists k <- cryptoSchema :: cryptoSchema[k].content.Action != DO_NOTHING) - - //= specification/structured-encryption/encrypt-structure.md#encrypted-structured-data-1 - //= type=implication - //# - The [Header Field](#header-field) MUST exist in the Encrypted Structured Data - && HeaderField in output.value.encryptedStructure.content.DataMap - - //= specification/structured-encryption/encrypt-structure.md#encrypted-structured-data-1 - //= type=implication - //# - The [Footer Field](#footer-field) MUST exist in the Encrypted Structured Data - && FooterField in output.value.encryptedStructure.content.DataMap + //= specification/structured-encryption/encrypt-structure.md#structured-data + //= type=implication + //# This Structured Data MUST be a [Structured Data Map](./structures.md#structured-data-map). + && input.plaintextStructure.content.DataMap? + + //= specification/structured-encryption/encrypt-structure.md#structured-data + //= type=implication + //# This Structured Data MUST NOT already contain data located at the [header index](./header.md#header-index) + //# or the [footer index](./footer.md#footer-index). + && var plainRecord := input.plaintextStructure.content.DataMap; + && DataMapIsFlat(plainRecord) + && HeaderField !in plainRecord + && FooterField !in plainRecord + + //= specification/structured-encryption/encrypt-structure.md#crypto-schema + //= type=implication + //# The Crypto Schema MUST explicitly configure a [Crypto Action](./structures.md#crypto-action) for every + //# [Terminal Data](./structures.md#terminal-data) that exists on the [input Structured Data](#structured-data), + //# and MUST NOT describe Crypto Actions for locations within the input Structured Data that either + //# do not exist, or contain non-Terminal Data structures; + //# otherwise, this operation operation MUST yield an error. + && input.cryptoSchema.content.SchemaMap? + && var cryptoSchema := input.cryptoSchema.content.SchemaMap; + && CryptoSchemaMapIsFlat(cryptoSchema) + && plainRecord.Keys == cryptoSchema.Keys + + //= specification/structured-encryption/encrypt-structure.md#crypto-schema + //= type=implication + //# The Crypto Schema MUST include at least one [ENCRYPT_AND_SIGN Crypto Action](./structures.md#encryptandsign) or + //# [SIGN_ONLY Crypto Action](./structures.md#signonly); + //# otherwise, this operation MUST yield an error. + && (exists k <- cryptoSchema :: cryptoSchema[k].content.Action != DO_NOTHING) + + //= specification/structured-encryption/encrypt-structure.md#encrypted-structured-data-1 + //= type=implication + //# - The [Header Field](#header-field) MUST exist in the Encrypted Structured Data + && HeaderField in output.value.encryptedStructure.content.DataMap + + //= specification/structured-encryption/encrypt-structure.md#encrypted-structured-data-1 + //= type=implication + //# - The [Footer Field](#footer-field) MUST exist in the Encrypted Structured Data + && FooterField in output.value.encryptedStructure.content.DataMap { :- Need(input.plaintextStructure.content.DataMap?, E("Input structure must be a DataMap")); @@ -487,13 +618,11 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst var cryptoSchema := input.cryptoSchema.content.SchemaMap; :- Need(CryptoSchemaMapIsFlat(cryptoSchema), E("Schema must be flat.")); :- Need(forall k <- cryptoSchema :: ValidString(k), E("Schema has bad field name.")); - :- Need(exists k <- cryptoSchema :: ( - || cryptoSchema[k].content.Action == ENCRYPT_AND_SIGN - || cryptoSchema[k].content.Action == SIGN_ONLY - ), E("At least one field in the Crypto Schema must be ENCRYPT_AND_SIGN or SIGN_ONLY.")); + :- Need(exists k <- cryptoSchema :: IsAuthAttr(cryptoSchema[k].content.Action), + E("At least one field in the Crypto Schema must be ENCRYPT_AND_SIGN, SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT or SIGN_ONLY.")); - var plainRecord := input.plaintextStructure.content.DataMap; - :- Need(DataMapIsFlat(plainRecord), E("Input DataMap must be flat.")); + :- Need(DataMapIsFlat(input.plaintextStructure.content.DataMap), E("Input DataMap must be flat.")); + var plainRecord : FlatDataMap := input.plaintextStructure.content.DataMap; :- Need(HeaderField !in plainRecord, E("The field name " + HeaderField + " is reserved.")); :- Need(FooterField !in plainRecord, E("The field name " + FooterField + " is reserved.")); :- Need(plainRecord.Keys == cryptoSchema.Keys, E("Schema must exactly match record")); @@ -501,12 +630,47 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst :- Need(ValidString(input.tableName), E("Bad Table Name")); var canonData :- CanonizeForEncrypt(input.tableName, plainRecord, cryptoSchema); + //= specification/structured-encryption/encrypt-structure.md#retrieve-encryption-materials + //# This operation MUST [calculate the appropriate CMM and encryption context](#create-new-encryption-context-and-cmm). + var encryptionContext := input.encryptionContext.UnwrapOr(map[]); + var cmm := input.cmm; + + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //# If no [Crypto Action](./structures.md#crypto-action) is configured to be + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) + //# then the input cmm and encryption context MUST be used unchanged. + if exists x <- cryptoSchema :: cryptoSchema[x].content.Action == SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT { + assume {:axiom} input.cmm.Modifies !! {config.materialProviders.History}; + var newEncryptionContext :- GetV2EncryptionContext(cryptoSchema, plainRecord); + if |newEncryptionContext| != 0 { + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //# An error MUST be returned if any of the entries added to the encryption context in this step + //# have the same key as any entry already in the encryption context. + :- Need(encryptionContext.Keys !! newEncryptionContext.Keys, + E("Internal Error - Structured Encryption encryption context overlaps with Item Encryptor encryption context.")); + encryptionContext := encryptionContext + newEncryptionContext; + assert cmm.Modifies !! {config.materialProviders.History}; + //= specification/structured-encryption/encrypt-structure.md#create-new-encryption-context-and-cmm + //# Then, this operation MUST create a [Required Encryption Context CMM](https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/required-encryption-context-cmm.md) + //# with the following inputs: + //# - This input [CMM](./ddb-table-encryption-config.md#cmm) as the underlying CMM. + //# - The name of every entry added above. + var cmmR := config.materialProviders.CreateRequiredEncryptionContextCMM( + CMP.CreateRequiredEncryptionContextCMMInput( + underlyingCMM := Some(cmm), + keyring := None, + requiredEncryptionContextKeys := SortedSets.ComputeSetToOrderedSequence2(newEncryptionContext.Keys, ByteLess) + ) + ); + cmm :- cmmR.MapFailure(e => AwsCryptographyMaterialProviders(e)); + } + } var mat :- GetStructuredEncryptionMaterials( - input.cmm, - input.encryptionContext, - input.algorithmSuiteId, - |canonData.encFields_c|, - SumValueSize(canonData.encFields_c, canonData.data_c)); + cmm, + Some(encryptionContext), + input.algorithmSuiteId, + |canonData.encFields_c|, + SumValueSize(canonData.encFields_c, canonData.data_c)); var key : Key := mat.plaintextDataKey.value; var alg := mat.algorithmSuite; @@ -537,7 +701,8 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst :- Need(|canonData.encFields_c| < (UINT32_LIMIT / 3), E("Too many encrypted fields")); var encryptedItems :- Crypt.Encrypt(config.primitives, alg, key, head, canonData.encFields_c, canonData.data_c); - var result : map := map k <- plainRecord | true :: k := + var result : map := map k <- plainRecord | true + :: k := var c := Paths.SimpleCanon(input.tableName, k); if c in encryptedItems then encryptedItems[c] @@ -570,7 +735,7 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst assert {:split_here} true; var footer :- Footer.CreateFooter(config.primitives, mat, canonData.signedFields_c, - canonData.encFields_c, encryptedItems, canonData.data_c, headerSerialized); + canonData.encFields_c, encryptedItems, canonData.data_c, headerSerialized); var footerAttribute := footer.makeTerminal(); result := result[HeaderField := headerAttribute]; @@ -580,12 +745,13 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst var headerAlgorithmSuite :- head.GetAlgorithmSuite(config.materialProviders); var parsedHeader := ParsedHeader( - cryptoSchema := canonData.cryptoSchema, - algorithmSuiteId := headerAlgorithmSuite.id.DBE, - encryptedDataKeys := head.dataKeys, - storedEncryptionContext := head.encContext + cryptoSchema := canonData.cryptoSchema, + algorithmSuiteId := headerAlgorithmSuite.id.DBE, + encryptedDataKeys := head.dataKeys, + storedEncryptionContext := head.encContext, + encryptionContext := mat.encryptionContext ); - + var encryptOutput := EncryptStructureOutput( encryptedStructure := StructuredData( content := StructuredDataContent.DataMap( @@ -601,21 +767,21 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst predicate DecryptStructureEnsuresPublicly( input: DecryptStructureInput, output: Result) { - // Input and output types must be the same, and this constraint is useful to Dafny users + // Input and output types must be the same, and this constraint is useful to Dafny users && (output.Success? && input.encryptedStructure.content.DataMap? ==> output.value.plaintextStructure.content.DataMap?) && (output.Success? && input.encryptedStructure.content.DataList? ==> output.value.plaintextStructure.content.DataList?) && (output.Success? && input.encryptedStructure.content.Terminal? ==> output.value.plaintextStructure.content.Terminal?) - // Ensure the CryptoSchema in the ParsedHeader is consistent with the input authenticateSchema + // Ensure the CryptoSchema in the ParsedHeader is consistent with the input authenticateSchema && (output.Success? ==> - // For now we only support decrypting flat maps - && output.value.parsedHeader.cryptoSchema.content.SchemaMap? - && var cryptoMap := output.value.parsedHeader.cryptoSchema.content.SchemaMap; - && CryptoSchemaMapIsFlat(cryptoMap) - && input.authenticateSchema.content.SchemaMap? - && var authMap := input.authenticateSchema.content.SchemaMap; - && AuthSchemaIsFlat(authMap) - && ValidString(input.tableName) - && ValidParsedCryptoSchema(cryptoMap, authMap + ReservedAuthMap, input.tableName)) + // For now we only support decrypting flat maps + && output.value.parsedHeader.cryptoSchema.content.SchemaMap? + && var cryptoMap := output.value.parsedHeader.cryptoSchema.content.SchemaMap; + && CryptoSchemaMapIsFlat(cryptoMap) + && input.authenticateSchema.content.SchemaMap? + && var authMap := input.authenticateSchema.content.SchemaMap; + && AuthSchemaIsFlat(authMap) + && ValidString(input.tableName) + && ValidParsedCryptoSchema(cryptoMap, authMap + ReservedAuthMap, input.tableName)) } predicate ValidParsedCryptoSchema(cryptoSchema: CryptoSchemaMap, authSchema: AuthenticateSchemaMap, tableName: GoodString) @@ -624,17 +790,16 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst { // Every field in the crypto map exists in the auth map as SIGN && (forall k <- cryptoSchema.Keys :: k in authSchema && authSchema[k].content.Action.SIGN?) - // The crypto map is not missing any SIGN fields from the auth map + // The crypto map is not missing any SIGN fields from the auth map && (forall kv <- authSchema.Items | kv.1.content.Action.SIGN? :: kv.0 in cryptoSchema.Keys) - // Every field in the crypto map is ENCRYPT_AND_SIGN or SIGN_ONLY - && (forall v <- cryptoSchema.Values :: v.content.Action.SIGN_ONLY? || v.content.Action.ENCRYPT_AND_SIGN?) - + // Every field in the crypto map is ENCRYPT_AND_SIGN, SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT or SIGN_ONLY + && (forall v <- cryptoSchema.Values :: IsAuthAttr(v.content.Action)) } const ReservedAuthMap : AuthSchemaPlain := map[ - HeaderField := DoNotSign, // The header field is authenticated in the footer via a separate mechanism - FooterField := DoNotSign - ] + HeaderField := DoNotSign, // The header field is authenticated in the footer via a separate mechanism + FooterField := DoNotSign + ] function method SafeDecode(data : CMP.Utf8Bytes) : string { @@ -680,67 +845,67 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst //# This operation MUST output a [Structured Data](#structured-data) with the following specifics: returns (output: Result) ensures output.Success? ==> - && input.encryptedStructure.content.DataMap? - && DataMapIsFlat(input.encryptedStructure.content.DataMap) - && var encRecord := input.encryptedStructure.content.DataMap; - - //= specification/structured-encryption/decrypt-structure.md#parse-the-header - //= type=implication - //# Given the [input Structured Data](#structured-data), - //# this operation MUST access the [Terminal Data](./structures.md#terminal-data) - //# at the "aws_dbe_head" - - //= specification/structured-encryption/decrypt-structure.md#parse-the-header - //= type=implication - //# The [Terminal Type Id](./structures.md#terminal-type-id) on this Terminal Data MUST be `0xFFFF`. - && NeedBinary(encRecord, HeaderField).Pass? - - //= specification/structured-encryption/decrypt-structure.md#verify-signatures - //= type=implication - //# A footer field MUST exist with the name `aws_dbe_foot` - - //= specification/structured-encryption/decrypt-structure.md#verify-signatures - //= type=implication - //# The footer field TypeID MUST be 0xFFFF - && NeedBinary(encRecord, FooterField).Pass? - - //= specification/structured-encryption/decrypt-structure.md#authenticate-schema - //= type=implication - //# The Authenticate Schema MUST explicitly configure a [Authenticate Action](./structures.md#authenticate-action) for every - //# [Terminal Data](./structures.md#terminal-data) that exists on the [input Structured Data](#structured-data), - //# and MUST NOT describe Authenticate Actions for locations within the input Structured Data that either - //# do not exist, or contain non-Terminal Data structures; - //# otherwise, this operation operation MUST yield an error. - && input.authenticateSchema.content.SchemaMap? - && input.authenticateSchema.content.SchemaMap.Keys + ReservedAuthMap.Keys == input.encryptedStructure.content.DataMap.Keys - - //= specification/structured-encryption/decrypt-structure.md#authenticate-schema - //= type=implication - //# The Authenticate Schema MUST include at least one [SIGN Authenticate Action](./structures.md#sign); - //# otherwise, this operation MUST yield an error. - && AuthSchemaIsFlat(input.authenticateSchema.content.SchemaMap) - && (exists x :: (x in input.authenticateSchema.content.SchemaMap && input.authenticateSchema.content.SchemaMap[x].content.Action == SIGN)) - - && var headerSerialized := encRecord[HeaderField].content.Terminal.value; - //= specification/structured-encryption/decrypt-structure.md#parse-the-header - //= type=implication - //# This operation MUST deserialize the header bytes - //# according to the [header format](./header.md). - && Header.PartialDeserialize(headerSerialized).Success? - && var head := Header.PartialDeserialize(headerSerialized).value; - - //= specification/structured-encryption/decrypt-structure.md#construct-decrypted-structured-data - //= type=implication - //# - [Terminal Data](./structures.md#terminal-data) MUST NOT exist at the "aws_dbe_head" - //# or "aws_dbe_foot". - && HeaderField !in output.value.plaintextStructure.content.DataMap - && FooterField !in output.value.plaintextStructure.content.DataMap + && input.encryptedStructure.content.DataMap? + && DataMapIsFlat(input.encryptedStructure.content.DataMap) + && var encRecord := input.encryptedStructure.content.DataMap; + + //= specification/structured-encryption/decrypt-structure.md#parse-the-header + //= type=implication + //# Given the [input Structured Data](#structured-data), + //# this operation MUST access the [Terminal Data](./structures.md#terminal-data) + //# at the "aws_dbe_head" + + //= specification/structured-encryption/decrypt-structure.md#parse-the-header + //= type=implication + //# The [Terminal Type Id](./structures.md#terminal-type-id) on this Terminal Data MUST be `0xFFFF`. + && NeedBinary(encRecord, HeaderField).Pass? + + //= specification/structured-encryption/decrypt-structure.md#verify-signatures + //= type=implication + //# A footer field MUST exist with the name `aws_dbe_foot` + + //= specification/structured-encryption/decrypt-structure.md#verify-signatures + //= type=implication + //# The footer field TypeID MUST be 0xFFFF + && NeedBinary(encRecord, FooterField).Pass? + + //= specification/structured-encryption/decrypt-structure.md#authenticate-schema + //= type=implication + //# The Authenticate Schema MUST explicitly configure a [Authenticate Action](./structures.md#authenticate-action) for every + //# [Terminal Data](./structures.md#terminal-data) that exists on the [input Structured Data](#structured-data), + //# and MUST NOT describe Authenticate Actions for locations within the input Structured Data that either + //# do not exist, or contain non-Terminal Data structures; + //# otherwise, this operation operation MUST yield an error. + && input.authenticateSchema.content.SchemaMap? + && input.authenticateSchema.content.SchemaMap.Keys + ReservedAuthMap.Keys == input.encryptedStructure.content.DataMap.Keys + + //= specification/structured-encryption/decrypt-structure.md#authenticate-schema + //= type=implication + //# The Authenticate Schema MUST include at least one [SIGN Authenticate Action](./structures.md#sign); + //# otherwise, this operation MUST yield an error. + && AuthSchemaIsFlat(input.authenticateSchema.content.SchemaMap) + && (exists x :: (x in input.authenticateSchema.content.SchemaMap && input.authenticateSchema.content.SchemaMap[x].content.Action == SIGN)) + + && var headerSerialized := encRecord[HeaderField].content.Terminal.value; + //= specification/structured-encryption/decrypt-structure.md#parse-the-header + //= type=implication + //# This operation MUST deserialize the header bytes + //# according to the [header format](./header.md). + && Header.PartialDeserialize(headerSerialized).Success? + && var head := Header.PartialDeserialize(headerSerialized).value; + + //= specification/structured-encryption/decrypt-structure.md#construct-decrypted-structured-data + //= type=implication + //# - [Terminal Data](./structures.md#terminal-data) MUST NOT exist at the "aws_dbe_head" + //# or "aws_dbe_foot". + && HeaderField !in output.value.plaintextStructure.content.DataMap + && FooterField !in output.value.plaintextStructure.content.DataMap { :- Need(input.authenticateSchema.content.SchemaMap?, E("Authenticate Schema must be a SchemaMap")); :- Need(AuthSchemaIsFlat(input.authenticateSchema.content.SchemaMap), E("Schema must be flat.")); :- Need(forall k <- input.authenticateSchema.content.SchemaMap :: ValidString(k), E("Schema has bad field name.")); :- Need(forall k <- input.authenticateSchema.content.SchemaMap | k in ReservedAuthMap :: - input.authenticateSchema.content.SchemaMap[k] == ReservedAuthMap[k], E("Reserved fields in Schema must be DO_NOT_SIGN.")); + input.authenticateSchema.content.SchemaMap[k] == ReservedAuthMap[k], E("Reserved fields in Schema must be DO_NOT_SIGN.")); var authSchema : AuthSchemaPlain := input.authenticateSchema.content.SchemaMap + ReservedAuthMap; :- Need(input.encryptedStructure.content.DataMap?, E("Input structure must be a DataMap")); @@ -759,10 +924,55 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst var head :- Header.PartialDeserialize(headerSerialized); var headerAlgorithmSuite :- head.GetAlgorithmSuite(config.materialProviders); + :- Need(ValidString(input.tableName), E("Bad Table Name")); + var canonData :- CanonizeForDecrypt(input.tableName, encRecord, authSchema, head.legend); + + assume {:axiom} input.cmm.Modifies !! {config.materialProviders.History}; + + //= specification/structured-encryption/decrypt-structure.md#retrieve-decryption-materials + //# This operation MUST [calculate the appropriate CMM and encryption context](#create-new-encryption-context-and-cmm). + var encryptionContext := input.encryptionContext.UnwrapOr(map[]); + var cmm := input.cmm; + + //= specification/structured-encryption/decrypt-structure.md#create-new-encryption-context-and-cmm + //# If the version stored in the header is 1, + //# then the input cmm and encryption context MUST be used unchanged. + if head.version == 2 { + //= specification/structured-encryption/decrypt-structure.md#create-new-encryption-context-and-cmm + //# Otherwise, this operation MUST add an [entry](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2) to the encryption context for every + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) + //# [Terminal Data](./structures.md#terminal-data) + //# in the input record, plus the Legend. + var newEncryptionContext :- GetV2EncryptionContext2(canonData.contextFields, encRecord); + if |newEncryptionContext| != 0 { + //= specification/structured-encryption/decrypt-structure.md#create-new-encryption-context-and-cmm + //# An error MUST be returned if any of the entries added to the encryption context in this step + //# have the same key as any entry already in the encryption context. + :- Need(encryptionContext.Keys !! newEncryptionContext.Keys, + E("Internal Error - Structured Encryption encryption context overlaps with Item Encryptor encryption context.")); + encryptionContext := encryptionContext + newEncryptionContext; + assert cmm.Modifies !! {config.materialProviders.History}; + + //= specification/structured-encryption/decrypt-structure.md#create-new-encryption-context-and-cmm + //# Then, this operation MUST create a [Required Encryption Context CMM](https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/required-encryption-context-cmm.md) + //# with the following inputs: + //# - This input [CMM](./ddb-table-encryption-config.md#cmm) as the underlying CMM. + //# - The name of every entry added above. + var cmmR := config.materialProviders.CreateRequiredEncryptionContextCMM( + CMP.CreateRequiredEncryptionContextCMMInput( + underlyingCMM := Some(cmm), + keyring := None, + requiredEncryptionContextKeys := SortedSets.ComputeSetToOrderedSequence2(newEncryptionContext.Keys, ByteLess) + ) + ); + cmm :- cmmR.MapFailure(e => AwsCryptographyMaterialProviders(e)); + } + } + //= specification/structured-encryption/decrypt-structure.md#retrieve-decryption-materials //# This operation MUST obtain a set of decryption materials by calling //# [Decrypt Materials](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cmm-interface.md#decrypt-materials) - //# on the [input CMM](#cmm). + //# on the [CMM](#cmm) calculated above. //= specification/structured-encryption/decrypt-structure.md#retrieve-decryption-materials //# The call to the CMM's Decrypt Materials operation MUST be constructed as follows: @@ -773,13 +983,13 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst // parsed in the header. // - Encrypted Data Keys: The [Encrypted Data Keys parsed from the header](./header.md#encrypted-data-keys). - var matR := input.cmm.DecryptMaterials( + var matR := cmm.DecryptMaterials( CMP.DecryptMaterialsInput ( algorithmSuiteId := headerAlgorithmSuite.id, commitmentPolicy := DBE_COMMITMENT_POLICY, encryptedDataKeys := head.dataKeys, encryptionContext := head.encContext, - reproducedEncryptionContext := input.encryptionContext + reproducedEncryptionContext := Some(encryptionContext) ) ); var matOutput :- matR.MapFailure(e => AwsCryptographyMaterialProviders(e)); @@ -808,9 +1018,6 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst //# The header field value MUST be [verified](header.md#commitment-verification) var ok :- head.verifyCommitment(config.primitives, postCMMAlg, commitKey, headerSerialized); - :- Need(ValidString(input.tableName), E("Bad Table Name")); - var canonData :- CanonizeForDecrypt(input.tableName, encRecord, authSchema, head.legend); - //= specification/structured-encryption/decrypt-structure.md#calculate-signed-and-encrypted-field-lists //= type=implication //# Decryption MUST fail if the length of this list does not equal the @@ -829,10 +1036,11 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst //= specification/structured-encryption/decrypt-structure.md#verify-signatures //# Decryption MUST fail immediately if verification fails. var _ :- footer.validate(config.primitives, mat, head.dataKeys, - canonData.signedFields_c, canonData.encFields_c, map[], canonData.data_c, headerSerialized); + canonData.signedFields_c, canonData.encFields_c, map[], canonData.data_c, headerSerialized); var decryptedItems :- Crypt.Decrypt(config.primitives, postCMMAlg, key, head, canonData.encFields_c, canonData.data_c); - var result : map := map k <- encRecord | true :: k := + var result : map := map k <- encRecord | true + :: k := var c := Paths.SimpleCanon(input.tableName, k); if c in decryptedItems then decryptedItems[c] @@ -862,20 +1070,12 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst //= type=implication //# The output MUST also include a [Parsed Header](#parsed-header) that contains //# data that was serialized into the header included in the output Structured Data. - - //= specification/structured-encryption/decrypt-structure.md#parsed-header - //# This structure MUST contain the following values, - //# representing the deserialized form of the header of the input encrypted structure: - //# - [Algorithm Suite ID](./header.md#format-flavor): The Algorithm Suite ID associated with the Format Flavor on the header. - //# - [Crypto Schema](./header.md#encrypt-legend): The Crypto Schema for each signed Terminal, - //# calculated using the Crypto Legend in the header, the signature scope used for decryption, and the data in the input structure. - //# - [Stored Encryption Context](./header.md#encryption-context): The Encryption Context stored in the header. - //# - [Encrypted Data Keys](./header.md#encrypted-data-keys): The Encrypted Data Keys stored in the header. var parsedHeader := ParsedHeader( - cryptoSchema := canonData.cryptoSchema, - algorithmSuiteId := headerAlgorithmSuite.id.DBE, - encryptedDataKeys := head.dataKeys, - storedEncryptionContext := head.encContext + cryptoSchema := canonData.cryptoSchema, + algorithmSuiteId := headerAlgorithmSuite.id.DBE, + encryptedDataKeys := head.dataKeys, + storedEncryptionContext := head.encContext, + encryptionContext := mat.encryptionContext ); var decryptOutput := DecryptStructureOutput( diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy index 47cd97894..d510fa19d 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy @@ -29,13 +29,17 @@ module StructuredEncryptionHeader { const COMMITMENT_LEN := 32 const PREFIX_LEN := VERSION_LEN + FLAVOR_LEN + MSGID_LEN const UINT8_LIMIT := 256 - const ENCRYPT_AND_SIGN_LEGEND : uint8 := 0x65; - const SIGN_ONLY_LEGEND : uint8 := 0x73; + const ENCRYPT_AND_SIGN_LEGEND : uint8 := 0x65 + const SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT_LEGEND : uint8 := 0x63 + const SIGN_ONLY_LEGEND : uint8 := 0x73 //= specification/structured-encryption/header.md#format-version //= type=implication - //# The Version MUST be `0x01`. + //# The Version MUST be `0x01` or `0x02`. type Version = x : uint8 | ValidVersion(x) witness 1 + predicate method ValidVersion(x : uint8) { + x == 1 || x == 2 + } type Flavor = x : uint8 | ValidFlavor(x) @@ -52,8 +56,20 @@ module StructuredEncryptionHeader { type Legend = x : seq | |x| < UINT16_LIMIT type CMPUtf8Bytes = x : CMP.Utf8Bytes | |x| < UINT16_LIMIT - predicate method ValidVersion(x : uint8) { - x == 1 + predicate method IsVersion2Schema(data : CryptoSchemaMap) + requires CryptoSchemaMapIsFlat(data) + { + exists x <- data :: data[x].content.Action == SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + } + function method VersionFromSchema(data : CryptoSchemaMap) : (ret : Version) + requires CryptoSchemaMapIsFlat(data) + ensures (exists x <- data :: data[x].content.Action == SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT) <==> (ret == 2) + ensures !(exists x <- data :: data[x].content.Action == SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT) <==> (ret == 1) + { + if IsVersion2Schema(data) then + 2 + else + 1 } //= specification/structured-encryption/header.md#format-flavor @@ -69,9 +85,9 @@ module StructuredEncryptionHeader { } predicate method ValidLegendByte(x : uint8) { - x in [ENCRYPT_AND_SIGN_LEGEND, SIGN_ONLY_LEGEND] + x in [ENCRYPT_AND_SIGN_LEGEND, SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT_LEGEND, SIGN_ONLY_LEGEND] } - + predicate method ValidEncryptionContext(x : CMP.EncryptionContext) { && |x| < UINT16_LIMIT && (forall k <- x :: |k| < UINT16_LIMIT && |x[k]| < UINT16_LIMIT) @@ -98,25 +114,25 @@ module StructuredEncryptionHeader { function method {:opaque} serialize() : (ret : Bytes) ensures && PREFIX_LEN <= |ret| - //= specification/structured-encryption/header.md#partial-header - //= type=implication - //# The Partial Header MUST be - // | Length (bytes) | Meaning | - // |---|---| - // | 1 | [Format Version](#format-version) | - // | 1 | [Format Flavor](#format-flavor) | - // | 32 | [Message ID](#message-id) | - // | Variable | [Encrypt Legend](#encrypt-legend) | - // | Variable | [Encryption Context](#encryption-context) | - // | Variable | [Encrypted Data Keys](#encrypted-data-keys) | + //= specification/structured-encryption/header.md#partial-header + //= type=implication + //# The Partial Header MUST be + // | Length (bytes) | Meaning | + // |---|---| + // | 1 | [Format Version](#format-version) | + // | 1 | [Format Flavor](#format-flavor) | + // | 32 | [Message ID](#message-id) | + // | Variable | [Encrypt Legend](#encrypt-legend) | + // | Variable | [Encryption Context](#encryption-context) | + // | Variable | [Encrypted Data Keys](#encrypted-data-keys) | && ret == ( - [version] - + [flavor] - + msgID - + SerializeLegend(legend) - + SerializeContext(encContext) - + SerializeDataKeys(dataKeys) - ) + [version] + + [flavor] + + msgID + + SerializeLegend(legend) + + SerializeContext(encContext) + + SerializeDataKeys(dataKeys) + ) { var context := SerializeContext(encContext); var keys := SerializeDataKeys(dataKeys); @@ -137,14 +153,14 @@ module StructuredEncryptionHeader { requires client.ValidState() ensures client.ValidState() ensures ret.Success? ==> - && COMMITMENT_LEN < |data| - && var storedCommitment := data[|data|-COMMITMENT_LEN..]; - && CalculateHeaderCommitment(client, alg, commitKey, data[..|data|-COMMITMENT_LEN]).Success? - && var calcCommitment := CalculateHeaderCommitment(client, alg, commitKey, data[..|data|-COMMITMENT_LEN]).value; - //= specification/structured-encryption/header.md#commitment-verification - //= type=implication - //# Header commitment comparisons MUST be constant time operations. - && ConstantTimeEquals(storedCommitment, calcCommitment) + && COMMITMENT_LEN < |data| + && var storedCommitment := data[|data|-COMMITMENT_LEN..]; + && CalculateHeaderCommitment(client, alg, commitKey, data[..|data|-COMMITMENT_LEN]).Success? + && var calcCommitment := CalculateHeaderCommitment(client, alg, commitKey, data[..|data|-COMMITMENT_LEN]).value; + //= specification/structured-encryption/header.md#commitment-verification + //= type=implication + //# Header commitment comparisons MUST be constant time operations. + && ConstantTimeEquals(storedCommitment, calcCommitment) { :- Need(COMMITMENT_LEN < |data|, E("Serialized header too short")); var storedCommitment := data[|data|-COMMITMENT_LEN..]; @@ -155,7 +171,7 @@ module StructuredEncryptionHeader { method GetAlgorithmSuite(matProv: MaterialProviders.MaterialProvidersClient) returns (ret: Result) ensures ret.Success? ==> - ValidSuite(ret.value) + ValidSuite(ret.value) { var algorithmSuiteR := matProv.GetAlgorithmSuiteInfo([DbeAlgorithmFamily, flavor as uint8]); if algorithmSuiteR.Success? { @@ -178,18 +194,18 @@ module StructuredEncryptionHeader { requires ValidSuite(alg) ensures ret.Success? ==> - && PREFIX_LEN <= |ret.value| - && CalculateHeaderCommitment(client, alg, commitKey, ret.value[..|ret.value|-COMMITMENT_LEN]).Success? - && ret.value[|ret.value|-COMMITMENT_LEN..] == CalculateHeaderCommitment(client, alg, commitKey, ret.value[..|ret.value|-COMMITMENT_LEN]).value - - //= specification/structured-encryption/header.md#header-value-1 - //= type=implication - //# The value of the header MUST be - // | Length (bytes) | Meaning | - // |---|---| - // | Variable | [Partial Header](#partial-header) | - // | 32 | [Header Commitment](#header-commitment) | - && ret.value == PartialHeader.serialize() + CalculateHeaderCommitment(client, alg, commitKey, ret.value[..|ret.value|-COMMITMENT_LEN]).value + && PREFIX_LEN <= |ret.value| + && CalculateHeaderCommitment(client, alg, commitKey, ret.value[..|ret.value|-COMMITMENT_LEN]).Success? + && ret.value[|ret.value|-COMMITMENT_LEN..] == CalculateHeaderCommitment(client, alg, commitKey, ret.value[..|ret.value|-COMMITMENT_LEN]).value + + //= specification/structured-encryption/header.md#header-value-1 + //= type=implication + //# The value of the header MUST be + // | Length (bytes) | Meaning | + // |---|---| + // | Variable | [Partial Header](#partial-header) | + // | 32 | [Header Commitment](#header-commitment) | + && ret.value == PartialHeader.serialize() + CalculateHeaderCommitment(client, alg, commitKey, ret.value[..|ret.value|-COMMITMENT_LEN]).value requires client.ValidState() ensures client.ValidState() @@ -201,12 +217,22 @@ module StructuredEncryptionHeader { // config to PartialHeader function method Create( - tableName : string, - schema : CryptoSchema, - msgID : MessageID, - mat : CMP.EncryptionMaterials - ) + tableName : string, + schema : CryptoSchema, + msgID : MessageID, + mat : CMP.EncryptionMaterials + ) : (ret : Result) + + //= specification/structured-encryption/header.md#format-version + //= type=implication + //# If any [Crypto Action](./structures.md#crypto-action) is configured as + //# [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) + //# the Version MUST be 0x02; otherwise, Version MUST be 0x01. + ensures ret.Success? ==> + && schema.content.SchemaMap? + && CryptoSchemaMapIsFlat(schema.content.SchemaMap) + && ret.value.version == VersionFromSchema(schema.content.SchemaMap) { :- Need(ValidString(tableName), E("Invalid table name.")); :- Need(ValidEncryptionContext(mat.encryptionContext), E("Invalid Encryption Context")); @@ -229,34 +255,34 @@ module StructuredEncryptionHeader { // It is difficult for dafny to prove ValidEncryptionContext here, so perform a runtime check instead :- Need(ValidEncryptionContext(storedEC), E("Invalid Encryption Context")); Success(PartialHeader( - version := 1, - flavor := mat.algorithmSuite.binaryId[1], - msgID := msgID, - legend := legend, - encContext := storedEC, - dataKeys := mat.encryptedDataKeys - )) - } - + version := VersionFromSchema(schema.content.SchemaMap), + flavor := mat.algorithmSuite.binaryId[1], + msgID := msgID, + legend := legend, + encContext := storedEC, + dataKeys := mat.encryptedDataKeys + )) + } + // bytes to PartialHeader, i.e. does not look at commitment -- Deserialize does that function method {:opaque} PartialDeserialize(data : Bytes) : (ret : Result) ensures ret.Success? ==> - && PREFIX_LEN <= |data| - && var v := ret.value; - && v.version == data[0] - && ValidVersion(v.version) - && v.flavor == data[1] - && ValidFlavor(v.flavor) - && v.msgID == data[VERSION_LEN+FLAVOR_LEN..PREFIX_LEN] - && var legendData := data[PREFIX_LEN..]; - && GetLegend(legendData).Success? - && var legendAndLen := GetLegend(legendData).value; - && v.legend == legendAndLen.0 - && var contextData := legendData[legendAndLen.1..]; - && GetContext(contextData).Success? - && var contextAndLen := GetContext(contextData).value; - && v.encContext == contextAndLen.0 + && PREFIX_LEN <= |data| + && var v := ret.value; + && v.version == data[0] + && ValidVersion(v.version) + && v.flavor == data[1] + && ValidFlavor(v.flavor) + && v.msgID == data[VERSION_LEN+FLAVOR_LEN..PREFIX_LEN] + && var legendData := data[PREFIX_LEN..]; + && GetLegend(legendData).Success? + && var legendAndLen := GetLegend(legendData).value; + && v.legend == legendAndLen.0 + && var contextData := legendData[legendAndLen.1..]; + && GetContext(contextData).Success? + && var contextAndLen := GetContext(contextData).value; + && v.encContext == contextAndLen.0 { :- Need(PREFIX_LEN <= |data|, E("Serialized PartialHeader too short.")); var version := data[0]; @@ -282,13 +308,13 @@ module StructuredEncryptionHeader { :- Need(|trailingData| <= COMMITMENT_LEN, E("Invalid header serialization: unexpected bytes.")); assert |trailingData| == COMMITMENT_LEN; Success(PartialHeader( - version := version, - flavor := flavor, - msgID := msgID, - legend := legend, - encContext := encContext, - dataKeys := dataKeys - )) + version := version, + flavor := flavor, + msgID := msgID, + legend := legend, + encContext := encContext, + dataKeys := dataKeys + )) } // calculate Hmac384 for header commitment @@ -300,27 +326,27 @@ module StructuredEncryptionHeader { ) : (ret : Result) requires ValidSuite(alg) ensures ret.Success? ==> - && |ret.value| == COMMITMENT_LEN - //= specification/structured-encryption/header.md#commitment-calculation - //= type=implication - //# The Header Commitment MUST be calculated as a the first 32 bytes of an HmacSha384, - //# with the serialized partial header as the message, and the Commit Key as the key. - && var input := Prim.HMacInput( - digestAlgorithm := alg.commitment.HKDF.hmac, - key := commitKey, - message := data - ); - && client.HMac(input).Success? - && |client.HMac(input).value| >= 32 - && ret.value == client.HMac(input).value[0..32] + && |ret.value| == COMMITMENT_LEN + //= specification/structured-encryption/header.md#commitment-calculation + //= type=implication + //# The Header Commitment MUST be calculated as a the first 32 bytes of an HmacSha384, + //# with the serialized partial header as the message, and the Commit Key as the key. + && var input := Prim.HMacInput( + digestAlgorithm := alg.commitment.HKDF.hmac, + key := commitKey, + message := data + ); + && client.HMac(input).Success? + && |client.HMac(input).value| >= 32 + && ret.value == client.HMac(input).value[0..32] requires client.ValidState() ensures client.ValidState() { var input := Prim.HMacInput ( - digestAlgorithm := alg.commitment.HKDF.hmac, - key := commitKey, - message := data - ); + digestAlgorithm := alg.commitment.HKDF.hmac, + key := commitKey, + message := data + ); var outputR := client.HMac(input); var output :- outputR.MapFailure(e => AwsCryptographyPrimitives(e)); if |output| < COMMITMENT_LEN then @@ -349,24 +375,24 @@ module StructuredEncryptionHeader { requires schema.content.SchemaMap? requires CryptoSchemaMapIsFlat(schema.content.SchemaMap) ensures ret.Success? ==> - //= specification/structured-encryption/header.md#encrypt-legend-bytes - //= type=implication - //# The length of this serialized value (in bytes) MUST equal the number of authenticated fields indicated - //# by the caller's [Authenticate Schema](./structures.md#authenticate-schema). - && |ret.value| == CountAuthAttrs(schema.content.SchemaMap) + //= specification/structured-encryption/header.md#encrypt-legend-bytes + //= type=implication + //# The length of this serialized value (in bytes) MUST equal the number of authenticated fields indicated + //# by the caller's [Authenticate Schema](./structures.md#authenticate-schema). + && |ret.value| == CountAuthAttrs(schema.content.SchemaMap) { var data := schema.content.SchemaMap; :- Need(forall k <- data :: ValidString(k), E("bad attribute name")); var authSchema: map := ( - var rawSchema := RestrictAuthAttrs(data); - // Ensure we get the expected number of auth attributes - LemmaRestrictAuthAttrsIdempotent(data); - assert CountAuthAttrs(data) == |rawSchema|; - // Can't use `k as GoodString` for some reason; instead assert validity and let inference handle the rest - assert forall k <- rawSchema :: ValidString(k); - rawSchema - ); + var rawSchema := RestrictAuthAttrs(data); + // Ensure we get the expected number of auth attributes + LemmaRestrictAuthAttrsIdempotent(data); + assert CountAuthAttrs(data) == |rawSchema|; + // Can't use `k as GoodString` for some reason; instead assert validity and let inference handle the rest + assert forall k <- rawSchema :: ValidString(k); + rawSchema + ); assert CountAuthAttrs(data) == |authSchema|; //= specification/structured-encryption/header.md#encrypt-legend-bytes @@ -394,14 +420,14 @@ module StructuredEncryptionHeader { // Create a Legend for the given attrs of the Schema function method {:tailrecursion} MakeLegend2( - attrs : seq, - data : map, - serialized : Legend := EmptyLegend - ) + attrs : seq, + data : map, + serialized : Legend := EmptyLegend + ) : (ret : Result) requires forall k <- attrs :: k in data - requires forall k <- data.Keys :: data[k].content.Action?; - requires forall k <- data.Keys :: IsAuthAttr(data[k].content.Action); + requires forall k <- data.Keys :: data[k].content.Action? + requires forall k <- data.Keys :: IsAuthAttr(data[k].content.Action) requires |attrs| + |serialized| == |data| ensures ret.Success? ==> |ret.value| == |data| { @@ -427,14 +453,19 @@ module StructuredEncryptionHeader { // - `0x73` (`s` in UTF-8, for "Sign Only") means that a particular field was not encrypted, // but still included in the signature calculation. // This indicates that this field will not be attempted to be decrypted during decryption. - // - no entry if the attribute is not signed + // - `0x63` (`c` in UTF-8, for "Context") means that a particular field was not encrypted, + // but still included in the signature calculation, + // as well as being included in the encryption context. + // This indicates that this field MUST NOT be attempted to be decrypted during decryption. // - no entry if the attribute is not signed ensures match (x) { - case ENCRYPT_AND_SIGN => ret == ENCRYPT_AND_SIGN_LEGEND - case SIGN_ONLY => ret == SIGN_ONLY_LEGEND - } + case ENCRYPT_AND_SIGN => ret == ENCRYPT_AND_SIGN_LEGEND + case SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT => ret == SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT_LEGEND + case SIGN_ONLY => ret == SIGN_ONLY_LEGEND + } { match (x) { case ENCRYPT_AND_SIGN => ENCRYPT_AND_SIGN_LEGEND + case SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT => SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT_LEGEND case SIGN_ONLY => SIGN_ONLY_LEGEND } } @@ -491,9 +522,9 @@ module StructuredEncryptionHeader { function method GetLegend(data : Bytes) : (ret : Result<(Legend, nat), Error>) ensures ret.Success? ==> - && ret.value.1 <= |data| - && ret.value.1 == |ret.value.0| + 2 - && ret.value.0 == data[2..ret.value.1] + && ret.value.1 <= |data| + && ret.value.1 == |ret.value.0| + 2 + && ret.value.0 == data[2..ret.value.1] { :- Need(2 <= |data|, E("Unexpected end of header data.")); var len := SeqToUInt16(data[0..2]); @@ -507,11 +538,11 @@ module StructuredEncryptionHeader { function method GetContext(data : Bytes) : (ret : Result<(CMPEncryptionContext, nat), Error>) ensures ret.Success? ==> - && ret.value.1 <= |data| + && ret.value.1 <= |data| ensures ( - && 2 <= |data| - && GetContext2(SeqToUInt16(data[0..2]) as nat, data, data[2..], (map[], 2)).Success? - ) ==> ret.Success? + && 2 <= |data| + && GetContext2(SeqToUInt16(data[0..2]) as nat, data, data[2..], (map[], 2)).Success? + ) ==> ret.Success? { :- Need(2 <= |data|, E("Unexpected end of header data.")); var count := SeqToUInt16(data[0..2]) as nat; @@ -523,17 +554,17 @@ module StructuredEncryptionHeader { function method GetOneKVPair(data : Bytes) : (ret : Result<(CMPUtf8Bytes, CMPUtf8Bytes, nat), Error>) ensures ret.Success? ==> - && ret.value.2 <= |data| - && SerializeOneKVPair(ret.value.0, ret.value.1) == data[..ret.value.2] + && ret.value.2 <= |data| + && SerializeOneKVPair(ret.value.0, ret.value.1) == data[..ret.value.2] ensures ( - && 2 <= |data| - && var keyLen := SeqToUInt16(data[0..2]) as nat; - && keyLen + 4 <= |data| - && UTF8.ValidUTF8Seq(data[2..keyLen+2]) - && var valueLen := SeqToUInt16(data[keyLen+2..keyLen+4]) as nat; - && keyLen + valueLen + 4 <= |data| - && UTF8.ValidUTF8Seq(data[keyLen+4..keyLen + valueLen + 4]) - ) <==> ret.Success? && SerializeOneKVPair(ret.value.0, ret.value.1) == data[..ret.value.2] + && 2 <= |data| + && var keyLen := SeqToUInt16(data[0..2]) as nat; + && keyLen + 4 <= |data| + && UTF8.ValidUTF8Seq(data[2..keyLen+2]) + && var valueLen := SeqToUInt16(data[keyLen+2..keyLen+4]) as nat; + && keyLen + valueLen + 4 <= |data| + && UTF8.ValidUTF8Seq(data[keyLen+4..keyLen + valueLen + 4]) + ) <==> ret.Success? && SerializeOneKVPair(ret.value.0, ret.value.1) == data[..ret.value.2] { :- Need(2 <= |data|, E("Unexpected end of header data.")); var keyLen := SeqToUInt16(data[0..2]) as nat; @@ -572,9 +603,9 @@ module StructuredEncryptionHeader { requires deserialized.1 <= |origData| requires deserialized.1 + |data| == |origData| requires data == origData[deserialized.1..] - ensures ret.Success? ==> - && ret.value.1 <= |origData| - && (count > 0 ==> GetOneKVPair(data).Success?) + ensures ret.Success? ==> + && ret.value.1 <= |origData| + && (count > 0 ==> GetOneKVPair(data).Success?) { if count == 0 then Success(deserialized) @@ -650,29 +681,29 @@ module StructuredEncryptionHeader { && var provInfoSize := ToUInt16(|k.keyProviderInfo|).value; && var cipherSize := ToUInt16(|k.ciphertext|).value; && ret == ( - UInt16ToSeq(provIdSize) - + k.keyProviderId - + UInt16ToSeq(provInfoSize) - + k.keyProviderInfo - + UInt16ToSeq(cipherSize) - + k.ciphertext - ) - { - UInt16ToSeq(|k.keyProviderId| as uint16) + UInt16ToSeq(provIdSize) + + k.keyProviderId + + UInt16ToSeq(provInfoSize) + + k.keyProviderInfo + + UInt16ToSeq(cipherSize) + + k.ciphertext + ) + { + UInt16ToSeq(|k.keyProviderId| as uint16) + k.keyProviderId + UInt16ToSeq(|k.keyProviderInfo| as uint16) + k.keyProviderInfo + UInt16ToSeq(|k.ciphertext| as uint16) + k.ciphertext } - + // Bytes to Data Key function method {:vcs_split_on_every_assert} GetOneDataKey(data : Bytes) : (ret : Result<(CMPEncryptedDataKey, nat), Error>) ensures ret.Success? ==> - && ret.value.1 <= |data| - && |SerializeOneDataKey(ret.value.0)| == ret.value.1 - && SerializeOneDataKey(ret.value.0) == data[0..ret.value.1] + && ret.value.1 <= |data| + && |SerializeOneDataKey(ret.value.0)| == ret.value.1 + && SerializeOneDataKey(ret.value.0) == data[0..ret.value.1] { :- Need(2 < |data|, E("Unexpected end of header data.")); var provIdSize := SeqToUInt16(data[0..2]) as nat; @@ -711,17 +742,17 @@ module StructuredEncryptionHeader { // Data Key List to Bytes function method SerializeDataKeys(x : CMPEncryptedDataKeyList) : (ret : Bytes) - //= specification/structured-encryption/header.md#encrypted-data-keys - //= type=implication - //# The Encrypted Data Keys MUST be serialized as follows - // | Field | Length (bytes) | Interpreted as | - // | ----- | -------------- | -------------- | - // | Encrypted Data Key Count | 1 | big endian UInt16 | - // | [Encrypted Data Key Entries | Variable. Determined by the count and length of each key-value pair. | Encrypted Data Key Entries | - ensures - && 1 <= |ret| - && ret[0] as nat == |x| - && ret == [|x| as uint8] + SerializeDataKeys2(x) + //= specification/structured-encryption/header.md#encrypted-data-keys + //= type=implication + //# The Encrypted Data Keys MUST be serialized as follows + // | Field | Length (bytes) | Interpreted as | + // | ----- | -------------- | -------------- | + // | Encrypted Data Key Count | 1 | big endian UInt16 | + // | [Encrypted Data Key Entries | Variable. Determined by the count and length of each key-value pair. | Encrypted Data Key Entries | + ensures + && 1 <= |ret| + && ret[0] as nat == |x| + && ret == [|x| as uint8] + SerializeDataKeys2(x) { var body := SerializeDataKeys2(x); [|x| as uint8] + body @@ -741,11 +772,11 @@ module StructuredEncryptionHeader { function method GetDataKeys(data : Bytes) : (ret : Result<(CMPEncryptedDataKeyList, nat), Error>) ensures ret.Success? ==> - && ret.value.1 <= |data| - && 1 <= |data| - && 1 <= ret.value.1 - && |ret.value.0| == data[0] as nat - && GetDataKeys2(|ret.value.0|, |ret.value.0|, data, data[1..], ([], 1)).Success? + && ret.value.1 <= |data| + && 1 <= |data| + && 1 <= ret.value.1 + && |ret.value.0| == data[0] as nat + && GetDataKeys2(|ret.value.0|, |ret.value.0|, data, data[1..], ([], 1)).Success? { :- Need(1 <= |data|, E("Unexpected end of header data.")); var count := data[0] as nat; @@ -769,23 +800,23 @@ module StructuredEncryptionHeader { requires deserialized.1 + |data| == |origData| requires origCount == count + |deserialized.0| ensures ret.Success? ==> - && ret.value.1 <= |origData| - && ret.value.1 >= deserialized.1 - && (count > 0 ==> GetOneDataKey(data).Success?) - && |ret.value.0| == origCount + && ret.value.1 <= |origData| + && ret.value.1 >= deserialized.1 + && (count > 0 ==> GetOneDataKey(data).Success?) + && |ret.value.0| == origCount { if count == 0 then Success(deserialized) else - if |deserialized.0| >= 255 then - Failure(E("Too Many Data Keys")) - else - var edk :- GetOneDataKey(data); - assert SerializeOneDataKey(edk.0) == data[..edk.1]; - GetDataKeys2(count-1, origCount, origData, data[edk.1..], (deserialized.0 + [edk.0], deserialized.1+edk.1)) + if |deserialized.0| >= 255 then + Failure(E("Too Many Data Keys")) + else + var edk :- GetOneDataKey(data); + assert SerializeOneDataKey(edk.0) == data[..edk.1]; + GetDataKeys2(count-1, origCount, origData, data[edk.1..], (deserialized.0 + [edk.0], deserialized.1+edk.1)) } -// End code, begin proofs + // End code, begin proofs // mapping with no filter does not change map size lemma MapKeepsCount(m : map, f : (GoodString) -> Z) @@ -801,9 +832,9 @@ module StructuredEncryptionHeader { lemma SerializeLegendRoundTrip(x : Legend) ensures GetLegend(SerializeLegend(x)).Success? ensures var ret := GetLegend(SerializeLegend(x)).value; - && ret.0 == x - && ret.1 == |x| + 2 - && ret.1 == |SerializeLegend(x)| + && ret.0 == x + && ret.1 == |x| + 2 + && ret.1 == |SerializeLegend(x)| {} // GetLegend ==> SerializeLegend @@ -829,7 +860,7 @@ module StructuredEncryptionHeader { assert data[keyLen+4..keyLen + valueLen + 4] == value; assert UTF8.ValidUTF8Seq(data[keyLen+4..keyLen + valueLen + 4]); } - + // GetOneKVPair ==> SerializeOneKVPair lemma GetOneKVPairRoundTrip(data : Bytes) requires GetOneKVPair(data).Success? @@ -845,21 +876,21 @@ module StructuredEncryptionHeader { && GetOneDataKey(data).Success? && GetOneDataKey(data).value.0 == k && GetOneDataKey(data).value.1 == |data| - { - var data := SerializeOneDataKey(k); - assert 2 <= |data|; - var provIdSize := SeqToUInt16(data[0..2]) as nat; - assert provIdSize + 2 < |data|; - var provId := data[2..2+provIdSize]; - assert provId == k.keyProviderId; - - var part1Size := 2 + provIdSize; - assert part1Size+2 <= |data|; - var provInfoSize := SeqToUInt16(data[part1Size..part1Size+2]) as nat; - assert part1Size + provInfoSize + 2 < |data|; - var provInfo := data[part1Size+2..part1Size+2+provInfoSize]; - assert provInfo == k.keyProviderInfo; - } + { + var data := SerializeOneDataKey(k); + assert 2 <= |data|; + var provIdSize := SeqToUInt16(data[0..2]) as nat; + assert provIdSize + 2 < |data|; + var provId := data[2..2+provIdSize]; + assert provId == k.keyProviderId; + + var part1Size := 2 + provIdSize; + assert part1Size+2 <= |data|; + var provInfoSize := SeqToUInt16(data[part1Size..part1Size+2]) as nat; + assert part1Size + provInfoSize + 2 < |data|; + var provInfo := data[part1Size+2..part1Size+2+provInfoSize]; + assert provInfo == k.keyProviderInfo; + } // GetOneDataKey ==> SerializeOneDataKey lemma GetOneDataKeyRoundTrip(data : Bytes) @@ -877,19 +908,19 @@ module StructuredEncryptionHeader { ensures && GetOneKVPair(y).Success? && GetOneKVPair(x).value == GetOneKVPair(y).value - { - assert 2 <= |y|; - var keyLen := SeqToUInt16(y[0..2]) as nat; - assert(keyLen + 4 <= |y|); - var key := y[2..keyLen+2]; - assert x[2..keyLen+2] == y[2..keyLen+2]; - assert(UTF8.ValidUTF8Seq(key)); - var valueLen := SeqToUInt16(y[keyLen+2..keyLen+4]) as nat; - var kvLen := 2 + keyLen + 2 + valueLen; - assert kvLen <= |y|; - var value := y[keyLen+4..kvLen]; - assert keyLen+4 <= kvLen; - assert x[keyLen+4..kvLen] == y[keyLen+4..kvLen]; - assert UTF8.ValidUTF8Seq(value); - } + { + assert 2 <= |y|; + var keyLen := SeqToUInt16(y[0..2]) as nat; + assert(keyLen + 4 <= |y|); + var key := y[2..keyLen+2]; + assert x[2..keyLen+2] == y[2..keyLen+2]; + assert(UTF8.ValidUTF8Seq(key)); + var valueLen := SeqToUInt16(y[keyLen+2..keyLen+4]) as nat; + var kvLen := 2 + keyLen + 2 + valueLen; + assert kvLen <= |y|; + var value := y[keyLen+4..kvLen]; + assert keyLen+4 <= kvLen; + assert x[keyLen+4..kvLen] == y[keyLen+4..kvLen]; + assert UTF8.ValidUTF8Seq(value); + } } diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Index.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Index.dfy index 9f0f9df97..58136cdee 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Index.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Index.dfy @@ -18,7 +18,8 @@ module } method StructuredEncryption(config: StructuredEncryptionConfig) - returns (res: Result) + returns (res: Result) + ensures res.Success? ==> res.value is StructuredEncryptionClient { var maybePrimitives := Primitives.AtomicPrimitives(); var primitives :- maybePrimitives.MapFailure(e => AwsCryptographyPrimitives(e)); diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy index e3c7bfdf5..d7e475eea 100644 --- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy +++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy @@ -12,6 +12,8 @@ module StructuredEncryptionUtil { import CMP = AwsCryptographyMaterialProvidersTypes import CSE = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes import AlgorithmSuites + import SortedSets + import Base64 // all attributes with this prefix reserved for the implementation const ReservedPrefix := "aws_dbe_" @@ -19,7 +21,23 @@ module StructuredEncryptionUtil { const HeaderField := ReservedPrefix + "head" const FooterField := ReservedPrefix + "foot" const ReservedCryptoContextPrefixString := "aws-crypto-" - const ReservedCryptoContextPrefixUTF8 := UTF8.EncodeAscii("aws-crypto-") + const ReservedCryptoContextPrefixUTF8 := UTF8.EncodeAscii(ReservedCryptoContextPrefixString) + + const ATTR_PREFIX := ReservedCryptoContextPrefixString + "attr." + const EC_ATTR_PREFIX : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(ATTR_PREFIX) + const LEGEND := ReservedCryptoContextPrefixString + "legend" + const LEGEND_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(LEGEND) + const LEGEND_STRING : char := 'S' + const LEGEND_NUMBER : char := 'N' + const LEGEND_LITERAL : char := 'L' + const LEGEND_BINARY : char := 'B' + + const NULL_STR : string := "null" + const NULL_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(NULL_STR) + const TRUE_STR : string := "true" + const TRUE_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(TRUE_STR) + const FALSE_STR : string := "false" + const FALSE_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(FALSE_STR) //= specification/structured-encryption/encrypt-structure.md#header-field //= type=implication @@ -59,11 +77,13 @@ module StructuredEncryptionUtil { const DoSign := CSE.AuthenticateSchema(content := CSE.AuthenticateSchemaContent.Action(CSE.AuthenticateAction.SIGN), attributes := None) const EncryptAndSign := - CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.ENCRYPT_AND_SIGN), attributes := None); + CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.ENCRYPT_AND_SIGN), attributes := None) + const ContextAndSign := + CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT), attributes := None) const SignOnly := - CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.SIGN_ONLY), attributes := None); + CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.SIGN_ONLY), attributes := None) const DoNothing := - CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.DO_NOTHING), attributes := None); + CSE.CryptoSchema(content := CSE.CryptoSchemaContent.Action(CSE.CryptoAction.DO_NOTHING), attributes := None) type Key = x : seq | |x| == KeySize witness * type Nonce = x : seq | |x| == NonceSize witness * @@ -128,6 +148,7 @@ module StructuredEncryptionUtil { { forall k <- data :: data[k].content.Action? } + type FlatSchemaMap = x : CryptoSchemaMap | CryptoSchemaMapIsFlat(x) // Schema must contain only Actions function method AuthSchemaIsFlat(data : AuthenticateSchemaMap) : (ret : bool) @@ -142,11 +163,12 @@ module StructuredEncryptionUtil { { forall k <- data :: data[k].content.Terminal? } + type FlatDataMap = x : StructuredDataMap | DataMapIsFlat(x) // attribute is "authorized", a.k.a. included in the signature predicate method IsAuthAttr(x : CryptoAction) { - x.ENCRYPT_AND_SIGN? || x.SIGN_ONLY? + x.ENCRYPT_AND_SIGN? || x.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT? || x.SIGN_ONLY? } // wrap a value in a StructuredData @@ -181,4 +203,90 @@ module StructuredEncryptionUtil { { x < y } + + //= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#type-id + //= type=implication + //# Type ID indicates what type a DynamoDB Attribute Value MUST + //# be serialized and deserialized as. + //# | Attribute Value Data Type | Terminal Type ID | + //# | ------------------------- | ---------------- | + //# | Null (NULL) | 0x0000 | + //# | String (S) | 0x0001 | + //# | Number (N) | 0x0002 | + //# | Binary (B) | 0xFFFF | + //# | Boolean (BOOL) | 0x0004 | + //# | String Set (SS) | 0x0101 | + //# | Number Set (NS) | 0x0102 | + //# | Binary Set (BS) | 0x01FF | + //# | Map (M) | 0x0200 | + //# | List (L) | 0x0300 | + const TERM_T : uint8 := 0x00 + const SET_T : uint8 := 0x01 + const MAP_T : uint8 := 0x02 + const LIST_T : uint8 := 0x03 + const NULL_T : uint8 := 0x00 + const STRING_T : uint8 := 0x01 + const NUMBER_T : uint8 := 0x02 + const BINARY_T : uint8 := 0xFF + const BOOLEAN_T : uint8 := 0x04 + + const NULL : TerminalTypeId := [TERM_T, NULL_T] + const STRING : TerminalTypeId := [TERM_T, STRING_T] + const NUMBER : TerminalTypeId := [TERM_T, NUMBER_T] + const BINARY : TerminalTypeId := [0xFF, 0xFF] + const BOOLEAN : TerminalTypeId := [TERM_T, BOOLEAN_T] + const STRING_SET : TerminalTypeId := [SET_T, STRING_T] + const NUMBER_SET : TerminalTypeId := [SET_T, NUMBER_T] + const BINARY_SET : TerminalTypeId := [SET_T, BINARY_T] + const MAP : TerminalTypeId := [MAP_T, NULL_T] + const LIST : TerminalTypeId := [LIST_T, NULL_T] + + method EcAsString(ec : CMP.EncryptionContext) returns (output : map) + { + var keys : seq := SortedSets.ComputeSetToOrderedSequence2(ec.Keys, ByteLess); + var ret : map := map[]; + for i := 0 to |keys| { + var key :- expect UTF8.Decode(keys[i]); + var value :- expect UTF8.Decode(ec[keys[i]]); + ret := ret[key := value]; + } + return ret; + } + + method PrintEncryptionContext(ec : CMP.EncryptionContext, name : string) + { + var keys : seq := SortedSets.ComputeSetToOrderedSequence2(ec.Keys, ByteLess); + print name, " := {\n"; + for i := 0 to |keys| { + var key :- expect UTF8.Decode(keys[i]); + var value :- expect UTF8.Decode(ec[keys[i]]); + print " ", key, " := ", value, "\n"; + } + print "}\n"; + } + + function method EncodeTerminal(t : StructuredDataTerminal) : (ret : UTF8.ValidUTF8Bytes) + //= specification/dynamodb-encryption-client/encrypt-item.md#base-context-value-version-1 + //= type=implication + //# The value MUST be the UTF8 Encoding of the + //# [Base 64 encoded](https://www.rfc-editor.org/rfc/rfc4648), + //# of the concatenation of the bytes `typeID + serializedValue` + //# where `typeId` is the attribute's [type ID](./ddb-attribute-serialization.md#type-id) + //# and `serializedValue` is the attribute's value serialized according to + //# [Attribute Value Serialization](./ddb-attribute-serialization.md#attribute-value-serialization). + ensures ret == UTF8.EncodeAscii(Base64.Encode(t.typeId + t.value)) + { + UTF8.EncodeAscii(Base64.Encode(t.typeId + t.value)) + } + + function method DecodeTerminal(t : UTF8.ValidUTF8Bytes) : (ret : Result) + { + var utf8DecodedVal :- UTF8.Decode(t); + var base64DecodedVal :- Base64.Decode(utf8DecodedVal); + :- Need(|base64DecodedVal| >= 2, "Invalid serialization of DDB Attribute in encryption context."); + var typeId := base64DecodedVal[..2]; + var serializedValue := base64DecodedVal[2..]; + Success(StructuredDataTerminal(value := serializedValue, typeId := typeId)) + } + } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionSignAndIncludeInEncryptionContext.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionSignAndIncludeInEncryptionContext.java new file mode 100644 index 000000000..93185a3bd --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEncryptionSignAndIncludeInEncryptionContext.java @@ -0,0 +1,13 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.BeanTableSchemaAttributeTag; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@BeanTableSchemaAttributeTag(EncryptionAttributeTags.class) +public @interface DynamoDbEncryptionSignAndIncludeInEncryptionContext { +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java index 668faad67..125d33f33 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryption.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ArrayList; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -24,6 +25,7 @@ import static software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DoNothingTag.CUSTOM_DDB_ENCRYPTION_DO_NOTHING_PREFIX; import static software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.SignOnlyTag.CUSTOM_DDB_ENCRYPTION_SIGN_ONLY_PREFIX; +import static software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.SignAndIncludeInEncryptionContextTag.CUSTOM_DDB_ENCRYPTION_SIGN_AND_INCLUDE_PREFIX; public class DynamoDbEnhancedClientEncryption { public static DynamoDbEncryptionInterceptor CreateDynamoDbEncryptionInterceptor( @@ -39,6 +41,7 @@ public static DynamoDbEncryptionInterceptor CreateDynamoDbEncryptionInterceptor( .build(); } + // return all attribute names that are keys in any index private static Set attributeNamesUsedInIndices( final TableMetadata tableMetadata ) { @@ -58,41 +61,95 @@ private static Set attributeNamesUsedInIndices( return allIndexAttributes; } - private static DynamoDbTableEncryptionConfig getTableConfig( - final DynamoDbEnhancedTableEncryptionConfig configWithSchema, - final String tableName + // return attributes used in the primary table index + private static Set attributeNamesUsedInPrimaryKey( + final TableMetadata tableMetadata ) { - Map actions = new HashMap<>(); + Set keyAttributes = new HashSet<>(); + tableMetadata.primaryKeys().stream() + .forEach(keyAttributes::add); + return keyAttributes; + } - TableSchema topTableSchema = configWithSchema.schemaOnEncrypt(); - Set signOnlyAttributes = getSignOnlyAttributes(topTableSchema); - Set doNothingAttributes = getDoNothingAttributes(topTableSchema); - Set keyAttributes = attributeNamesUsedInIndices(topTableSchema.tableMetadata()); + private static void throwUsageError(String tableName, String attributeName, String usage, String usage2) + { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Attribute %s of table %s is used as both %s and %s.", + attributeName, tableName, usage, usage2)) + .build(); - if (!Collections.disjoint(keyAttributes, doNothingAttributes)) { - throw DynamoDbEncryptionException.builder() - .message(String.format( - "Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: %s", - tableName)) - .build(); - } else if (!Collections.disjoint(signOnlyAttributes, doNothingAttributes)) { - throw DynamoDbEncryptionException.builder() - .message(String.format( - "Cannot use @DynamoDbEncryptionDoNothing and @DynamoDbEncryptionSignOnly on same attribute. Found on Table Name: %s", - tableName)) - .build(); + } + + // Any given attribute MUST have one and only one Cryptographic Action. + // i.e: It can't be both SignOnly and DoNothing. + // validateAttributeUsage throws an error if + // the given attribute is marked with `usage` and another Cryptographic Action. + // For example, for a SignOnly, signOnly will be empty, and an error must be reported + // if the attribute exists in any of the other sets. + private static void validateAttributeUsage( + String tableName, + String attributeName, + String usage, + Optional> signOnly, + Optional> signAndInclude, + Optional> doNothing + ) + { + if (signOnly.isPresent()) { + if (signOnly.get().contains(attributeName)) { + throwUsageError(tableName, attributeName, usage, "@DynamoDbEncryptionSignOnly"); + } + } + if (signAndInclude.isPresent()) { + if (signAndInclude.get().contains(attributeName)) { + throwUsageError(tableName, attributeName, usage, "@DynamoDbEncryptionSignAndIncludeInEncryptionContext"); + } } + if (doNothing.isPresent()) { + if (doNothing.get().contains(attributeName)) { + throwUsageError(tableName, attributeName, usage, "@DynamoDbEncryptionDoNothing"); + } + } + } + // return a map containing all top level attributes in the schema + // If an attribute is used in an index, it is SignOnly + // Else if an attribute is tagged with a single action, it gets that action + // Else if an attribute is tagged with a multiple actions, an error is thrown + // Else if an attribute is not tagged, it is to be encrypted + private static Map getActionsFromSchema(String tableName, TableSchema topTableSchema) + { + Set signOnlyAttributes = getSignOnlyAttributes(topTableSchema); + Set signAndIncludeAttributes = getSignAndIncludeInEncryptionContextAttributes(topTableSchema); + Set doNothingAttributes = getDoNothingAttributes(topTableSchema); + Set keyAttributes = attributeNamesUsedInIndices(topTableSchema.tableMetadata()); + Set tableKeys = attributeNamesUsedInPrimaryKey(topTableSchema.tableMetadata()); List attributeNames = topTableSchema.attributeNames(); + + Map actions = new HashMap<>(); StringBuilder path = new StringBuilder(); path.append(tableName).append("."); for (String attributeName : attributeNames) { - if (keyAttributes.contains(attributeName)) { - // key attributes are always SIGN_ONLY - actions.put(attributeName, CryptoAction.SIGN_ONLY); + if (tableKeys.contains(attributeName)) { + if (signAndIncludeAttributes.isEmpty()) { + validateAttributeUsage(tableName, attributeName, "a primary key", Optional.empty(), Optional.of(signAndIncludeAttributes), Optional.of(doNothingAttributes)); + actions.put(attributeName, CryptoAction.SIGN_ONLY); + } else { + validateAttributeUsage(tableName, attributeName, "a primary key", Optional.of(signOnlyAttributes), Optional.empty(), Optional.of(doNothingAttributes)); + actions.put(attributeName, CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT); + } } else if (signOnlyAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "@DynamoDbEncryptionSignOnly", Optional.empty(), Optional.of(signAndIncludeAttributes), Optional.of(doNothingAttributes)); + actions.put(attributeName, CryptoAction.SIGN_ONLY); + } else if (signAndIncludeAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "@DynamoDbEncryptionSignAndIncludeInEncryptionContext", Optional.of(signOnlyAttributes), Optional.empty(), Optional.of(doNothingAttributes)); + actions.put(attributeName, CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT); + } else if (keyAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "an index key", Optional.empty(), Optional.of(signAndIncludeAttributes), Optional.of(doNothingAttributes)); actions.put(attributeName, CryptoAction.SIGN_ONLY); } else if (doNothingAttributes.contains(attributeName)) { + validateAttributeUsage(tableName, attributeName, "@DynamoDbEncryptionDoNothing", Optional.of(signOnlyAttributes), Optional.of(signAndIncludeAttributes), Optional.empty()); actions.put(attributeName, CryptoAction.DO_NOTHING); } else { // non-key attributes are ENCRYPT_AND_SIGN unless otherwise annotated @@ -102,12 +159,101 @@ private static DynamoDbTableEncryptionConfig getTableConfig( // Detect Encryption Flags that are Ignored b/c they are in a Nested Class scanForIgnoredEncryptionTags(topTableSchema, attributeName, path); } + return actions; + } + + // given action maps from multiple tables, merge them into one + // we throw an error if the one attribute is given two different actions + private static Map mergeActions(List> actionList) + { + // most common case + if (actionList.size() == 1) { + return actionList.get(0); + } + + // Gather set of all attributes + HashSet attributes = new HashSet<>(); + for (Map config : actionList) { + attributes.addAll(config.keySet()); + } + + // for each attribute, ensure that everyone agrees on its action + Map actions = new HashMap<>(); + for (String attr : attributes) { + Optional action = Optional.empty(); + for (Map config : actionList) { + CryptoAction act = config.get(attr); + if (act != null) { + if (action.isPresent()) { + if (!action.get().equals(act)) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Attribute %s set to %s in one table and %s in another.", + attr, action.get(), act)) + .build(); + } + } else { + action = Optional.of(act); + } + } + } + actions.put(attr, action.get()); + } + return actions; + } + + // return the partition key name + // throw an error if two schemas disagree + private static String getPartitionKeyName(List> schemas) + { + String partitionName = schemas.get(0).tableMetadata().primaryPartitionKey(); + for (TableSchema schema : schemas) { + String part = schema.tableMetadata().primaryPartitionKey(); + if (!partitionName.equals(part)) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Primary Key set to %s in one table and %s in another.", + partitionName, part)) + .build(); + } + } + return partitionName; + } + + // return the sort key name + // throw an error if two schemas disagree + private static Optional getSortKeyName(List> schemas) + { + Optional sortName = schemas.get(0).tableMetadata().primarySortKey(); + for (TableSchema schema : schemas) { + Optional sort = schema.tableMetadata().primarySortKey(); + if (!sortName.equals(sort)) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Primary Key set to %s in one table and %s in another.", + sortName, sort)) + .build(); + } + } + return sortName; + } + + // Convert enhanced client config to regular config + private static DynamoDbTableEncryptionConfig getTableConfig( + final DynamoDbEnhancedTableEncryptionConfig configWithSchema, + final String tableName + ) { + List> actionList = new ArrayList<>(); + for (TableSchema schema : configWithSchema.schemaOnEncrypt()) { + actionList.add(getActionsFromSchema(tableName, schema)); + } + Map actions = mergeActions(actionList); DynamoDbTableEncryptionConfig.Builder builder = DynamoDbTableEncryptionConfig.builder(); - String partitionName = topTableSchema.tableMetadata().primaryPartitionKey(); + String partitionName = getPartitionKeyName(configWithSchema.schemaOnEncrypt()); builder = builder.partitionKeyName(partitionName); - Optional sortName = topTableSchema.tableMetadata().primarySortKey(); + Optional sortName = getSortKeyName(configWithSchema.schemaOnEncrypt()); if (sortName.isPresent()) { builder = builder.sortKeyName(sortName.get()); } @@ -142,6 +288,13 @@ private static Set getSignOnlyAttributes(TableSchema tableSchema) { .orElseGet(HashSet::new); } + @SuppressWarnings("unchecked") + private static Set getSignAndIncludeInEncryptionContextAttributes(TableSchema tableSchema) { + return tableSchema.tableMetadata() + .customMetadataObject(CUSTOM_DDB_ENCRYPTION_SIGN_AND_INCLUDE_PREFIX, Set.class) + .orElseGet(HashSet::new); + } + @SuppressWarnings("unchecked") private static Set getDoNothingAttributes(TableSchema tableSchema) { return tableSchema.tableMetadata() @@ -186,6 +339,16 @@ private static void scanForIgnoredEncryptionTags( attributePath.append(signOnlyAttributes.toArray()[0]))) .build(); } + Set signAndIncludeAttributes = getSignAndIncludeInEncryptionContextAttributes(subTableSchema); + if (signAndIncludeAttributes.size() > 0) { + throw DynamoDbEncryptionException.builder() + .message(String.format( + "Detected DynamoDbEncryption Tag %s on a nested attribute with Path %s. " + + "This is NOT Supported at this time!", + CUSTOM_DDB_ENCRYPTION_SIGN_AND_INCLUDE_PREFIX, + attributePath.append(signAndIncludeAttributes.toArray()[0]))) + .build(); + } Set doNothingAttributes = getDoNothingAttributes(subTableSchema); if (doNothingAttributes.size() > 0) { throw DynamoDbEncryptionException.builder() diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java index 1b468a9d6..a1b3eeb85 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedTableEncryptionConfig.java @@ -1,6 +1,7 @@ package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; import java.util.List; +import java.util.ArrayList; import java.util.Objects; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.LegacyOverride; @@ -14,7 +15,7 @@ public class DynamoDbEnhancedTableEncryptionConfig { private final String logicalTableName; - private final TableSchema schemaOnEncrypt; + private final List> schemaOnEncrypt; private final List allowedUnsignedAttributes; private final String allowedUnsignedAttributePrefix; private final Keyring keyring; @@ -39,7 +40,7 @@ protected DynamoDbEnhancedTableEncryptionConfig(BuilderImpl builder) { public String logicalTableName() { return this.logicalTableName; } - public TableSchema schemaOnEncrypt() { + public List> schemaOnEncrypt() { return this.schemaOnEncrypt; } @@ -83,7 +84,7 @@ public interface Builder { String logicalTableName(); Builder logicalTableName(String logicalTableName); Builder schemaOnEncrypt(TableSchema schemaOnEncrypt); - TableSchema schemaOnEncrypt(); + List> schemaOnEncrypt(); Builder allowedUnsignedAttributes(List allowedUnsignedAttributes); List allowedUnsignedAttributes(); Builder allowedUnsignedAttributePrefix(String allowedUnsignedAttributePrefix); @@ -101,7 +102,7 @@ public interface Builder { protected static class BuilderImpl implements Builder { protected String logicalTableName; - protected TableSchema schemaOnEncrypt; + protected List> schemaOnEncrypt; protected List allowedUnsignedAttributes; protected String allowedUnsignedAttributePrefix; protected Keyring keyring; @@ -132,11 +133,14 @@ public Builder logicalTableName(String logicalTableName) { public String logicalTableName() { return this.logicalTableName; } public Builder schemaOnEncrypt(TableSchema schemaOnEncrypt) { - this.schemaOnEncrypt = schemaOnEncrypt; + if (Objects.isNull(this.schemaOnEncrypt())) { + this.schemaOnEncrypt = new ArrayList(); + } + this.schemaOnEncrypt.add(schemaOnEncrypt); return this; } - public TableSchema schemaOnEncrypt() { + public List> schemaOnEncrypt() { return this.schemaOnEncrypt; } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/EncryptionAttributeTags.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/EncryptionAttributeTags.java index fca35834f..1762d756d 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/EncryptionAttributeTags.java +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/EncryptionAttributeTags.java @@ -10,6 +10,10 @@ public static StaticAttributeTag attributeTagFor(DynamoDbEncryptionSignOnly anno return new SignOnlyTag(); } + public static StaticAttributeTag attributeTagFor(DynamoDbEncryptionSignAndIncludeInEncryptionContext annotation) { + return new SignAndIncludeInEncryptionContextTag(); + } + public static StaticAttributeTag attributeTagFor(DynamoDbEncryptionDoNothing annotation) { return new DoNothingTag(); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/SignAndIncludeInEncryptionContextTag.java b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/SignAndIncludeInEncryptionContextTag.java new file mode 100644 index 000000000..0fff024fd --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/main/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/SignAndIncludeInEncryptionContextTag.java @@ -0,0 +1,19 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient; + +import java.util.Arrays; +import java.util.Collections; +import java.util.function.Consumer; + +import software.amazon.awssdk.enhanced.dynamodb.AttributeValueType; +import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticAttributeTag; +import software.amazon.awssdk.enhanced.dynamodb.mapper.StaticTableMetadata; + +public class SignAndIncludeInEncryptionContextTag implements StaticAttributeTag { + public static final String CUSTOM_DDB_ENCRYPTION_SIGN_AND_INCLUDE_PREFIX = "DynamoDbEncryption:SignAndIncludeInEncryptionContext"; + + @Override + public Consumer modifyMetadata(String attributeName, AttributeValueType attributeValueType) { + return metadata -> metadata + .addCustomMetadataObject(CUSTOM_DDB_ENCRYPTION_SIGN_AND_INCLUDE_PREFIX, Collections.singleton(attributeName)); + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryption.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryption.java index 73b73b0f1..b44d6b65a 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryption.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/DynamoDbEncryption.java @@ -6,7 +6,6 @@ import Wrappers_Compile.Result; import java.lang.IllegalArgumentException; import java.util.Objects; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.DynamoDbEncryptionClient; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.__default; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.IDynamoDbEncryptionClient; @@ -20,7 +19,7 @@ public class DynamoDbEncryption { protected DynamoDbEncryption(BuilderImpl builder) { DynamoDbEncryptionConfig input = builder.DynamoDbEncryptionConfig(); software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.DynamoDbEncryptionConfig dafnyValue = ToDafny.DynamoDbEncryptionConfig(input); - Result result = __default.DynamoDbEncryption(dafnyValue); + Result result = __default.DynamoDbEncryption(dafnyValue); if (result.is_Failure()) { throw ToNative.Error(result.dtor_error()); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/DynamoDbItemEncryptor.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/DynamoDbItemEncryptor.java index 227f1ec15..422764b6d 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/DynamoDbItemEncryptor.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/DynamoDbItemEncryptor.java @@ -6,7 +6,6 @@ import Wrappers_Compile.Result; import java.lang.IllegalArgumentException; import java.util.Objects; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.DynamoDbItemEncryptorClient; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.__default; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.IDynamoDbItemEncryptorClient; @@ -22,7 +21,7 @@ public class DynamoDbItemEncryptor { protected DynamoDbItemEncryptor(BuilderImpl builder) { DynamoDbItemEncryptorConfig input = builder.DynamoDbItemEncryptorConfig(); software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.DynamoDbItemEncryptorConfig dafnyValue = ToDafny.DynamoDbItemEncryptorConfig(input); - Result result = __default.DynamoDbItemEncryptor(dafnyValue); + Result result = __default.DynamoDbItemEncryptor(dafnyValue); if (result.is_Failure()) { throw ToNative.Error(result.dtor_error()); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java index 64b8e4d54..da8714f8b 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToDafny.java @@ -147,7 +147,11 @@ public static ParsedHeader ParsedHeader( encryptedDataKeys = software.amazon.cryptography.materialproviders.ToDafny.EncryptedDataKeyList(nativeValue.encryptedDataKeys()); DafnyMap, ? extends DafnySequence> storedEncryptionContext; storedEncryptionContext = software.amazon.cryptography.materialproviders.ToDafny.EncryptionContext(nativeValue.storedEncryptionContext()); - return new ParsedHeader(attributeActionsOnEncrypt, algorithmSuiteId, encryptedDataKeys, storedEncryptionContext); + DafnyMap, ? extends DafnySequence> encryptionContext; + encryptionContext = software.amazon.cryptography.materialproviders.ToDafny.EncryptionContext(nativeValue.encryptionContext()); + DafnyMap, ? extends AttributeValue> selectorContext; + selectorContext = software.amazon.cryptography.services.dynamodb.internaldafny.ToDafny.Key(nativeValue.selectorContext()); + return new ParsedHeader(attributeActionsOnEncrypt, algorithmSuiteId, encryptedDataKeys, storedEncryptionContext, encryptionContext, selectorContext); } public static Error Error(DynamoDbItemEncryptorException nativeValue) { diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java index eca5d5e0b..02c93dfcb 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/ToNative.java @@ -147,6 +147,8 @@ public static ParsedHeader ParsedHeader( nativeBuilder.algorithmSuiteId(software.amazon.cryptography.materialproviders.ToNative.DBEAlgorithmSuiteId(dafnyValue.dtor_algorithmSuiteId())); nativeBuilder.encryptedDataKeys(software.amazon.cryptography.materialproviders.ToNative.EncryptedDataKeyList(dafnyValue.dtor_encryptedDataKeys())); nativeBuilder.storedEncryptionContext(software.amazon.cryptography.materialproviders.ToNative.EncryptionContext(dafnyValue.dtor_storedEncryptionContext())); + nativeBuilder.encryptionContext(software.amazon.cryptography.materialproviders.ToNative.EncryptionContext(dafnyValue.dtor_encryptionContext())); + nativeBuilder.selectorContext(software.amazon.cryptography.services.dynamodb.internaldafny.ToNative.Key(dafnyValue.dtor_selectorContext())); return nativeBuilder.build(); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/ParsedHeader.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/ParsedHeader.java index 175360f3a..72531d67e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/ParsedHeader.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/itemencryptor/model/ParsedHeader.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.model.CryptoAction; import software.amazon.cryptography.materialproviders.model.DBEAlgorithmSuiteId; import software.amazon.cryptography.materialproviders.model.EncryptedDataKey; @@ -34,11 +35,23 @@ public class ParsedHeader { */ private final Map storedEncryptionContext; + /** + * The full encryption context. + */ + private final Map encryptionContext; + + /** + * The encryption context as presented to the branch key selector. + */ + private final Map selectorContext; + protected ParsedHeader(BuilderImpl builder) { this.attributeActionsOnEncrypt = builder.attributeActionsOnEncrypt(); this.algorithmSuiteId = builder.algorithmSuiteId(); this.encryptedDataKeys = builder.encryptedDataKeys(); this.storedEncryptionContext = builder.storedEncryptionContext(); + this.encryptionContext = builder.encryptionContext(); + this.selectorContext = builder.selectorContext(); } /** @@ -69,6 +82,20 @@ public Map storedEncryptionContext() { return this.storedEncryptionContext; } + /** + * @return The full encryption context. + */ + public Map encryptionContext() { + return this.encryptionContext; + } + + /** + * @return The encryption context as presented to the branch key selector. + */ + public Map selectorContext() { + return this.selectorContext; + } + public Builder toBuilder() { return new BuilderImpl(this); } @@ -118,6 +145,26 @@ public interface Builder { */ Map storedEncryptionContext(); + /** + * @param encryptionContext The full encryption context. + */ + Builder encryptionContext(Map encryptionContext); + + /** + * @return The full encryption context. + */ + Map encryptionContext(); + + /** + * @param selectorContext The encryption context as presented to the branch key selector. + */ + Builder selectorContext(Map selectorContext); + + /** + * @return The encryption context as presented to the branch key selector. + */ + Map selectorContext(); + ParsedHeader build(); } @@ -130,6 +177,10 @@ static class BuilderImpl implements Builder { protected Map storedEncryptionContext; + protected Map encryptionContext; + + protected Map selectorContext; + protected BuilderImpl() { } @@ -138,6 +189,8 @@ protected BuilderImpl(ParsedHeader model) { this.algorithmSuiteId = model.algorithmSuiteId(); this.encryptedDataKeys = model.encryptedDataKeys(); this.storedEncryptionContext = model.storedEncryptionContext(); + this.encryptionContext = model.encryptionContext(); + this.selectorContext = model.selectorContext(); } public Builder attributeActionsOnEncrypt(Map attributeActionsOnEncrypt) { @@ -176,6 +229,24 @@ public Map storedEncryptionContext() { return this.storedEncryptionContext; } + public Builder encryptionContext(Map encryptionContext) { + this.encryptionContext = encryptionContext; + return this; + } + + public Map encryptionContext() { + return this.encryptionContext; + } + + public Builder selectorContext(Map selectorContext) { + this.selectorContext = selectorContext; + return this; + } + + public Map selectorContext() { + return this.selectorContext; + } + public ParsedHeader build() { if (Objects.isNull(this.attributeActionsOnEncrypt())) { throw new IllegalArgumentException("Missing value for required field `attributeActionsOnEncrypt`"); @@ -189,6 +260,12 @@ public ParsedHeader build() { if (Objects.isNull(this.storedEncryptionContext())) { throw new IllegalArgumentException("Missing value for required field `storedEncryptionContext`"); } + if (Objects.isNull(this.encryptionContext())) { + throw new IllegalArgumentException("Missing value for required field `encryptionContext`"); + } + if (Objects.isNull(this.selectorContext())) { + throw new IllegalArgumentException("Missing value for required field `selectorContext`"); + } return new ParsedHeader(this); } } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/DynamoDbEncryptionTransforms.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/DynamoDbEncryptionTransforms.java index 7140ad9c4..35d961639 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/DynamoDbEncryptionTransforms.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/dynamodb/transforms/DynamoDbEncryptionTransforms.java @@ -7,7 +7,6 @@ import java.lang.IllegalArgumentException; import java.util.Objects; import software.amazon.cryptography.dbencryptionsdk.dynamodb.model.DynamoDbTablesEncryptionConfig; -import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.DynamoDbEncryptionTransformsClient; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.__default; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.IDynamoDbEncryptionTransformsClient; @@ -72,7 +71,7 @@ public class DynamoDbEncryptionTransforms { protected DynamoDbEncryptionTransforms(BuilderImpl builder) { DynamoDbTablesEncryptionConfig input = builder.DynamoDbTablesEncryptionConfig(); software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.DynamoDbTablesEncryptionConfig dafnyValue = software.amazon.cryptography.dbencryptionsdk.dynamodb.ToDafny.DynamoDbTablesEncryptionConfig(input); - Result result = __default.DynamoDbEncryptionTransforms(dafnyValue); + Result result = __default.DynamoDbEncryptionTransforms(dafnyValue); if (result.is_Failure()) { throw ToNative.Error(result.dtor_error()); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/StructuredEncryption.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/StructuredEncryption.java index b181d887c..d242a3a4e 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/StructuredEncryption.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/StructuredEncryption.java @@ -6,7 +6,6 @@ import Wrappers_Compile.Result; import java.lang.IllegalArgumentException; import java.util.Objects; -import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.StructuredEncryptionClient; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.__default; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.Error; import software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.IStructuredEncryptionClient; @@ -22,7 +21,7 @@ public class StructuredEncryption { protected StructuredEncryption(BuilderImpl builder) { StructuredEncryptionConfig input = builder.StructuredEncryptionConfig(); software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.StructuredEncryptionConfig dafnyValue = ToDafny.StructuredEncryptionConfig(input); - Result result = __default.StructuredEncryption(dafnyValue); + Result result = __default.StructuredEncryption(dafnyValue); if (result.is_Failure()) { throw ToNative.Error(result.dtor_error()); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java index 118f2ed73..c60cfb420 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToDafny.java @@ -154,7 +154,9 @@ public static ParsedHeader ParsedHeader( encryptedDataKeys = software.amazon.cryptography.materialproviders.ToDafny.EncryptedDataKeyList(nativeValue.encryptedDataKeys()); DafnyMap, ? extends DafnySequence> storedEncryptionContext; storedEncryptionContext = software.amazon.cryptography.materialproviders.ToDafny.EncryptionContext(nativeValue.storedEncryptionContext()); - return new ParsedHeader(cryptoSchema, algorithmSuiteId, encryptedDataKeys, storedEncryptionContext); + DafnyMap, ? extends DafnySequence> encryptionContext; + encryptionContext = software.amazon.cryptography.materialproviders.ToDafny.EncryptionContext(nativeValue.encryptionContext()); + return new ParsedHeader(cryptoSchema, algorithmSuiteId, encryptedDataKeys, storedEncryptionContext, encryptionContext); } public static StructuredData StructuredData( @@ -209,6 +211,9 @@ public static CryptoAction CryptoAction( case ENCRYPT_AND_SIGN: { return CryptoAction.create_ENCRYPT__AND__SIGN(); } + case SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT: { + return CryptoAction.create_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT(); + } case SIGN_ONLY: { return CryptoAction.create_SIGN__ONLY(); } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java index da1008704..97bbd90cc 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/ToNative.java @@ -152,6 +152,7 @@ public static ParsedHeader ParsedHeader( nativeBuilder.algorithmSuiteId(software.amazon.cryptography.materialproviders.ToNative.DBEAlgorithmSuiteId(dafnyValue.dtor_algorithmSuiteId())); nativeBuilder.encryptedDataKeys(software.amazon.cryptography.materialproviders.ToNative.EncryptedDataKeyList(dafnyValue.dtor_encryptedDataKeys())); nativeBuilder.storedEncryptionContext(software.amazon.cryptography.materialproviders.ToNative.EncryptionContext(dafnyValue.dtor_storedEncryptionContext())); + nativeBuilder.encryptionContext(software.amazon.cryptography.materialproviders.ToNative.EncryptionContext(dafnyValue.dtor_encryptionContext())); return nativeBuilder.build(); } @@ -195,6 +196,9 @@ public static CryptoAction CryptoAction( if (dafnyValue.is_ENCRYPT__AND__SIGN()) { return CryptoAction.ENCRYPT_AND_SIGN; } + if (dafnyValue.is_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT()) { + return CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT; + } if (dafnyValue.is_SIGN__ONLY()) { return CryptoAction.SIGN_ONLY; } diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CryptoAction.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CryptoAction.java index 0f6b99336..a62f53e33 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CryptoAction.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/CryptoAction.java @@ -6,6 +6,8 @@ public enum CryptoAction { ENCRYPT_AND_SIGN("ENCRYPT_AND_SIGN"), + SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT("SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"), + SIGN_ONLY("SIGN_ONLY"), DO_NOTHING("DO_NOTHING"); diff --git a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/ParsedHeader.java b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/ParsedHeader.java index 9b27c45a2..75167d9cf 100644 --- a/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/ParsedHeader.java +++ b/DynamoDbEncryption/runtimes/java/src/main/smithy-generated/software/amazon/cryptography/dbencryptionsdk/structuredencryption/model/ParsedHeader.java @@ -18,11 +18,14 @@ public class ParsedHeader { private final Map storedEncryptionContext; + private final Map encryptionContext; + protected ParsedHeader(BuilderImpl builder) { this.cryptoSchema = builder.cryptoSchema(); this.algorithmSuiteId = builder.algorithmSuiteId(); this.encryptedDataKeys = builder.encryptedDataKeys(); this.storedEncryptionContext = builder.storedEncryptionContext(); + this.encryptionContext = builder.encryptionContext(); } public CryptoSchema cryptoSchema() { @@ -41,6 +44,10 @@ public Map storedEncryptionContext() { return this.storedEncryptionContext; } + public Map encryptionContext() { + return this.encryptionContext; + } + public Builder toBuilder() { return new BuilderImpl(this); } @@ -66,6 +73,10 @@ public interface Builder { Map storedEncryptionContext(); + Builder encryptionContext(Map encryptionContext); + + Map encryptionContext(); + ParsedHeader build(); } @@ -78,6 +89,8 @@ static class BuilderImpl implements Builder { protected Map storedEncryptionContext; + protected Map encryptionContext; + protected BuilderImpl() { } @@ -86,6 +99,7 @@ protected BuilderImpl(ParsedHeader model) { this.algorithmSuiteId = model.algorithmSuiteId(); this.encryptedDataKeys = model.encryptedDataKeys(); this.storedEncryptionContext = model.storedEncryptionContext(); + this.encryptionContext = model.encryptionContext(); } public Builder cryptoSchema(CryptoSchema cryptoSchema) { @@ -124,6 +138,15 @@ public Map storedEncryptionContext() { return this.storedEncryptionContext; } + public Builder encryptionContext(Map encryptionContext) { + this.encryptionContext = encryptionContext; + return this; + } + + public Map encryptionContext() { + return this.encryptionContext; + } + public ParsedHeader build() { if (Objects.isNull(this.cryptoSchema())) { throw new IllegalArgumentException("Missing value for required field `cryptoSchema`"); @@ -137,6 +160,9 @@ public ParsedHeader build() { if (Objects.isNull(this.storedEncryptionContext())) { throw new IllegalArgumentException("Missing value for required field `storedEncryptionContext`"); } + if (Objects.isNull(this.encryptionContext())) { + throw new IllegalArgumentException("Missing value for required field `encryptionContext`"); + } return new ParsedHeader(this); } } diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java index 95ed61a55..90a01fa8f 100644 --- a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/DynamoDbEnhancedClientEncryptionTest.java @@ -32,6 +32,10 @@ public class DynamoDbEnhancedClientEncryptionTest { public void TestMultipleTables() { TableSchema simpleSchema = TableSchema.fromBean(SimpleClass.class); TableSchema signOnlySchema = TableSchema.fromBean(SignOnlyClass.class); + TableSchema signAndIncludeSchema = TableSchema.fromBean(SignAndIncludeInEncryptionContextClass.class); + TableSchema singleTable1Schema = TableSchema.fromBean(SingleTable1.class); + TableSchema singleTable2Schema = TableSchema.fromBean(SingleTable2.class); + TableSchema singleTable3Schema = TableSchema.fromBean(SingleTable3.class); Map tableConfigs = new HashMap<>(); tableConfigs.put("SimpleClassTestTable", DynamoDbEnhancedTableEncryptionConfig.builder() @@ -46,13 +50,29 @@ public void TestMultipleTables() { .keyring(createKmsKeyring()) .schemaOnEncrypt(signOnlySchema) .build()); + tableConfigs.put("SignAndIncludeInEncryptionContextClassTestTable", + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName("SignAndIncludeInEncryptionContextClassTestTable") + .keyring(createKmsKeyring()) + .schemaOnEncrypt(signAndIncludeSchema) + .build()); + tableConfigs.put("SingleTableTestTable", + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName("SingleTableTestTable") + .keyring(createKmsKeyring()) + .allowedUnsignedAttributes(Arrays.asList("doNothing")) + .allowedUnsignedAttributePrefix("extraDoNothing") + .schemaOnEncrypt(singleTable1Schema) + .schemaOnEncrypt(singleTable2Schema) + .schemaOnEncrypt(singleTable3Schema) + .build()); DynamoDbEncryptionInterceptor interceptor = DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( CreateDynamoDbEncryptionInterceptorInput.builder() .tableEncryptionConfigs(tableConfigs) .build() ); - assertEquals(2, interceptor.config().tableEncryptionConfigs().size()); + assertEquals(4, interceptor.config().tableEncryptionConfigs().size()); DynamoDbTableEncryptionConfig simpleConfig = interceptor.config().tableEncryptionConfigs().get("SimpleClassTestTable"); assertEquals(CryptoAction.DO_NOTHING, simpleConfig.attributeActionsOnEncrypt().get("doNothing")); @@ -66,6 +86,47 @@ public void TestMultipleTables() { assertEquals(CryptoAction.SIGN_ONLY, signOnlyConfig.attributeActionsOnEncrypt().get("sort_key")); assertEquals(CryptoAction.SIGN_ONLY, signOnlyConfig.attributeActionsOnEncrypt().get("attr1")); assertEquals(CryptoAction.SIGN_ONLY, signOnlyConfig.attributeActionsOnEncrypt().get("attr2")); + + DynamoDbTableEncryptionConfig signAndIncludeConfig = interceptor.config().tableEncryptionConfigs().get("SignAndIncludeInEncryptionContextClassTestTable"); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, signAndIncludeConfig.attributeActionsOnEncrypt().get("partition_key")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, signAndIncludeConfig.attributeActionsOnEncrypt().get("sort_key")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, signAndIncludeConfig.attributeActionsOnEncrypt().get("attr1")); + assertEquals(CryptoAction.SIGN_ONLY, signAndIncludeConfig.attributeActionsOnEncrypt().get("attr2")); + + DynamoDbTableEncryptionConfig singleTableConfig = interceptor.config().tableEncryptionConfigs().get("SingleTableTestTable"); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("partition_key")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("sort_key")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("doNothing")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("signOnly")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("encryptAndSign")); + + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing1")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing2")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing3")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing12")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing13")); + assertEquals(CryptoAction.DO_NOTHING, singleTableConfig.attributeActionsOnEncrypt().get("extraDoNothing23")); + + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly1")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly2")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly3")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly12")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly13")); + assertEquals(CryptoAction.SIGN_ONLY, singleTableConfig.attributeActionsOnEncrypt().get("extraSignOnly23")); + + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign1")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign2")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign3")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign12")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign13")); + assertEquals(CryptoAction.ENCRYPT_AND_SIGN, singleTableConfig.attributeActionsOnEncrypt().get("extraEncryptAndSign23")); + + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude1")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude2")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude3")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude12")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude13")); + assertEquals(CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, singleTableConfig.attributeActionsOnEncrypt().get("extraSignAndInclude23")); } @Test @@ -127,7 +188,7 @@ public void TestEnhancedCreateWithAlgorithmSuite() { @Test( expectedExceptions = DynamoDbEncryptionException.class, - expectedExceptionsMessageRegExp = "Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: DynamoDbEncryptionInterceptorTestTable" + expectedExceptionsMessageRegExp = "Attribute id of table DynamoDbEncryptionInterceptorTestTable is used as both a primary key and @DynamoDbEncryptionDoNothing." ) public void TestDoNothingOnPartitionAttribute() { TableSchema schemaOnEncrypt = TableSchema.fromBean(InvalidAnnotatedPartitionClass.class); @@ -189,7 +250,7 @@ public void TestInconsistentSignatureScopeIncorrect() { @Test( expectedExceptions = DynamoDbEncryptionException.class, - expectedExceptionsMessageRegExp = "Cannot use @DynamoDbEncryptionDoNothing on primary key attributes. Found on Table Name: DynamoDbEncryptionInterceptorTestTable" + expectedExceptionsMessageRegExp = "Attribute sortKey of table DynamoDbEncryptionInterceptorTestTable is used as both a primary key and @DynamoDbEncryptionDoNothing." ) public void TestDoNothingOnSortAttribute() { TableSchema schemaOnEncrypt = TableSchema.fromBean(InvalidAnnotatedSortClass.class); @@ -208,7 +269,7 @@ public void TestDoNothingOnSortAttribute() { @Test( expectedExceptions = DynamoDbEncryptionException.class, - expectedExceptionsMessageRegExp = "Cannot use @DynamoDbEncryptionDoNothing and @DynamoDbEncryptionSignOnly on same attribute. Found on Table Name: DynamoDbEncryptionInterceptorTestTable" + expectedExceptionsMessageRegExp = "Attribute invalid of table DynamoDbEncryptionInterceptorTestTable is used as both @DynamoDbEncryptionSignOnly and @DynamoDbEncryptionDoNothing." ) public void TestDoubleAnnotationOnAttribute() { TableSchema schemaOnEncrypt = TableSchema.fromBean(InvalidDoubleAnnotationClass.class); @@ -245,6 +306,29 @@ public void TestFlattenedNestedBeanAnnotationMissingUnauthenticatedAttributes() .build()); } + @Test( + expectedExceptions = DynamoDbEncryptionException.class, + expectedExceptionsMessageRegExp = "Attribute partition_key set to SIGN_ONLY in one table and SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT in another." + ) + public void TestIncompatibleClasses() { + TableSchema schemaOnEncrypt1 = + TableSchema.fromBean(SignOnlyClass.class); + TableSchema schemaOnEncrypt2 = + TableSchema.fromBean(SignAndIncludeInEncryptionContextClass.class); + Map tableConfigs = new HashMap<>(); + tableConfigs.put(TEST_TABLE_NAME, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(TEST_TABLE_NAME) + .keyring(createKmsKeyring()) + .schemaOnEncrypt(schemaOnEncrypt1) + .schemaOnEncrypt(schemaOnEncrypt2) + .build()); + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build()); + } + @Test public void TestFlattenedNestedBeanAnnotation() { TableSchema schemaOnEncrypt = diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SignAndIncludeInEncryptionContextClass.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SignAndIncludeInEncryptionContextClass.java new file mode 100644 index 000000000..bdb3e4f01 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SignAndIncludeInEncryptionContextClass.java @@ -0,0 +1,55 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +@DynamoDbBean +public class SignAndIncludeInEncryptionContextClass { + + private String partitionKey; + private int sortKey; + private String attr1; + private String attr2; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getAttr1() { + return this.attr1; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + @DynamoDbEncryptionSignOnly + public String getAttr2() { + return this.attr2; + } + + public void setAttr2(String attr2) { + this.attr2 = attr2; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable1.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable1.java new file mode 100644 index 000000000..b0e9794f1 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable1.java @@ -0,0 +1,123 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SingleTable1 { + + private String partitionKey; + private int sortKey; + private String encryptAndSign; + private String doNothing; + private String signOnly; + + private String extraSignOnly1; + private String extraSignOnly12; + private String extraSignOnly13; + private String extraSignAndInclude1; + private String extraSignAndInclude12; + private String extraSignAndInclude13; + private String extraDoNothing1; + private String extraDoNothing12; + private String extraDoNothing13; + private String extraEncryptAndSign1; + private String extraEncryptAndSign12; + private String extraEncryptAndSign13; + + public String getExtraEncryptAndSign1() {return this.extraEncryptAndSign1;} + public String getExtraEncryptAndSign12() {return this.extraEncryptAndSign12;} + public String getExtraEncryptAndSign13() {return this.extraEncryptAndSign13;} + + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing1() {return this.extraDoNothing1;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing12() {return this.extraDoNothing12;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing13() {return this.extraDoNothing13;} + + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly1() {return this.extraSignOnly1;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly12() {return this.extraSignOnly12;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly13() {return this.extraSignOnly13;} + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude1() {return this.extraSignAndInclude1;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude12() {return this.extraSignAndInclude12;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude13() {return this.extraSignAndInclude13;} + + public void setExtraDoNothing1(String text) {this.extraDoNothing1 = text;} + public void setExtraDoNothing12(String text) {this.extraDoNothing12 = text;} + public void setExtraDoNothing13(String text) {this.extraDoNothing13 = text;} + + public void setExtraSignOnly1(String text) {this.extraSignOnly1 = text;} + public void setExtraSignOnly12(String text) {this.extraSignOnly12 = text;} + public void setExtraSignOnly13(String text) {this.extraSignOnly13 = text;} + + public void setExtraSignAndInclude1(String text) {this.extraSignAndInclude1 = text;} + public void setExtraSignAndInclude12(String text) {this.extraSignAndInclude12 = text;} + public void setExtraSignAndInclude13(String text) {this.extraSignAndInclude13 = text;} + + public void setExtraEncryptAndSign1(String text) {this.extraEncryptAndSign1 = text;} + public void setExtraEncryptAndSign12(String text) {this.extraEncryptAndSign12 = text;} + public void setExtraEncryptAndSign13(String text) {this.extraEncryptAndSign13 = text;} + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getEncryptAndSign() { + return this.encryptAndSign; + } + + public void setEncryptAndSign(String encryptAndSign) { + this.encryptAndSign = encryptAndSign; + } + + @DynamoDbEncryptionSignOnly + public String getSignOnly() { + return this.signOnly; + } + + public void setSignOnly(String signOnly) { + this.signOnly = signOnly; + } + + @DynamoDbEncryptionDoNothing + public String getDoNothing() { + return this.doNothing; + } + + public void setDoNothing(String doNothing) { + this.doNothing = doNothing; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable2.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable2.java new file mode 100644 index 000000000..2031f5c31 --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable2.java @@ -0,0 +1,124 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SingleTable2 { + + private String partitionKey; + private int sortKey; + private String encryptAndSign; + private String doNothing; + private String signOnly; + + private String extraSignOnly2; + private String extraSignOnly12; + private String extraSignOnly23; + private String extraSignAndInclude2; + private String extraSignAndInclude12; + private String extraSignAndInclude23; + private String extraDoNothing2; + private String extraDoNothing12; + private String extraDoNothing23; + private String extraEncryptAndSign2; + private String extraEncryptAndSign12; + private String extraEncryptAndSign23; + + public String getExtraEncryptAndSign2() {return this.extraEncryptAndSign2;} + public String getExtraEncryptAndSign12() {return this.extraEncryptAndSign12;} + public String getExtraEncryptAndSign23() {return this.extraEncryptAndSign23;} + + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing2() {return this.extraDoNothing2;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing12() {return this.extraDoNothing12;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing23() {return this.extraDoNothing23;} + + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly2() {return this.extraSignOnly2;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly12() {return this.extraSignOnly12;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly23() {return this.extraSignOnly23;} + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude2() {return this.extraSignAndInclude2;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude12() {return this.extraSignAndInclude12;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude23() {return this.extraSignAndInclude23;} + + public void setExtraDoNothing2(String text) {this.extraDoNothing2 = text;} + public void setExtraDoNothing12(String text) {this.extraDoNothing12 = text;} + public void setExtraDoNothing23(String text) {this.extraDoNothing23 = text;} + + public void setExtraSignOnly2(String text) {this.extraSignOnly2 = text;} + public void setExtraSignOnly12(String text) {this.extraSignOnly12 = text;} + public void setExtraSignOnly23(String text) {this.extraSignOnly23 = text;} + + public void setExtraSignAndInclude2(String text) {this.extraSignAndInclude2 = text;} + public void setExtraSignAndInclude12(String text) {this.extraSignAndInclude12 = text;} + public void setExtraSignAndInclude23(String text) {this.extraSignAndInclude23 = text;} + + public void setExtraEncryptAndSign2(String text) {this.extraEncryptAndSign2 = text;} + public void setExtraEncryptAndSign12(String text) {this.extraEncryptAndSign12 = text;} + public void setExtraEncryptAndSign23(String text) {this.extraEncryptAndSign23 = text;} + + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getEncryptAndSign() { + return this.encryptAndSign; + } + + public void setEncryptAndSign(String encryptAndSign) { + this.encryptAndSign = encryptAndSign; + } + + @DynamoDbEncryptionSignOnly + public String getSignOnly() { + return this.signOnly; + } + + public void setSignOnly(String signOnly) { + this.signOnly = signOnly; + } + + @DynamoDbEncryptionDoNothing + public String getDoNothing() { + return this.doNothing; + } + + public void setDoNothing(String doNothing) { + this.doNothing = doNothing; + } +} diff --git a/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable3.java b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable3.java new file mode 100644 index 000000000..faad4846c --- /dev/null +++ b/DynamoDbEncryption/runtimes/java/src/test/java/software/amazon/cryptography/dbencryptionsdk/dynamodb/enhancedclient/validdatamodels/SingleTable3.java @@ -0,0 +1,123 @@ +package software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.validdatamodels; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SingleTable3 { + + private String partitionKey; + private int sortKey; + private String encryptAndSign; + private String doNothing; + private String signOnly; + + private String extraSignOnly3; + private String extraSignOnly23; + private String extraSignOnly13; + private String extraSignAndInclude3; + private String extraSignAndInclude23; + private String extraSignAndInclude13; + private String extraDoNothing3; + private String extraDoNothing23; + private String extraDoNothing13; + private String extraEncryptAndSign3; + private String extraEncryptAndSign23; + private String extraEncryptAndSign13; + + public String getExtraEncryptAndSign3() {return this.extraEncryptAndSign3;} + public String getExtraEncryptAndSign23() {return this.extraEncryptAndSign23;} + public String getExtraEncryptAndSign13() {return this.extraEncryptAndSign13;} + + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing3() {return this.extraDoNothing3;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing23() {return this.extraDoNothing23;} + @DynamoDbEncryptionDoNothing + public String getExtraDoNothing13() {return this.extraDoNothing13;} + + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly3() {return this.extraSignOnly3;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly23() {return this.extraSignOnly23;} + @DynamoDbEncryptionSignOnly + public String getExtraSignOnly13() {return this.extraSignOnly13;} + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude3() {return this.extraSignAndInclude3;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude23() {return this.extraSignAndInclude23;} + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getExtraSignAndInclude13() {return this.extraSignAndInclude13;} + + public void setExtraDoNothing3(String text) {this.extraDoNothing3 = text;} + public void setExtraDoNothing23(String text) {this.extraDoNothing23 = text;} + public void setExtraDoNothing13(String text) {this.extraDoNothing13 = text;} + + public void setExtraSignOnly3(String text) {this.extraSignOnly3 = text;} + public void setExtraSignOnly23(String text) {this.extraSignOnly23 = text;} + public void setExtraSignOnly13(String text) {this.extraSignOnly13 = text;} + + public void setExtraSignAndInclude3(String text) {this.extraSignAndInclude3 = text;} + public void setExtraSignAndInclude23(String text) {this.extraSignAndInclude23 = text;} + public void setExtraSignAndInclude13(String text) {this.extraSignAndInclude13 = text;} + + public void setExtraEncryptAndSign3(String text) {this.extraEncryptAndSign3 = text;} + public void setExtraEncryptAndSign23(String text) {this.extraEncryptAndSign23 = text;} + public void setExtraEncryptAndSign13(String text) {this.extraEncryptAndSign13 = text;} + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getEncryptAndSign() { + return this.encryptAndSign; + } + + public void setEncryptAndSign(String encryptAndSign) { + this.encryptAndSign = encryptAndSign; + } + + @DynamoDbEncryptionSignOnly + public String getSignOnly() { + return this.signOnly; + } + + public void setSignOnly(String signOnly) { + this.signOnly = signOnly; + } + + @DynamoDbEncryptionDoNothing + public String getDoNothing() { + return this.doNothing; + } + + public void setDoNothing(String doNothing) { + this.doNothing = doNothing; + } +} diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs index addc17eb9..439bc499c 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryption/TypeConversion.cs @@ -1357,22 +1357,22 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph { switch (value) { - case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyMaterialProviders dafnyVal: - return AWS.Cryptography.MaterialProviders.TypeConversion.FromDafny_CommonError( - dafnyVal._AwsCryptographyMaterialProviders - ); - case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyPrimitives dafnyVal: - return AWS.Cryptography.Primitives.TypeConversion.FromDafny_CommonError( - dafnyVal._AwsCryptographyPrimitives - ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyDbEncryptionSdkStructuredEncryption dafnyVal: return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption ); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyMaterialProviders dafnyVal: + return AWS.Cryptography.MaterialProviders.TypeConversion.FromDafny_CommonError( + dafnyVal._AwsCryptographyMaterialProviders + ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_ComAmazonawsDynamodb dafnyVal: return Com.Amazonaws.Dynamodb.TypeConversion.FromDafny_CommonError( dafnyVal._ComAmazonawsDynamodb ); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_AwsCryptographyPrimitives dafnyVal: + return AWS.Cryptography.Primitives.TypeConversion.FromDafny_CommonError( + dafnyVal._AwsCryptographyPrimitives + ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_DynamoDbEncryptionException dafnyVal: return FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__S27_DynamoDbEncryptionException(dafnyVal); case software.amazon.cryptography.dbencryptionsdk.dynamodb.internaldafny.types.Error_CollectionOfErrors dafnyVal: diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs index 931a421df..396852a23 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbEncryptionTransforms/TypeConversion.cs @@ -4567,6 +4567,7 @@ public static Amazon.DynamoDBv2.AttributeAction FromDafny_N3_com__N9_amazonaws__ public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoAction(software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoAction value) { if (value.is_ENCRYPT__AND__SIGN) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.ENCRYPT_AND_SIGN; + if (value.is_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT; if (value.is_SIGN__ONLY) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_ONLY; if (value.is_DO__NOTHING) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.DO_NOTHING; throw new System.ArgumentException("Invalid AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value"); @@ -4574,6 +4575,7 @@ public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction public static software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoAction ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoAction(AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value) { if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.ENCRYPT_AND_SIGN.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_ENCRYPT__AND__SIGN(); + if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT(); if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_ONLY.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_SIGN__ONLY(); if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.DO_NOTHING.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_DO__NOTHING(); throw new System.ArgumentException("Invalid AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value"); @@ -6487,6 +6489,10 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph return AWS.Cryptography.DbEncryptionSDK.DynamoDb.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyDbEncryptionSdkDynamoDb ); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_AwsCryptographyDbEncryptionSdkStructuredEncryption dafnyVal: + return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( + dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption + ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_AwsCryptographyMaterialProviders dafnyVal: return AWS.Cryptography.MaterialProviders.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyMaterialProviders @@ -6495,10 +6501,6 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph return Com.Amazonaws.Dynamodb.TypeConversion.FromDafny_CommonError( dafnyVal._ComAmazonawsDynamodb ); - case software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_AwsCryptographyDbEncryptionSdkStructuredEncryption dafnyVal: - return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( - dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption - ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.transforms.internaldafny.types.Error_AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptor dafnyVal: return AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptor diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/ParsedHeader.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/ParsedHeader.cs index decf16e3a..f98a38e39 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/ParsedHeader.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/ParsedHeader.cs @@ -11,6 +11,8 @@ public class ParsedHeader private AWS.Cryptography.MaterialProviders.DBEAlgorithmSuiteId _algorithmSuiteId; private System.Collections.Generic.List _encryptedDataKeys; private System.Collections.Generic.Dictionary _storedEncryptionContext; + private System.Collections.Generic.Dictionary _encryptionContext; + private System.Collections.Generic.Dictionary _selectorContext; public System.Collections.Generic.Dictionary AttributeActionsOnEncrypt { get { return this._attributeActionsOnEncrypt; } @@ -47,12 +49,32 @@ public bool IsSetStoredEncryptionContext() { return this._storedEncryptionContext != null; } + public System.Collections.Generic.Dictionary EncryptionContext + { + get { return this._encryptionContext; } + set { this._encryptionContext = value; } + } + public bool IsSetEncryptionContext() + { + return this._encryptionContext != null; + } + public System.Collections.Generic.Dictionary SelectorContext + { + get { return this._selectorContext; } + set { this._selectorContext = value; } + } + public bool IsSetSelectorContext() + { + return this._selectorContext != null; + } public void Validate() { if (!IsSetAttributeActionsOnEncrypt()) throw new System.ArgumentException("Missing value for required property 'AttributeActionsOnEncrypt'"); if (!IsSetAlgorithmSuiteId()) throw new System.ArgumentException("Missing value for required property 'AlgorithmSuiteId'"); if (!IsSetEncryptedDataKeys()) throw new System.ArgumentException("Missing value for required property 'EncryptedDataKeys'"); if (!IsSetStoredEncryptionContext()) throw new System.ArgumentException("Missing value for required property 'StoredEncryptionContext'"); + if (!IsSetEncryptionContext()) throw new System.ArgumentException("Missing value for required property 'EncryptionContext'"); + if (!IsSetSelectorContext()) throw new System.ArgumentException("Missing value for required property 'SelectorContext'"); } } diff --git a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs index bdb119725..54124cea9 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/DynamoDbItemEncryptor/TypeConversion.cs @@ -243,12 +243,14 @@ public static AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor.ParsedHead software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.ParsedHeader concrete = (software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.ParsedHeader)value; AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor.ParsedHeader converted = new AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor.ParsedHeader(); converted.AttributeActionsOnEncrypt = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M25_attributeActionsOnEncrypt(concrete._attributeActionsOnEncrypt); converted.AlgorithmSuiteId = (AWS.Cryptography.MaterialProviders.DBEAlgorithmSuiteId)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M16_algorithmSuiteId(concrete._algorithmSuiteId); converted.EncryptedDataKeys = (System.Collections.Generic.List)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptedDataKeys(concrete._encryptedDataKeys); - converted.StoredEncryptionContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M23_storedEncryptionContext(concrete._storedEncryptionContext); return converted; + converted.StoredEncryptionContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M23_storedEncryptionContext(concrete._storedEncryptionContext); + converted.EncryptionContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptionContext(concrete._encryptionContext); + converted.SelectorContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M15_selectorContext(concrete._selectorContext); return converted; } public static software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types._IParsedHeader ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader(AWS.Cryptography.DbEncryptionSDK.DynamoDb.ItemEncryptor.ParsedHeader value) { - return new software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.ParsedHeader(ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M25_attributeActionsOnEncrypt(value.AttributeActionsOnEncrypt), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M16_algorithmSuiteId(value.AlgorithmSuiteId), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptedDataKeys(value.EncryptedDataKeys), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M23_storedEncryptionContext(value.StoredEncryptionContext)); + return new software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.ParsedHeader(ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M25_attributeActionsOnEncrypt(value.AttributeActionsOnEncrypt), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M16_algorithmSuiteId(value.AlgorithmSuiteId), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptedDataKeys(value.EncryptedDataKeys), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M23_storedEncryptionContext(value.StoredEncryptionContext), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptionContext(value.EncryptionContext), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M15_selectorContext(value.SelectorContext)); } public static string FromDafny_N6_smithy__N3_api__S6_String(Dafny.ISequence value) { @@ -394,6 +396,22 @@ public static System.Collections.Generic.Dictionary FromDafny_N3 { return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext(value); } + public static System.Collections.Generic.Dictionary FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptionContext(Dafny.IMap, Dafny.ISequence> value) + { + return FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext(value); + } + public static Dafny.IMap, Dafny.ISequence> ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M17_encryptionContext(System.Collections.Generic.Dictionary value) + { + return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext(value); + } + public static System.Collections.Generic.Dictionary FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M15_selectorContext(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) + { + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key(value); + } + public static Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S12_ParsedHeader__M15_selectorContext(System.Collections.Generic.Dictionary value) + { + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key(value); + } public static string FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__S16_AttributeActions__M3_key(Dafny.ISequence value) { return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S13_AttributeName(value); @@ -575,9 +593,20 @@ public static System.Collections.Generic.Dictionary FromDafny_N3 new Dafny.Pair, Dafny.ISequence>(ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext__M3_key(pair.Key), ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext__M5_value(pair.Value)) )); } + public static System.Collections.Generic.Dictionary FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key(Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> value) + { + return value.ItemEnumerable.ToDictionary(pair => FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M3_key(pair.Car), pair => FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M5_value(pair.Cdr)); + } + public static Dafny.IMap, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue> ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key(System.Collections.Generic.Dictionary value) + { + return Dafny.Map, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue>.FromCollection(value.Select(pair => + new Dafny.Pair, software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue>(ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M3_key(pair.Key), ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M5_value(pair.Value)) + )); + } public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoAction(software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoAction value) { if (value.is_ENCRYPT__AND__SIGN) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.ENCRYPT_AND_SIGN; + if (value.is_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT; if (value.is_SIGN__ONLY) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_ONLY; if (value.is_DO__NOTHING) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.DO_NOTHING; throw new System.ArgumentException("Invalid AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value"); @@ -585,6 +614,7 @@ public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction public static software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoAction ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoAction(AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value) { if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.ENCRYPT_AND_SIGN.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_ENCRYPT__AND__SIGN(); + if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT(); if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_ONLY.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_SIGN__ONLY(); if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.DO_NOTHING.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_DO__NOTHING(); throw new System.ArgumentException("Invalid AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value"); @@ -719,6 +749,22 @@ public static Dafny.ISequence ToDafny_N3_aws__N12_cryptography__N17_materi { return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S9_Utf8Bytes(value); } + public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M3_key(Dafny.ISequence value) + { + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S13_AttributeName(value); + } + public static Dafny.ISequence ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M3_key(string value) + { + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S13_AttributeName(value); + } + public static Amazon.DynamoDBv2.Model.AttributeValue FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M5_value(software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue value) + { + return FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S14_AttributeValue(value); + } + public static software.amazon.cryptography.services.dynamodb.internaldafny.types._IAttributeValue ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S3_Key__M5_value(Amazon.DynamoDBv2.Model.AttributeValue value) + { + return ToDafny_N3_com__N9_amazonaws__N8_dynamodb__S14_AttributeValue(value); + } public static string FromDafny_N3_com__N9_amazonaws__N8_dynamodb__S20_StringAttributeValue(Dafny.ISequence value) { return new string(value.Elements); @@ -914,6 +960,10 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph return AWS.Cryptography.MaterialProviders.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyMaterialProviders ); + case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_AwsCryptographyDbEncryptionSdkStructuredEncryption dafnyVal: + return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( + dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption + ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_ComAmazonawsDynamodb dafnyVal: return Com.Amazonaws.Dynamodb.TypeConversion.FromDafny_CommonError( dafnyVal._ComAmazonawsDynamodb @@ -922,10 +972,6 @@ public static System.Exception FromDafny_CommonError(software.amazon.cryptograph return AWS.Cryptography.DbEncryptionSDK.DynamoDb.TypeConversion.FromDafny_CommonError( dafnyVal._AwsCryptographyDbEncryptionSdkDynamoDb ); - case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_AwsCryptographyDbEncryptionSdkStructuredEncryption dafnyVal: - return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.TypeConversion.FromDafny_CommonError( - dafnyVal._AwsCryptographyDbEncryptionSdkStructuredEncryption - ); case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_DynamoDbItemEncryptorException dafnyVal: return FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N8_dynamoDb__N13_itemEncryptor__S30_DynamoDbItemEncryptorException(dafnyVal); case software.amazon.cryptography.dbencryptionsdk.dynamodb.itemencryptor.internaldafny.types.Error_CollectionOfErrors dafnyVal: diff --git a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/CryptoAction.cs b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/CryptoAction.cs index ce79bf759..ba3c011e3 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/CryptoAction.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/CryptoAction.cs @@ -12,11 +12,13 @@ public class CryptoAction : ConstantClass public static readonly CryptoAction ENCRYPT_AND_SIGN = new CryptoAction("ENCRYPT_AND_SIGN"); + public static readonly CryptoAction SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT = new CryptoAction("SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"); + public static readonly CryptoAction SIGN_ONLY = new CryptoAction("SIGN_ONLY"); public static readonly CryptoAction DO_NOTHING = new CryptoAction("DO_NOTHING"); public static readonly CryptoAction[] Values = { - DO_NOTHING , ENCRYPT_AND_SIGN , SIGN_ONLY + DO_NOTHING , ENCRYPT_AND_SIGN , SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT , SIGN_ONLY }; public CryptoAction(string value) : base(value) { } } diff --git a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/ParsedHeader.cs b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/ParsedHeader.cs index 5b1bb0b65..ee3e1d376 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/ParsedHeader.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/ParsedHeader.cs @@ -11,6 +11,7 @@ public class ParsedHeader private AWS.Cryptography.MaterialProviders.DBEAlgorithmSuiteId _algorithmSuiteId; private System.Collections.Generic.List _encryptedDataKeys; private System.Collections.Generic.Dictionary _storedEncryptionContext; + private System.Collections.Generic.Dictionary _encryptionContext; public AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoSchema CryptoSchema { get { return this._cryptoSchema; } @@ -47,12 +48,22 @@ public bool IsSetStoredEncryptionContext() { return this._storedEncryptionContext != null; } + public System.Collections.Generic.Dictionary EncryptionContext + { + get { return this._encryptionContext; } + set { this._encryptionContext = value; } + } + public bool IsSetEncryptionContext() + { + return this._encryptionContext != null; + } public void Validate() { if (!IsSetCryptoSchema()) throw new System.ArgumentException("Missing value for required property 'CryptoSchema'"); if (!IsSetAlgorithmSuiteId()) throw new System.ArgumentException("Missing value for required property 'AlgorithmSuiteId'"); if (!IsSetEncryptedDataKeys()) throw new System.ArgumentException("Missing value for required property 'EncryptedDataKeys'"); if (!IsSetStoredEncryptionContext()) throw new System.ArgumentException("Missing value for required property 'StoredEncryptionContext'"); + if (!IsSetEncryptionContext()) throw new System.ArgumentException("Missing value for required property 'EncryptionContext'"); } } diff --git a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs index b22c7304f..e83e3b2e6 100644 --- a/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs +++ b/DynamoDbEncryption/runtimes/net/Generated/StructuredEncryption/TypeConversion.cs @@ -58,6 +58,7 @@ public static software.amazon.cryptography.dbencryptionsdk.structuredencryption. public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoAction(software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoAction value) { if (value.is_ENCRYPT__AND__SIGN) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.ENCRYPT_AND_SIGN; + if (value.is_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT; if (value.is_SIGN__ONLY) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_ONLY; if (value.is_DO__NOTHING) return AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.DO_NOTHING; throw new System.ArgumentException("Invalid AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value"); @@ -65,6 +66,7 @@ public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction public static software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoAction ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoAction(AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value) { if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.ENCRYPT_AND_SIGN.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_ENCRYPT__AND__SIGN(); + if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_SIGN__AND__INCLUDE__IN__ENCRYPTION__CONTEXT(); if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.SIGN_ONLY.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_SIGN__ONLY(); if (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction.DO_NOTHING.Equals(value)) return software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.CryptoAction.create_DO__NOTHING(); throw new System.ArgumentException("Invalid AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoAction value"); @@ -502,12 +504,13 @@ public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.ParsedHeader software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.ParsedHeader concrete = (software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.ParsedHeader)value; AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.ParsedHeader converted = new AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.ParsedHeader(); converted.CryptoSchema = (AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoSchema)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M12_cryptoSchema(concrete._cryptoSchema); converted.AlgorithmSuiteId = (AWS.Cryptography.MaterialProviders.DBEAlgorithmSuiteId)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M16_algorithmSuiteId(concrete._algorithmSuiteId); converted.EncryptedDataKeys = (System.Collections.Generic.List)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptedDataKeys(concrete._encryptedDataKeys); - converted.StoredEncryptionContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M23_storedEncryptionContext(concrete._storedEncryptionContext); return converted; + converted.StoredEncryptionContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M23_storedEncryptionContext(concrete._storedEncryptionContext); + converted.EncryptionContext = (System.Collections.Generic.Dictionary)FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptionContext(concrete._encryptionContext); return converted; } public static software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._IParsedHeader ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader(AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.ParsedHeader value) { - return new software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.ParsedHeader(ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M12_cryptoSchema(value.CryptoSchema), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M16_algorithmSuiteId(value.AlgorithmSuiteId), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptedDataKeys(value.EncryptedDataKeys), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M23_storedEncryptionContext(value.StoredEncryptionContext)); + return new software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types.ParsedHeader(ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M12_cryptoSchema(value.CryptoSchema), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M16_algorithmSuiteId(value.AlgorithmSuiteId), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptedDataKeys(value.EncryptedDataKeys), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M23_storedEncryptionContext(value.StoredEncryptionContext), ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptionContext(value.EncryptionContext)); } public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoSchema FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoSchema(software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoSchema value) { @@ -687,6 +690,14 @@ public static System.Collections.Generic.Dictionary FromDafny_N3 { return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext(value); } + public static System.Collections.Generic.Dictionary FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptionContext(Dafny.IMap, Dafny.ISequence> value) + { + return FromDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext(value); + } + public static Dafny.IMap, Dafny.ISequence> ToDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_ParsedHeader__M17_encryptionContext(System.Collections.Generic.Dictionary value) + { + return ToDafny_N3_aws__N12_cryptography__N17_materialProviders__S17_EncryptionContext(value); + } public static AWS.Cryptography.DbEncryptionSDK.StructuredEncryption.CryptoSchemaContent FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S12_CryptoSchema__M7_content(software.amazon.cryptography.dbencryptionsdk.structuredencryption.internaldafny.types._ICryptoSchemaContent value) { return FromDafny_N3_aws__N12_cryptography__N15_dbEncryptionSdk__N20_structuredEncryption__S19_CryptoSchemaContent(value); diff --git a/Examples/Makefile b/Examples/Makefile new file mode 100644 index 000000000..d7137db24 --- /dev/null +++ b/Examples/Makefile @@ -0,0 +1,18 @@ +default: + @echo make targets are : test_java, test_net, test_migrate, clean + +# requires "make build_java mvn_local_deploy" in DynamoDbEncryption +test_java: + gradle -p runtimes/java/DynamoDbEncryption test + +# requires "make transpile_net" in DynamoDbEncryption +test_net: + cd runtimes/net && dotnet run + +# requires "make build_java mvn_local_deploy" in DynamoDbEncryption +test_migrate: + gradle -p runtimes/java/Migration/PlaintextToAWSDBE test + gradle -p runtimes/java/Migration/DDBECToAWSDBE test + +clean: + rm -f runtimes/net/*.pem runtimes/java/*.pem diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/EnhancedPutGetExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/EnhancedPutGetExample.java index 09dd90a32..1cf4b62bf 100644 --- a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/EnhancedPutGetExample.java +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/EnhancedPutGetExample.java @@ -5,6 +5,8 @@ import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable; +import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.cryptography.materialproviders.IKeyring; import software.amazon.cryptography.materialproviders.MaterialProviders; @@ -17,8 +19,11 @@ import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; import software.amazon.awssdk.enhanced.dynamodb.Key; + import java.util.HashMap; import java.util.Map; +import java.util.List; +import java.util.ArrayList; /* This example sets up DynamoDb Encryption for the DynamoDb Enhanced Client @@ -48,14 +53,14 @@ public static void PutItemGetItem(String kmsKeyId, String ddbTableName) { .build(); final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); - // 2. Create a Table Schema over your annotated class (See SimpleClass.java in this directory). + // 2. Create a Table Schema over your annotated class (See SimpleClass4.java in this directory). // By default, all primary key attributes will be signed but not encrypted (SIGN_ONLY) // and all non-primary key attributes will be encrypted and signed (ENCRYPT_AND_SIGN). // If you want a particular non-primary key attribute to be signed but not encrypted, // use the `DynamoDbEncryptionSignOnly` annotation. // If you want a particular attribute to be neither signed nor encrypted (DO_NOTHING), // use the `DynamoDbEncryptionDoNothing` annotation. - final TableSchema tableSchema = TableSchema.fromBean(SimpleClass.class); + final TableSchema tableSchema = TableSchema.fromBean(SimpleClass4.class); // 3. Configure which attributes we expect to be included in the signature // when reading items. There are two options for configuring this: @@ -127,17 +132,18 @@ public static void PutItemGetItem(String kmsKeyId, String ddbTableName) { final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() .dynamoDbClient(ddb) .build(); - final DynamoDbTable table = enhancedClient.table(ddbTableName, tableSchema); + final DynamoDbTable table = enhancedClient.table(ddbTableName, tableSchema); // 8. Put an item into your table using the DynamoDb Enhanced Client. // The item will be encrypted client-side according to your // configuration above before it is sent to DynamoDb. - final SimpleClass item = new SimpleClass(); + final SimpleClass4 item = new SimpleClass4(); item.setPartitionKey("EnhancedPutGetExample"); item.setSortKey(0); item.setAttribute1("encrypt and sign me!"); item.setAttribute2("sign me!"); item.setAttribute3("ignore me!"); + item.setAttribute4("sign and include me!"); table.putItem(item); @@ -148,11 +154,19 @@ public static void PutItemGetItem(String kmsKeyId, String ddbTableName) { .partitionValue("EnhancedPutGetExample").sortValue(0) .build(); - final SimpleClass result = table.getItem( + final SimpleClass4 result = table.getItem( (GetItemEnhancedRequest.Builder requestBuilder) -> requestBuilder.key(key)); // Demonstrate we get the original item back assert result.getAttribute1().equals("encrypt and sign me!"); + + // retrieve the same record via a Query + PageIterable items = table.query(QueryConditional.keyEqualTo(k -> k.partitionValue("EnhancedPutGetExample"))); + List itemList = new ArrayList(); + + items.stream().forEach(p -> p.items().forEach(itemList::add)); + assert itemList.size() == 1; + assert itemList.get(0).getAttribute1().equals("encrypt and sign me!"); } public static void main(final String[] args) { diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass2.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass2.java new file mode 100644 index 000000000..2c8246b49 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass2.java @@ -0,0 +1,70 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SimpleClass2 { + + private String partitionKey; + private int sortKey; + private String attribute4; + private String attribute5; + private String attribute3; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getAttribute4() { + return this.attribute4; + } + + public void setAttribute4(String attribute4) { + this.attribute4 = attribute4; + } + + @DynamoDbEncryptionSignOnly + public String getAttribute5() { + return this.attribute5; + } + + public void setAttribute5(String attribute5) { + this.attribute5 = attribute5; + } + + @DynamoDbEncryptionDoNothing + public String getAttribute3() { + return this.attribute3; + } + + @DynamoDbAttribute(value = ":attribute3") + public void setAttribute3(String attribute3) { + this.attribute3 = attribute3; + } +} + diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass3.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass3.java new file mode 100644 index 000000000..8b8b29e68 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass3.java @@ -0,0 +1,69 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SimpleClass3 { + + private String partitionKey; + private int sortKey; + private String attribute6; + private String attribute2; + private String attribute7; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getAttribute6() { + return this.attribute6; + } + + public void setAttribute6(String attribute6) { + this.attribute6 = attribute6; + } + + @DynamoDbEncryptionSignOnly + public String getAttribute2() { + return this.attribute2; + } + + public void setAttribute2(String attribute2) { + this.attribute2 = attribute2; + } + + @DynamoDbEncryptionDoNothing + public String getAttribute7() { + return this.attribute7; + } + + @DynamoDbAttribute(value = ":attribute7") + public void setAttribute7(String attribute7) { + this.attribute7 = attribute7; + } +} \ No newline at end of file diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass4.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass4.java new file mode 100644 index 000000000..bac3d82a5 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SimpleClass4.java @@ -0,0 +1,82 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey; +import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionDoNothing; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignOnly; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEncryptionSignAndIncludeInEncryptionContext; + +/** + * This class is used by the Enhanced Client Tests + */ + +@DynamoDbBean +public class SimpleClass4 { + + private String partitionKey; + private int sortKey; + private String attribute1; + private String attribute2; + private String attribute3; + private String attribute4; + + @DynamoDbPartitionKey + @DynamoDbAttribute(value = "partition_key") + public String getPartitionKey() { + return this.partitionKey; + } + + public void setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + } + + @DynamoDbSortKey + @DynamoDbAttribute(value = "sort_key") + public int getSortKey() { + return this.sortKey; + } + + public void setSortKey(int sortKey) { + this.sortKey = sortKey; + } + + public String getAttribute1() { + return this.attribute1; + } + + public void setAttribute1(String attribute1) { + this.attribute1 = attribute1; + } + + @DynamoDbEncryptionSignOnly + public String getAttribute2() { + return this.attribute2; + } + + public void setAttribute2(String attribute2) { + this.attribute2 = attribute2; + } + + @DynamoDbEncryptionDoNothing + public String getAttribute3() { + return this.attribute3; + } + + @DynamoDbAttribute(value = ":attribute3") + public void setAttribute3(String attribute3) { + this.attribute3 = attribute3; + } + + @DynamoDbEncryptionSignAndIncludeInEncryptionContext + public String getAttribute4() { + return this.attribute4; + } + + public void setAttribute4(String attribute4) { + this.attribute4 = attribute4; + } + +} + diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SingleTableExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SingleTableExample.java new file mode 100644 index 000000000..c536c9555 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/main/java/software/amazon/cryptography/examples/enhanced/SingleTableExample.java @@ -0,0 +1,213 @@ +package software.amazon.cryptography.examples.enhanced; + +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; +import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable; +import software.amazon.awssdk.enhanced.dynamodb.TableSchema; +import software.amazon.awssdk.enhanced.dynamodb.model.GetItemEnhancedRequest; +import software.amazon.awssdk.services.dynamodb.DynamoDbClient; +import software.amazon.awssdk.enhanced.dynamodb.Document; +import software.amazon.cryptography.materialproviders.IKeyring; +import software.amazon.cryptography.materialproviders.MaterialProviders; +import software.amazon.cryptography.materialproviders.model.CreateAwsKmsMrkMultiKeyringInput; +import software.amazon.cryptography.materialproviders.model.DBEAlgorithmSuiteId; +import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.DynamoDbEncryptionInterceptor; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.CreateDynamoDbEncryptionInterceptorInput; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedClientEncryption; +import software.amazon.cryptography.dbencryptionsdk.dynamodb.enhancedclient.DynamoDbEnhancedTableEncryptionConfig; +import software.amazon.awssdk.enhanced.dynamodb.Key; +import software.amazon.awssdk.enhanced.dynamodb.model.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.List; + +/* + This example sets up DynamoDb Encryption for the DynamoDb Enhanced Client + and uses the high level putItem() and getItem() APIs to demonstrate + putting a client-side encrypted item into DynamoDb + and then retrieving and decrypting that item from DynamoDb. + + Running this example requires access to the DDB Table whose name + is provided in CLI arguments. + This table must be configured with the following + primary key configuration: + - Partition key is named "partition_key" with type (S) + - Sort key is named "sort_key" with type (S) + */ +public class SingleTableExample { + + public static void TransactWriteItems(String kmsKeyId, String ddbTableName) { + // 1. Create a Keyring. This Keyring will be responsible for protecting the data keys that protect your data. + // For this example, we will create a AWS KMS Keyring with the AWS KMS Key we want to use. + // We will use the `CreateMrkMultiKeyring` method to create this keyring, + // as it will correctly handle both single region and Multi-Region KMS Keys. + final MaterialProviders matProv = MaterialProviders.builder() + .MaterialProvidersConfig(MaterialProvidersConfig.builder().build()) + .build(); + final CreateAwsKmsMrkMultiKeyringInput keyringInput = CreateAwsKmsMrkMultiKeyringInput.builder() + .generator(kmsKeyId) + .build(); + final IKeyring kmsKeyring = matProv.CreateAwsKmsMrkMultiKeyring(keyringInput); + + // 2. Create a Table Schema over your annotated class (See SimpleClass.java in this directory). + // By default, all primary key attributes will be signed but not encrypted (SIGN_ONLY) + // and all non-primary key attributes will be encrypted and signed (ENCRYPT_AND_SIGN). + // If you want a particular non-primary key attribute to be signed but not encrypted, + // use the `DynamoDbEncryptionSignOnly` annotation. + // If you want a particular attribute to be neither signed nor encrypted (DO_NOTHING), + // use the `DynamoDbEncryptionDoNothing` annotation. + final TableSchema tableSchema1 = TableSchema.fromBean(SimpleClass.class); + final TableSchema tableSchema2 = TableSchema.fromBean(SimpleClass2.class); + final TableSchema tableSchema3 = TableSchema.fromBean(SimpleClass3.class); + + // 3. Configure which attributes we expect to be included in the signature + // when reading items. There are two options for configuring this: + // + // - (Recommended) Configure `allowedUnsignedAttributesPrefix`: + // When defining your DynamoDb schema and deciding on attribute names, + // choose a distinguishing prefix (such as ":") for all attributes that + // you do not want to include in the signature. + // This has two main benefits: + // - It is easier to reason about the security and authenticity of data within your item + // when all unauthenticated data is easily distinguishable by their attribute name. + // - If you need to add new unauthenticated attributes in the future, + // you can immediately start writing to a new `DynamoDbEncryptionDoNothing` attribute + // as long as it's name uses this prefix, without any other configuration update needed. + // Once you configure this field, it is not safe to update it. + // + // - Configure `allowedUnsignedAttributes`: You may also explicitly list + // a set of attributes that should be considered unauthenticated when encountered + // on read. Be careful if you use this configuration. Do not remove an attribute + // name from this configuration, even if you are no longer writing with that attribute, + // as old items may still include this attribute, and our configuration needs to know + // to continue to exclude this attribute from the signature scope. + // If you add new attribute names to this field, you must first deploy the update to this + // field to all readers in your host fleet before deploying the update to start writing + // with that new attribute. + // + // For this example, we have designed our DynamoDb table such that any attribute name with + // the ":" prefix should be considered unauthenticated. + final String unsignAttrPrefix = ":"; + + // 4. Create the DynamoDb Encryption configuration for the table we will be writing to, + final Map tableConfigs = new HashMap<>(); + tableConfigs.put(ddbTableName, + DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(ddbTableName) + .keyring(kmsKeyring) + .allowedUnsignedAttributePrefix(unsignAttrPrefix) + .schemaOnEncrypt(tableSchema1) + .schemaOnEncrypt(tableSchema2) + .schemaOnEncrypt(tableSchema3) + // Specifying an algorithm suite is not required, + // but is done here to demonstrate how to do so. + // We suggest using the + // `ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384` suite, + // which includes AES-GCM with key derivation, signing, and key commitment. + // This is also the default algorithm suite if one is not specified in this config. + // For more information on supported algorithm suites, see: + // https://docs.aws.amazon.com/database-encryption-sdk/latest/devguide/supported-algorithms.html + .algorithmSuiteId( + DBEAlgorithmSuiteId.ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384) + .build()); + + // 5. Create the DynamoDb Encryption Interceptor, using the DynamoDbEnhancedClientEncryption helper + final DynamoDbEncryptionInterceptor encryptionInterceptor = + DynamoDbEnhancedClientEncryption.CreateDynamoDbEncryptionInterceptor( + CreateDynamoDbEncryptionInterceptorInput.builder() + .tableEncryptionConfigs(tableConfigs) + .build() + ); + + // 6. Create a new AWS SDK DynamoDb client using the DynamoDb Encryption Interceptor above + final DynamoDbClient ddb = DynamoDbClient.builder() + .overrideConfiguration( + ClientOverrideConfiguration.builder() + .addExecutionInterceptor(encryptionInterceptor) + .build()) + .build(); + + // 7. Create the DynamoDbEnhancedClient using the AWS SDK Client created above, + // and create a Table with your modelled class + final DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() + .dynamoDbClient(ddb) + .build(); + final DynamoDbTable table1 = enhancedClient.table(ddbTableName, tableSchema1); + final DynamoDbTable table2 = enhancedClient.table(ddbTableName, tableSchema2); + final DynamoDbTable table3 = enhancedClient.table(ddbTableName, tableSchema3); + + // 8. Put an item into your table using the DynamoDb Enhanced Client. + // The item will be encrypted client-side according to your + // configuration above before it is sent to DynamoDb. + final SimpleClass item1 = new SimpleClass(); + item1.setPartitionKey("EnhancedPutGetExample1"); + item1.setSortKey(0); + item1.setAttribute1("item1 encrypt and sign me!"); + item1.setAttribute2("item1 sign me!"); + item1.setAttribute3("item1 ignore me!"); + + final SimpleClass2 item2 = new SimpleClass2(); + item2.setPartitionKey("EnhancedPutGetExample2"); + item2.setSortKey(0); + item2.setAttribute4("item2 encrypt and sign me!"); + item2.setAttribute5("item2 sign me!"); + item2.setAttribute3("item2 ignore me!"); + + final SimpleClass3 item3 = new SimpleClass3(); + item3.setPartitionKey("EnhancedPutGetExample3"); + item3.setSortKey(0); + item3.setAttribute6("item3 encrypt and sign me!"); + item3.setAttribute2("item3 sign me!"); + item3.setAttribute7("item3 sign and include me!"); + + // Create enhanced request providing the table schema and the item types we want to write + final TransactWriteItemsEnhancedRequest request = TransactWriteItemsEnhancedRequest.builder() + .addPutItem(table1, item1) + .addPutItem(table2, item2) + .addPutItem(table3, item3) + .build(); + enhancedClient.transactWriteItems(request); + + // 9. Get the item back from the table using the DynamoDb Enhanced Client. + // The item will be decrypted client-side, and you will get back the + // original item. + final SimpleClass key1 = new SimpleClass(); + key1.setPartitionKey("EnhancedPutGetExample1"); + key1.setSortKey(0); + + final SimpleClass2 key2 = new SimpleClass2(); + key2.setPartitionKey("EnhancedPutGetExample2"); + key2.setSortKey(0); + + final SimpleClass3 key3 = new SimpleClass3(); + key3.setPartitionKey("EnhancedPutGetExample3"); + key3.setSortKey(0); + + final TransactGetItemsEnhancedRequest getRequest = TransactGetItemsEnhancedRequest.builder() + .addGetItem(table1, key1) + .addGetItem(table2, key2) + .addGetItem(table3, key3) + .build(); + List getResult = enhancedClient.transactGetItems(getRequest); + + final SimpleClass result1 = getResult.get(0).getItem(table1); + final SimpleClass2 result2 = getResult.get(1).getItem(table2); + final SimpleClass3 result3 = getResult.get(2).getItem(table3); + + // Demonstrate we get the original item back + assert result1.getAttribute1().equals("item1 encrypt and sign me!"); + assert result2.getAttribute4().equals("item2 encrypt and sign me!"); + assert result3.getAttribute6().equals("item3 encrypt and sign me!"); + } + + public static void main(final String[] args) { + if (args.length < 2) { + throw new IllegalArgumentException("To run this example, include the kmsKeyId as args[0] and ddbTableName as args[1]"); + } + final String kmsKeyId = args[0]; + final String ddbTableName = args[1]; + TransactWriteItems(kmsKeyId, ddbTableName); + } +} diff --git a/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/enhanced/TestSingleTableExample.java b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/enhanced/TestSingleTableExample.java new file mode 100644 index 000000000..7a3dff2a1 --- /dev/null +++ b/Examples/runtimes/java/DynamoDbEncryption/src/test/java/software/amazon/cryptography/examples/enhanced/TestSingleTableExample.java @@ -0,0 +1,11 @@ +package software.amazon.cryptography.examples.enhanced; + +import org.testng.annotations.Test; +import software.amazon.cryptography.examples.TestUtils; + +public class TestSingleTableExample { + @Test + public void TestEnhancedSingleTable() { + SingleTableExample.TransactWriteItems(TestUtils.TEST_KMS_KEY_ID, TestUtils.TEST_DDB_TABLE_NAME); + } +} diff --git a/SharedMakefile.mk b/SharedMakefile.mk index 43c0425a0..41fb832ef 100644 --- a/SharedMakefile.mk +++ b/SharedMakefile.mk @@ -209,6 +209,9 @@ transpile_dependencies: # Since they are defined per target # a single target can decide what parts it wants to build. +# NOTE : below we say --dafny-version 4.1, evn though we're on dafny 4.2 +# This is because there's a bug in smithy-dafny that does the wrong thing with --dafny-version 4.1 +# In the future the dafny version should be retrieved from a central source so it's the same everywhere _polymorph: @: $(if ${CODEGEN_CLI_ROOT},,$(error You must pass the path CODEGEN_CLI_ROOT: CODEGEN_CLI_ROOT=/[path]/[to]/smithy-dafny/codegen/smithy-dafny-codegen-cli)); cd $(CODEGEN_CLI_ROOT); \ @@ -216,6 +219,7 @@ _polymorph: $(OUTPUT_DAFNY) \ $(OUTPUT_DOTNET) \ $(OUTPUT_JAVA) \ + --dafny-version 4.1 \ --model $(if $(DIR_STRUCTURE_V2), $(LIBRARY_ROOT)/dafny/$(SERVICE)/Model, $(SMITHY_MODEL_ROOT)) \ --dependent-model $(PROJECT_ROOT)/$(SMITHY_DEPS) \ $(patsubst %, --dependent-model $(PROJECT_ROOT)/%/Model, $($(service_deps_var))) \ diff --git a/TestVectors/Makefile b/TestVectors/Makefile index 25bc7ce21..786fe9fb6 100644 --- a/TestVectors/Makefile +++ b/TestVectors/Makefile @@ -58,3 +58,6 @@ SERVICE_DEPS_DDBEncryption := \ format_net: pushd runtimes/net && dotnet format && popd + +clean: + rm -f runtimes/java/WriteTests1.json runtimes/java/decrypt.json runtimes/java/encrypt.json runtimes/net/*.json diff --git a/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy b/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy index b2f92676d..1471408bc 100644 --- a/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy +++ b/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy @@ -327,8 +327,10 @@ module {:options "-functionSyntax:4"} JsonConfig { legacyOverride := legacyOverride, plaintextOverride := plaintextOverride ); - var enc :- expect DynamoDbItemEncryptor.DynamoDbItemEncryptor(encryptorConfig); - return Success(enc); + var enc : ENC.IDynamoDbItemEncryptorClient :- expect DynamoDbItemEncryptor.DynamoDbItemEncryptor(encryptorConfig); + assert enc is DynamoDbItemEncryptor.DynamoDbItemEncryptorClient; + var encr := enc as DynamoDbItemEncryptor.DynamoDbItemEncryptorClient; + return Success(encr); } method GetOneTableConfig(name : string, data : JSON) returns (output : Result) @@ -546,6 +548,8 @@ module {:options "-functionSyntax:4"} JsonConfig { match data.str { case "ENCRYPT_AND_SIGN" => return Success(SE.ENCRYPT_AND_SIGN); case "SIGN_ONLY" => return Success(SE.SIGN_ONLY); + case "CONTEXT_AND_SIGN" => return Success(SE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT); + case "SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT" => return Success(SE.SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT); case "DO_NOTHING" => return Success(SE.DO_NOTHING); case _ => return Failure(data.str + " is not a valid CryptoAction."); } diff --git a/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy b/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy index 7a6c67301..d5b3416fb 100644 --- a/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy +++ b/TestVectors/dafny/DDBEncryption/src/TestVectors.dfy @@ -86,8 +86,10 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors { print |roundTripTests[1].configs|, " configs and ", |roundTripTests[1].records|, " records for round trip.\n"; } - var _ :- expect DecryptManifest.Decrypt("decrypt_dotnet.json"); - var _ :- expect DecryptManifest.Decrypt("decrypt_java.json"); + var _ :- expect DecryptManifest.Decrypt("decrypt_dotnet_42.json"); + var _ :- expect DecryptManifest.Decrypt("decrypt_java_42.json"); + var _ :- expect DecryptManifest.Decrypt("decrypt_dotnet_43.json"); + var _ :- expect DecryptManifest.Decrypt("decrypt_java_43.json"); var _ :- expect WriteManifest.Write("encrypt.json"); var _ :- expect EncryptManifest.Encrypt("encrypt.json", "decrypt.json", "java", "3.2"); var _ :- expect DecryptManifest.Decrypt("decrypt.json"); diff --git a/TestVectors/dafny/DDBEncryption/src/WriteManifest.dfy b/TestVectors/dafny/DDBEncryption/src/WriteManifest.dfy index b0dadd9e8..154a4378a 100644 --- a/TestVectors/dafny/DDBEncryption/src/WriteManifest.dfy +++ b/TestVectors/dafny/DDBEncryption/src/WriteManifest.dfy @@ -35,6 +35,31 @@ module {:options "-functionSyntax:4"} WriteManifest { ""Junk"": ""ENCRYPT_AND_SIGN"" } }" + const BasicV2Config := @"{ + ""attributeActionsOnEncrypt"": { + ""RecNum"": ""SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"", + ""Stuff"": ""ENCRYPT_AND_SIGN"", + ""Junk"": ""ENCRYPT_AND_SIGN"" + } + }" + const LongerV2Config1 := @"{ + ""attributeActionsOnEncrypt"": { + ""RecNum"": ""SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"", + ""Stuff"": ""ENCRYPT_AND_SIGN"", + ""Junk"": ""SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"", + ""Thing2"": ""ENCRYPT_AND_SIGN"", + ""Thing3"": ""ENCRYPT_AND_SIGN"" + } + }" + const LongerV2Config2 := @"{ + ""attributeActionsOnEncrypt"": { + ""RecNum"": ""SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"", + ""Stuff"": ""SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT"", + ""Junk"": ""ENCRYPT_AND_SIGN"", + ""Thing2"": ""ENCRYPT_AND_SIGN"", + ""Thing3"": ""ENCRYPT_AND_SIGN"" + } + }" const MixedConfig := @"{ ""attributeActionsOnEncrypt"": { ""RecNum"": ""SIGN_ONLY"", @@ -135,7 +160,10 @@ module {:options "-functionSyntax:4"} WriteManifest { var test8 := MakeTest("8", "positive-encrypt", "Change Sig to NoSig", BasicConfigSig, BasicRecord, Some(BasicConfigNoSig)); var test9 := MakeTest("9", "positive-encrypt", "Change NoSig to Sig", BasicConfigNoSig, BasicRecord, Some(BasicConfigSig)); var test10 := MakeTest("10", "positive-encrypt", "Complex encrypt", MixedConfig, ComplexRecord); - var tests : seq<(string, JSON)> := [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10]; + var test11 := MakeTest("11", "positive-encrypt", "Basic encrypt V2", BasicV2Config, BasicRecord); + var test12 := MakeTest("12", "positive-encrypt", "Basic encrypt V2 switching1", LongerV2Config1, BasicRecord, Some(LongerV2Config2)); + var test13 := MakeTest("13", "positive-encrypt", "Basic encrypt V2 switching2", LongerV2Config2, BasicRecord, Some(LongerV2Config1)); + var tests : seq<(string, JSON)> := [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12, test13]; var final := Object(result + [("tests", Object(tests))]); var jsonBytes :- expect API.Serialize(final); diff --git a/TestVectors/runtimes/java/decrypt_dotnet.json b/TestVectors/runtimes/java/decrypt_dotnet_42.json similarity index 100% rename from TestVectors/runtimes/java/decrypt_dotnet.json rename to TestVectors/runtimes/java/decrypt_dotnet_42.json diff --git a/TestVectors/runtimes/java/decrypt_dotnet_43.json b/TestVectors/runtimes/java/decrypt_dotnet_43.json new file mode 100644 index 000000000..38eec4f36 --- /dev/null +++ b/TestVectors/runtimes/java/decrypt_dotnet_43.json @@ -0,0 +1 @@ +{"manifest":{"type":"aws-dbesdk-decrypt","version":"1"},"client":{"name":"aws/aws-dbesdk-java","version":"3.2"},"keys":"file://keys.json","tests":{"1":{"type":"positive-decrypt","description":"Basic encrypt","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAHpRZMCvsBiN1o9H3rJfoveL2zGRhvp5HA="},"Stuff":{"B":"AAHy84wRRJP0EPgRJkjcdyRm0r4PuP+PkvTy"},"aws_dbe_foot":{"B":"5KNO42JCAx/OQUkB4WbC40hK5BfoeeHzkbMs8KmXBDuwfEhRZLgmt3bZsehB6zArMGUCMFpdZcPZKW0DIbnJXCuB+CCwrBjuF+IAl0eMNuVDZ3G9JslDSOS+p0RI3/O7oem0qAIxAI4b/mtQqK+CeozFr9I1/mEBrIgNfZY2E1Xf8exVfjkqEFzpVC+Hc2GBg0VZtXFj9Q=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQG/AEjo2KJX7iczBlT381h00zGEg+Gs+f+QakxgMskkrAADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQW10SWRlRVdoaWhDWVlkbHluQmQxczc3Nml1M2ViM0lBV1JkQ3RVT0NhSGpOdWpmT1Y4dFZsUS94VXVNK2FJZ3hBPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIydukpphN/Gtvqm43ZMAAVJjwneWXUdy5VRcpR6xKU4rzR42tOKYsd2Nx57wiHFVFrPiY6Mw8j1AfdP75Tb7kyBoKtHQ03NbqG2lkOQ6c4Yo+21QnKfhhyst5l/9kB1BrqTWEYDymPFu3tiBgTOSAWtIu9dneeEq44LpA+PshPwI7BjS2F3S1/Y2deOMS2Py1GrWTY6PodVdd1Pu6CRYMI6p3qC8RinLwoPUE7m"}}},"2":{"type":"positive-decrypt","description":"Change ENCRYPT_AND_SIGN to SIGN_ONLY","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"SIGN_ONLY","Junk":"SIGN_ONLY"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAE2/tY+DLjg08YgwXbymMxswrXo3ntW1GI="},"Stuff":{"B":"AAFpvr9geyq2TWV1vUaYJ4iETDWU+N89mPHH"},"aws_dbe_foot":{"B":"PryE45wJXGVJp+84qIQ4YGX7tzI7Qh+7ErDSZy96B78N7Z2SV7PUX2vpLOpsSmxzMGUCMFfWEnDRTMdFhg/C5O4AHeQF+Ek/Nv+9v9L3eOcpJjfli5mou7r3vQjCi9KgbkjZ/gIxAJ6QQZEc0kqhX5bQNrep27eXhnQZCG9CzhTFktr7lnScIWnTSXm3jqYWOfZzQbzARA=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQHz8eHCswnJhdDrILS/UCRm9nLRZu4pPYpDw6O7/uVyCwADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQTJ3Y0U4UGprV3Aya2xseWg3SE9TUTYxcmo1Y1E4S2tVOWt4WDFOc3c1WHZKbDhmSldMV1B6cDQ3SUt4VHpYZlF3PT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIydnmScGl5biKtlrtDqKdK9Muk3ALsRbNgBy6TMcjy6ZO0zPsh1CCmkNfbECeXw4U3Pjiw7AxpXkTU5l1H6TwCtszs6LhmtQXra7Nvl6c4Yo+21QnKfhhyst5l/9sGK8AzbGXTVAFpg8wP0vewmqTeQvfKa0onMZHZp4P+Vltdie0oQHkowMnfg8kZZ4XTS8kW2ywp56j8I8jLIgzfhu1MFmOXZuhbqGwT0Gi9L"}}},"3":{"type":"positive-decrypt","description":"Change SIGN_ONLY to ENCRYPT_AND_SIGN","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"S":"JunkData"},"Stuff":{"S":"StuffData"},"aws_dbe_foot":{"B":"mVfQsE6pfrqiM/LX2kZ2ZBuIlyEIZ5ZZ6WmHLoGlhHihdwqD/mcDvvqlYlz1TPEcMGUCMHgZXcV0IJTAOnpsgHxtXmVf4vDi5G529G6KjVlvQmmhgXFALVkzmUMj1PGM86F8/gIxAM7+oR24Qqf/MGdbwUaE+SMOTx3idgjT8w+1XUv95uvbGFFy0h2j+W/D1mh/NzU90w=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQF9PlqLoh0sC9H/Ous4LveUBcQNb7fn9oieF+FNFwbndAADc3NzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXd6U2R5R2M3eUwxSHdxQjd3Um9uZG9uTmJyczkvWUZTL05NS2N0c1lxSDJhSkJDTnQreUtVWTMxWWJDaDlqOFh3PT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIy1Yy6/XSyR3P3AWKUmYauFxLCtdHUtfpeoWdPrV+dktxUNJ1AV8U6BQYqYzRnNvMscGz+hgrSHWTZv+X6jQmZvydx5Zfs56oGnGSvo6c4Yo+21QnKfhhyst5l/9kkNzVq9oWM7qapMkw6vClUyJnej3F7ddOvu+6NPZk7zPFvWWLH/gefy4E+bVyrwGqN380HwcoWlqOZcMABfN9iiFPSi7e5Ci3ST2RGTGGcq"}}},"4":{"type":"negative-decrypt","description":"Change ENCRYPT_AND_SIGN to DO_NOTHING","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"DO_NOTHING","Junk":"DO_NOTHING"},"allowedUnsignedAttributes":["Stuff","Junk"]},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAHcRufWGLV4tOINkP6xV+pKQW8358kx6IE="},"Stuff":{"B":"AAFAvnG1zYE0HVZRBD0dbN/D7MiDEgkY3Pi/"},"aws_dbe_foot":{"B":"hQ9pOiCU+/4KT3LmSdSpuqcKLCAbSlnwWpUuUCJHLdvr5ZnjpBvDIhMMwOhKU+bGMGUCMHRRoYzL98xhahge1jt4nrGCvL8eXTtd12aR4YiV951vSq32mxenD6bdSmFE/DDZDwIxAKygt6bhyrwel0UtSkEsJwaChhy2sVC9zcMbeeVDCF+JCzhuNwDfv9HdV/GhlfLHXg=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQF1XfTHwxOQOgISU6CLkWRL0Bimyh68uuFlx7L4FSga7wADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQTFNOVorbnZXVy9DRTBUVnV6ZWdSUEx0M3dDUDAycGJUcVU0SjQ5ZU9qQnc0b0xDU1M1MTJYRmEyREFoOWtIVWZRPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIzrDB+y1n04/WPUGy/k4XepdFSdarS1yUBUul8C3zXHwe7K5Hv4eueA/nQm3M3C7IJ2Msg/ZKXaEoyZiZ5P788a0abBwO7uL3hyb+rG6c4Yo+21QnKfhhyst5l/9u0Q5yM1PBb04AvCbz0QZnc6loJsFMiq1RpVpiPBJyZ+CQYNdR43Ciq+cFW7HWDZv95y6KcFhslTRn4+39mW3WgvIULPbTxvj1wgSLsGjt7b"}}},"6":{"type":"positive-decrypt","description":"Basic With Sig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAEjddd9xvPW4Q03LeA9Zmd3anZoFuqxSBs="},"Stuff":{"B":"AAEBTgRGtiPhqdeRgIrKBwcr9s4MvhBtiBX8"},"aws_dbe_foot":{"B":"Vs0m67U7wObvlv+LbmPhikMkuWxI15MKSg6gQC7dQQ0msH1xpDBt5WIyR3vTe/9nMGUCMQCKpb76QGOB2Ub+TfrZXawsgx1mw10qH3uSCbt07e32pan4SD8zjdetg8fsmuHFM7kCMCbN3rJetpF+LmjAdFzeDb5T7gM9ASVDLBaXdiliDiSz30ZY2d4dada+l2nodmim7w=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQF80niaf7jkx9DAitIPhmkuRPJW7/TVgDFnVLCW5sq0AwADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXplTEpxaTNRU1Vua1djcHpvR015NDcwTUNtZi9iZVR6eXc1NWtqOGdQMzlnOXlwZ1lNZW5MVjB6QWFQWFkzVmdBPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIz3buO0wRPbS9ESheQd1V0CiuTLmD1nCCG8BPyp9IfZx87p7sGwOCVl71QmOlbM2UkQOW438x3+EDaL9zfJ/A9+t+zznmnagX8/Saht6c4Yo+21QnKfhhyst5l/9g/f2v7vOQ+cqBjznL36smGLfIp5PsH3nzNUNWLIIjMej+K5/9nd1RGWXD611ZwjJqhAC7H7Cve8Hnrpvt07Gn+Qn9uIyYXhnQxBe40ywaPP"}}},"7":{"type":"positive-decrypt","description":"Basic Without Sig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAGOGOuMwJjLBg77iaxhncw7nQjPNBz5ppc="},"Stuff":{"B":"AAG4k7CfxQlrDlFJ6z09XHh9K4hebQWsmWvN"},"aws_dbe_foot":{"B":"VrmNl182mJo0RlmSxE+w7JhaCZLb7LedUKRT3jyR4TBalchV1luYOsoFF+hgKMJX"},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQD78917rcJbvCswFcEc6rvKUTdwemdKiMk0Hkk4UzLABQADZWVzAAABABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIzwZaxbkUvDj7epL0K4ESMcvZhLaJY3YkW7aZRA4qSJxKXDjqp9wtZrMa54uL3ro6iFMqVBuU6fB+/6wikUj/D1pLjKZ2wZwr3hc4vm6c4Yo+21QnKfhhyst5l/9g2w/TjN1XmmpWU/KqZc9OcGuc4uMiC0/CImHRlEjx4O/HMxnbs3MRLeV/8rscVWbPTZk+zp7EyPv5NrZYIhHtCSJViSLjxsUlUORGrKcd4i"}}},"8":{"type":"positive-decrypt","description":"Change Sig to NoSig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAFocpmEr2rxTKL3TE10/tiN+CvP40m08JQ="},"Stuff":{"B":"AAFvtDBaVqG5/xWliM4Ew3FPs3luIdxNe00S"},"aws_dbe_foot":{"B":"YWO16/tE21f4Y34N2Jo1I63lwvuR0g2J9j8JqNoZYxHHQwiy0QEZawfnAz92z2DOMGUCMQCSAGJ87Gu2GczUooNDKT8GWjzW3Vs0eVKxZVzan4ZzaKsTXKu2tniHFU3A/4ir5oICMEzp3T9sLt6MlZsYqKsIyJVL3AuGUAEyrYeSqICsiNe5X5bQ+rLibR4E9ezbWNYbVw=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQEXr2b/QDpfw0iTcxn7g4D2gHxSCJ7YIc0W/iux+Gy0MQADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQThlVHR3QytGeTJ2OUZuMDczR0pjS2RaQmVWUEozYkpmSXFzRVZLektzaTNxNFhEbVRPZzdVd0dTMWhRVXlOeUpBPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIx5Xe05KFuMV5yWyMj3fdUtyKYW8Gxb0l3fACOkrWILUt3VqZ9Pn0eL+/8hRLiBQ+OnuLKtQOOt3MZnWWC2abWtM2vshU7cD7ItyvaM6c4Yo+21QnKfhhyst5l/9tCw9nEENLKs+gYrLMePMpC9or3ARSFzvYwuVfjRj7ppzWSojV9IPd3aKGwAaJiYnFoIhhwA2l1xjTDTFwYhmLgYB03gqG4bW3KpK9LOvFlb"}}},"9":{"type":"positive-decrypt","description":"Change NoSig to Sig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAGY7loxA62J/K6q/yo3bNvM6Cr2hTutk7U="},"Stuff":{"B":"AAGMl8fOusAfneylPeiQzaGFzvNurF2A2wNZ"},"aws_dbe_foot":{"B":"nZcDgWeLNun2VyNpzlgNmp06v5vlyVPNrMECRq2Ki/cAC4sLU7VbTQOlDpjuyosL"},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQC370weuAZ8BdIWY0tOLyfagaihQbqYFml4iVoRSNYN8AADZWVzAAABABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxFAuFeyOHLvKTrU92fkKRwG1eZtB79lr5tYZrnkRtt4HSlHxxYCP21U7wZGSvSvbw9sk4FWEcjh01lGA/2555pC965NJEUO8tX9pjY6c4Yo+21QnKfhhyst5l/9gOwqzImNrPQmskip+SCok4tso+YT6ECAa7Lwrl/97cgcFkUifelp4F3RxSPtHVUpayFWTlqdUbe/RNR+XDtmGG677Bsf6TSB9a9UemV2qPL"}}},"10":{"type":"positive-decrypt","description":"Complex encrypt","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"SIGN_ONLY","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":{"L":[{"M":{"A":"B","C":"D"}},{"NS":["00.0011","0000","2000.000","10.01"]},{"SS":["00.0011","0000","2000.000","10.01"]}]},"Junk":{"L":[{"M":{"A":"B","C":"D"}},{"NS":["00.0011","0000","2000.000","10.01"]},{"SS":["00.0011","0000","2000.000","10.01"]}]}},"encrypted":{"Junk":{"B":"AwB+O4bHLCe5HuPf6rA5MmPQXZlJ5753sNB+NWLrxHuZpFS3zajfgvL1+9KbkPCc247DDLnkQmtqfZSpSFuwdRRzXDtl/IJ4xqabBiWnkrze69GiUByTKq/0pXqET2jQpkOdl9j8PlvwKmVXDEtXNMzcYEGrF1t7S6XYRh5jNEIqSZpLjK4WrEWV2Z4aQkuy8kD1h/l9dKs="},"Stuff":{"L":[{"M":{"A":{"S":"B"},"C":{"S":"D"}}},{"NS":["0","0.0011","10.01","2000"]},{"SS":["00.0011","0000","10.01","2000.000"]}]},"aws_dbe_foot":{"B":"BnJ7Ima1tKUNNYJj9R/jBAl0YpENtK9y4Zkgb871oX2DCJumsm7BmS0q2vkY9TpGMGUCMDuPGqIj+Pr57+qVp4v+iF+/tMwOMkVjtortG9/AsaMs8n/b1UJqcLZ3ouvMK8Q+lQIxALsRkKj0thRfriqUyO1BvOPemvLATPko/EAg63YFAey4bM6hTI2xRfTQixuUJPr7yA=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AQHXyXKVECR0ldy+ldf6naXFDU9zcshLrV5jD6Q0nPDdlgADZXNzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQTZrNHd0ZHlJRllZM1BRN3VCQ1BlME4wMnh3bndNYjZXSHdJYXRlc2pWckdNVlBMWGZ4TjM4bllmeVkxbnJlY0xBPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxSNgdM2L9Z+gkhC1doG82cQnAVMfGLuB0s5y/cKAYB6b+T2hsNeidXxiEJdqNi27a8K+ewFDgkeVo4icKSUjIlQUsvFmkCJO+3W3eC6c4Yo+21QnKfhhyst5l/9uzQDznp3/RDR3ZYpvNQe+CoZbChyhL6KGhHqJCk15cZiw4b1WHqmahD1CI691PyV2S654GGUDnaLaiSoI3soLVMa4hs1RdfdC7cFBoetCIK"}}},"11":{"type":"positive-decrypt","description":"Basic encrypt V2","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAHrO5wSb/Od3c7ia7gNctn5Wv95QYvM9bQ="},"Stuff":{"B":"AAEdf706YFKMcRwBLicdjHWeBkpy7iSagUcw"},"aws_dbe_foot":{"B":"uC+TZyf/SA5NUCEwrdLt2pDVT0nX6/3DAIJg7ABMc+cBsvX/6fE1q/xZw0qNQzYIMGUCMQDN9qZxrnfMCRIs9RmfhQQfHXKsWyh3rKza6itiUQTD6S+kSvEBew67DAqg8esHrzoCMG9NbvUih+96bh5zUwFel8/9k//JtdSTchoX475oCGQgvgxB+QK8SXIl0U0Fm4HcNw=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AgEN4lJWxnI9XjNnX4DgksXFIouqoNsbdxdiLw2ykAuJkgADZWVjAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWwyaDluc3R4OVJmT0w4UDZva09udFhVbUVvZitTWG9keEhIbjhubjVkM3NVN2kwbkpscGNHK1VpUHNReDQ3UzZnPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxzAf/JJkLmnUC8Af84T3CWKaHy7E/GMmmgwC9xyuyozUcLk+6XcDPOKJBnXPFZ4aLhYUmOne3jB426B9/ipWURfrY7lG0kGz/jJIMX6c4Yo+21QnKfhhyst5l/9qX5XwZEdr1esW1Egt2ELgh1vjPCm/gjwOX/xaeaLHVxJXLTkrlqg1OElJHCcT2/B1duYfKYEEWqWwihRmmUMD+mMZuYeVEKtIKcQLjCOKrc"}}},"12":{"type":"positive-decrypt","description":"Basic encrypt V2 switching1","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Stuff":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Junk":"ENCRYPT_AND_SIGN","Thing2":"ENCRYPT_AND_SIGN","Thing3":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"S":"JunkData"},"Stuff":{"B":"AAFP7BvEA42I12+FHv04GQmDHWrLgwjgGwD4"},"aws_dbe_foot":{"B":"qEfQAWRsKeIAUU5redKgNbZpCU6vGXDlLXw/Xd+Vx8eTUOr5bZG4Amdtl0C2Un+YMGUCMHI48pq5qWtc1vmIkKM7A2L4VPoOPasyzuymjv4iGidGWYMNaT5knMn0C6F8cN/OVQIxALcnsb/VL2W75Oz4AGVePmRmpuzfYdSZvBVMNJEozcjtshR92BJaSMo/ts6GyztMgg=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AgFJTw85pEhhRs/sSZybsm/oL+kgZi6SIrEFImIniT06YQADY2VjAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWc5TFppZnBweWg0Z2FOd09lWHhLeGcvdDY2bW1obHA3dEJZdnBsakdIUURaSkJRdit1Ly9sQ2V4RWRRVmhIZ3h3PT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIza1ec+PY4fb11gRgrFFL/3OMkyi5+4OBufAJClhjdpEuIpd83AmHWrvZHwppRmJrdj6eIyN0ZASshAaVDW0JoV8/ua8SkZNGaEgdyC6c4Yo+21QnKfhhyst5l/9t05KvkbCtR2hBQCO1YFWn6ZwOpwHBbFcu4PqTOP1uUitqO2Hi9xJZ6FsOr0yggFx0fMjPpRA1rv6c4M1xvPYHPIJnu8x/i4TnMtiLI3mF0O"}}},"13":{"type":"positive-decrypt","description":"Basic encrypt V2 switching2","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Stuff":"ENCRYPT_AND_SIGN","Junk":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Thing2":"ENCRYPT_AND_SIGN","Thing3":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"Junk":{"B":"AAH0jXZWLDRSt7Qw2SrWWkYf6CbJWiBD+0c="},"Stuff":{"S":"StuffData"},"aws_dbe_foot":{"B":"13P6KWpITWxtw9vHExhcc3NyHec51p6TdF0Mmsy+2oXRVtPsNUwxKD/qRIqsLyN1MGUCMQDXanXK7zbuaKyoph5jKDm+7Dh3YNrfx84v/gIU5F/RtwAStxCukI6kNMWCos8uMY0CMH2mruL1/gKnPmt0ZgXu4hUQqwvoyarp1spOQCEnUKP7pvm4ibxAwDTFF/LDy8a85Q=="},"RecNum":{"N":"1"},"aws_dbe_head":{"B":"AgGOA1CgXVuYb/+88MOed9R4Vw3HcXIaH3quIHW8uvmhOwADZWNjAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXprNEY2MFkwQzVTVFhxOFB6cWpLODhUYTlaVmpDQXA0cnd5Si84ZTVXQjN6TFJJWEF1eHJEWUhxM3dpN1NUbXRnPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxcjN92i7r4KE98vAeW5/ZpG6O8mV1BiGJtyYbSTeKfQb1S/HOK0xHBh/UlkzbYHIVeYNL3wis+/nugGX0IerC5N7S0tj+7u03O11Z16c4Yo+21QnKfhhyst5l/9s5Z5IOEyovdyvf2K9kOedMF33KgjFOjZszLSX2DBJXvoN95eZvERXAZ0CIGdYLF2FOqLtP8iA5Dr2B0b2Y3WqetnG/1SiojvXNrTcJ5Ijxg"}}}}} \ No newline at end of file diff --git a/TestVectors/runtimes/java/decrypt_java.json b/TestVectors/runtimes/java/decrypt_java_42.json similarity index 100% rename from TestVectors/runtimes/java/decrypt_java.json rename to TestVectors/runtimes/java/decrypt_java_42.json diff --git a/TestVectors/runtimes/java/decrypt_java_43.json b/TestVectors/runtimes/java/decrypt_java_43.json new file mode 100644 index 000000000..cda6045f1 --- /dev/null +++ b/TestVectors/runtimes/java/decrypt_java_43.json @@ -0,0 +1 @@ +{"manifest":{"type":"aws-dbesdk-decrypt","version":"1"},"client":{"name":"aws/aws-dbesdk-java","version":"3.2"},"keys":"file://keys.json","tests":{"1":{"type":"positive-decrypt","description":"Basic encrypt","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQFAgVq8gzfX5sC/+PMKC3l30i2s4DgKuZkIs+wp+XRp3gADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWxGUnBLZnJWY1IxaWRqWi94a01qK1JCV0dPSXJjaUsvYVpXbmRsWDdPMmovN0xqL2h0R1E1cE12YW1mV3NnNUlBPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIzWMuXpi4KuMBywAAjuk+5Vt47JPk/N0HI8/oEmBgxoa+vWZdqBDO0oxIMAOxnQWC9Wa+yv6/kot+odFTfkKCBJYuxM+VAs9wxSiDep6c4Yo+21QnKfhhyst5l/9mlO8KgVijihXgMJrDK8GGCQ6neeezz1l79uCm/KLP+t3NW7ACH7HJaGTnLLoTi2r/IgbS89PSyjQH4VGN3DSHsYgEME/MpC5SPmHOcn1ejV"},"aws_dbe_foot":{"B":"A/OBkDZ8hxyaRmb64bRg/3DfM/B33eVnkd8ewJHfOV/AeS9JUTG+4kGc8wJMvthqMGUCMQD4TUJ4RRfhdsRv2M4hCUpUe7JeVN9CzhxN8o7n+zzCR/skufL84Wm37Ap32AB08FUCMH8Axm2qlJao86Q+R4oOegyLgTJ04SOv+t9L7EMAo6SBZChYfTLWOUigdqGzE4X84w=="},"Junk":{"B":"AAEOM1xBOUHdb62FOGxefX6Zrpe65V7Q7MM="},"RecNum":{"N":"1"},"Stuff":{"B":"AAELAO6Oj13FOHjOJJgAYtXykM1QCpCmlVkt"}}},"2":{"type":"positive-decrypt","description":"Change ENCRYPT_AND_SIGN to SIGN_ONLY","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"SIGN_ONLY","Junk":"SIGN_ONLY"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQG/VCogzJyImmWc9MoYZRX8v0hKBikgd0reHfPxVgXxUgADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQTJTdXZIYnFvanp1Vm83VklBWFE1QXJpWHFlTk1qQVE5TllZVXFSL09sQXlTWWJETmNkODA2cVIzbEN3Z2RNb2RnPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIyfqP02NnoLVdKfaFAjNPtW8JEdBeptHGXbusB2mHOdo3WFGb4bpXSsV7v7j7jIZQknWhtOqbwNgX9mUvZtcY13J7hnLLAw8Myqmtj76c4Yo+21QnKfhhyst5l/9npyTXdHhNjaPBRLitx05jewSi89407Yh1lpf1lVD3WtDhdsb4XsN5674QRyabZ9CcB99PVzSS8YNZF5nWs4AJhV6eiA4sakOoI/pTAlYwXX"},"aws_dbe_foot":{"B":"eHcXj2rE0iJb/xmV6pkcO+fAv4VuhtPkqQBKLgbCzwx40BLlEc9Emib7kK3ilb4PMGUCMQCfPv6GxID//Opk6ZIXTr18znZRGadFyJwTZfa8Fo2VVKpEO+lkpql4IoXkMZSGa2wCMGAU98WptFRDgswJO6GjoRZSK7gYpotnvoz9Go9n21e5XwXcXJ/WMO8yiBnpslKxiw=="},"Junk":{"B":"AAGXNFlPo0gWH1oZwFvO0eysazO38agRTI4="},"RecNum":{"N":"1"},"Stuff":{"B":"AAGxH1zJg9aKRX0JHJqaqPiX8dNBKXSa/01Y"}}},"3":{"type":"positive-decrypt","description":"Change SIGN_ONLY to ENCRYPT_AND_SIGN","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQG8MEqxkm7bd2EaGD+yg7AUfg4N9SN8oxRAltkDeS6KIQADc3NzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWhFWFc0Ny9ISHlaK1hGa0loTVlBODBkN0RDUk9rUTU0bk9hVzRoTG80ZE5mcUR6NXBZSzdSa3dVVTgvYlRxcDZRPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIztg+VpFJ959A3jG5ffZkVsNXIMDwERhZf1w3NAWH+yBH4suA1vcphhjutd99rT68d6PTVh+TSV3fhWumQ5K8oxeGF1qxtms9Z8hvKv6c4Yo+21QnKfhhyst5l/9lFXkPLLJ43piFyg6TsQsVjH+agwXAiHX/xg/s3HCzx+BrIs0skb9l5kZbKdSip2ZY7zRFFhOujy2NsqpGcli8ZMSD/GcPIPYgBOkVeWzypx"},"aws_dbe_foot":{"B":"69fxS4rxWh17JXbvhYSc8xUiZBN6nbXZ2xVA0xaKYUTMn/FQIPlzY0WIrMyH+ibtMGUCMD7T7Fim+aaAVJMYPjM1DvdFtWfdfOBgS09grqMJt110ESjOyXrYgMljqpk4UosblwIxALVctFgvki3MiVHiFiDVGPFUFmxx9U///ReKdGe4tUCH2pctGbRhe3WEu9SsTQUQXQ=="},"Junk":{"S":"JunkData"},"RecNum":{"N":"1"},"Stuff":{"S":"StuffData"}}},"4":{"type":"negative-decrypt","description":"Change ENCRYPT_AND_SIGN to DO_NOTHING","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"DO_NOTHING","Junk":"DO_NOTHING"},"allowedUnsignedAttributes":["Stuff","Junk"]},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQELJ+0zHJAMxVDNSqT0EyjSf8nQ3oeilcNWHOOmk53g6wADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQTA5dDFpajV5ZWs1dTN5eDQrZkJ3bmVqVEdZYmY5MHZ5Vi8xTGkxWVVVYS9wbFdTMEpTa2hXbWZQSnA0S0ZuUS9BPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxqss+hgR6fZUpeV2jHOyGWU+c5c3dROJ0R0X+/nx7rk7CXVjBdlKi5goHxnHPCY7WIshzTKjEUKfvBJD/P/jwourXlJdsjcOwLCBfD6c4Yo+21QnKfhhyst5l/9s2XpIYlr92GizqoNOWw4Yw16rwbAKY1eds0LlcDSa3KRb24fw9Oj7MdzkPhqLl+VsWP5TOgIu7mvNi24QHJkHA946PIOF/fHFBosuFrgM3i"},"aws_dbe_foot":{"B":"8zHl49lzwNYzr0WNkdnx46Vg7k8CSiG1WBinX6OJxcvYW3K2lx+elUQDctEghdAzMGUCMQDBEv9UP7jWX3SJPt4ERiqnzDG53JyeA7KnOF+lyTfr7oHrhLyLdiUWhnjHZ4OWWTsCMBIerEtaop9bg4ZE0frj1Vky9zXzT5m4MYkjDpLXybRQWjJDFaZeAV04m+BdzdGrGA=="},"Junk":{"B":"AAGqvkxpd3l1tnd1scpRB7jybyMxVXWHGKk="},"RecNum":{"N":"1"},"Stuff":{"B":"AAGtmrA8m1il2inLuKp/ZyXdrtrUpgbQ4kWx"}}},"6":{"type":"positive-decrypt","description":"Basic With Sig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQEsnPpvnTrrQRKwIyVxBlm9pz9r+6X0cVYjLyFqWMa9EgADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXpPVWVTNUkweXRoN0ZtZ0F4Rm54cGI3OFdaRFpvUlYzTjI1UEswaHJGMFRPZ3VRZVBZZnF1UE5uS1lrNExKUVlBPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxMfeJbt6tOaXxrvzadZWvZUvJai36OjPyQPzPhSmNVRxVjxvRXSWx2qQZhDXmC2R0zuj279wSHI+fSsy5x9fk0RgbQj0OSrEGEqHeW6c4Yo+21QnKfhhyst5l/9gaUYmxZWz7jN4sO1Z15nPvcesIsNWrFtjviARwtD+pIX9nnzt5sZXPoe8Y+zFnYSmnzb02AasDfRXI0IhCGDlaBYmztQeOIRQml1E79BlS4"},"aws_dbe_foot":{"B":"OGm5/7mdsDc9yxJDJqCZ90CR/Jp+p8rYgxXAD/fRjWOjXO7Z4F3NGmubtnLxsI+3MGUCMQDm5Oh+ZU4aodFt7bFdrgqMzCa3Hds1epkSENXaYf52UtKPvl+bWSA1pVn2a9ZS2TMCMCKsRjf88zLHvah2iqf8rJmYoDowakxzWBFS49HU6s2zr/aYs1BLkOTqPbP0vex3nQ=="},"Junk":{"B":"AAE/DrvjFyqgmlm0chtieZ2tJJoq+Dp6bfs="},"RecNum":{"N":"1"},"Stuff":{"B":"AAGVY67K7xrRIi1Tyu25o1ag/81ZggYTR08L"}}},"7":{"type":"positive-decrypt","description":"Basic Without Sig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQC2PSzNyvCmbtmjonH6x7TPhf09r6ALxoRShTmiyTpqWgADZWVzAAABABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxzjRiXSSeXP0WfB/CM8TiRs/G4azVIICM1F2qNUKixS2bNtSzOnJhnjPc7XE3MzKprWnuBD99Eu1CKxcyjpvSbMe364w1aH0UnVZlJ6c4Yo+21QnKfhhyst5l/9m8T4pq3MG/9Pdf+QkUzcno4e3cQKXY2HmD9qAx9rDrkM/Bu+weokU6VpcGMNqg/hEM0q7acXVXw+S/g2hiMk5aGR0Tv/yG03+/MQycTiD5L"},"aws_dbe_foot":{"B":"c2YnGyTeBwo0F+6mi99Bf0x/qHDnLTh7mFBFRlsLjCxDKH810ZkgmxWxdx90IOHt"},"Junk":{"B":"AAEr1qO+6aLkBW2LJMWUhznkcpK8ufdenvs="},"RecNum":{"N":"1"},"Stuff":{"B":"AAGMYIotI2JcvXBegAmWC2PPKhonxljFq7SU"}}},"8":{"type":"positive-decrypt","description":"Change Sig to NoSig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQHWH8rBCqR8bZV7iJl/fUu/+VYc61PNgU1flGrG2sDrpAADZWVzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQW1yWlNYM1lxbnMrTm14cWd5TjAyd0FaeG5UV3VLZ3REbUZya0FhTysyTStUMUZtdVZ0elNVYWJVUE9LRjUwMFd3PT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIw2cd00IQk/GurwSuSj+yE8PHbo+pcpEZ2y2gw3c6iqCZ/WaSQIakKtoMdkiJ7R8bkQpeS+Fg1St/BfDwU8K9BWXfLojjIA16rniEuf6c4Yo+21QnKfhhyst5l/9jSqrAcf6W9mfxaUSvOvkNCik03n/l495Z7FPPJ0hDMV/MfMSjK4VxUWW7LuuxbH2Mu8/WAHOYCSGuA0qVeul43yj40R6VAFIRgEuCBkxDwY"},"aws_dbe_foot":{"B":"NL8IHu9mVK6ZN+EPCGPnejCMLSlFrxx+hvw7lh7QBo9Lfi48L/Mi+ayAzaGy/jVwMGUCMCZizFEreaXF6qbzmlsO4uF8QejNEeCe45v0KQEjdYiNue1CAtjT0Xz5Di0VYyI5cAIxAMqW51wg8iEUgQmpDadjigQ4x0FqkwZqkd1pkaUT7QqBFia7DCF9ldF08/vG6dcbwg=="},"Junk":{"B":"AAHr7ZInIO4kCJpRr1g7n6d7Ij8DDykgN90="},"RecNum":{"N":"1"},"Stuff":{"B":"AAGZBem17PLRuQuLpOPtE/LuQbUaJnARyPZ1"}}},"9":{"type":"positive-decrypt","description":"Change NoSig to Sig","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"},"algorithmSuiteId":"ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384"},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AQA36EOubGwH2b8oXkADXMqUfZkgI5Csly05WuWliGIbIAADZWVzAAABABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIw8gckBxVvl0oVO+Fye0kgI5UwzM793dPIAiTJ2vcEgc1UWKr95EVwRkkWX8ChKd65cBoWyw/TplG54/KdmC7HQhOft3ZCmXC0N2jyR6c4Yo+21QnKfhhyst5l/9poEP1B2sOXHBQDONSoa3RoMQBJnq+mlX+Y33bGCAPl0zrMiKLhXJeXv3tDVXCMg7W1K0Vf9VTD3GO12ifsFPQIDTebUjG7BjWL4SyNgig5T"},"aws_dbe_foot":{"B":"w3JOTu6nUr/U1sIKTddWDAV7usVQqWwkdoyWXVRzgN6qJDBDyN6vpIEcu9DH+ew0"},"Junk":{"B":"AAFXvOlM2/1+Y19ol9RXUs99H5wDESilA2M="},"RecNum":{"N":"1"},"Stuff":{"B":"AAGJ+HsEnSJoDAEJ7dIIPqF/cIGSUqvnD1wx"}}},"10":{"type":"positive-decrypt","description":"Complex encrypt","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_ONLY","Stuff":"SIGN_ONLY","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":{"L":[{"M":{"A":"B","C":"D"}},{"NS":["00.0011","0000","2000.000","10.01"]},{"SS":["00.0011","0000","2000.000","10.01"]}]},"Junk":{"L":[{"M":{"A":"B","C":"D"}},{"NS":["00.0011","0000","2000.000","10.01"]},{"SS":["00.0011","0000","2000.000","10.01"]}]}},"encrypted":{"aws_dbe_head":{"B":"AQEiPLfEqGvgAdY6ZeWZS6LcRTO9Ngi1aJ4R/kZDBtGMFwADZXNzAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQTByREJDaExtVXdka3FjbGl3eTN6TVJ1S1RhVWhnek9zaU1WSTl1ZGpRNWNBd0hYV0F6Ykx1dDVvV0tRM2VwYkx3PT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxlAM/xUgcbCXjCEs6EICAEsJ5F5WStwbLShYwf7ixbrzQ7I2GZtLT8GQ/WgIS0WBckpka1e5N560USIl6jvJB1Y//zHmGAKKNZLu4f6c4Yo+21QnKfhhyst5l/9sXBDQE8vpLqDOxiThbRPbtDUEyU1fD7way2e/NU2e0dDOnjCFlyroMb61ZqNNLk/PtT5i4crfnSKtdDLpRn/C3hKvNqbdNlY+cfyGWP3FLN"},"aws_dbe_foot":{"B":"K8R/rRr/y20UK/fAXPfcgLbeOQ/i2qPE31uw9hEaKT6RqR7iHM7JviBfqVpHYvdWMGUCMQCnCUTBVs4I1ly7TQOjp8RT9bm98JTP0X9cmNdyUwVTN3uhYebxJUlQKao4qLOhjBYCMFlTBahK9AADMBCTbAmVRYsuNei1GpddAJaWY6R10fbDAiTyTAFSfT7A9TgdudZrNQ=="},"Junk":{"B":"AwD9ecK4rUaqm1q3kltJz05syG7AaUVsHanlW7YY//v9L9OxOvpIhjxH79Jjvh5MqIIyIVCAyzQ2FiqYk4aW0bRjMQ7znrnhHw0cGOvY1I2I1baowNU6f5dteQEUfH+xFvtn6rvEkP0c1r22n8d6AJIjEM7a4q7qjlnYW1hwZi0W0VLjwhRi0cr0m7bd1Nix6GuXj7KgOZY="},"RecNum":{"N":"1"},"Stuff":{"L":[{"M":{"A":{"S":"B"},"C":{"S":"D"}}},{"NS":["0","0.0011","10.01","2000"]},{"SS":["00.0011","0000","10.01","2000.000"]}]}}},"11":{"type":"positive-decrypt","description":"Basic encrypt V2","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Stuff":"ENCRYPT_AND_SIGN","Junk":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AgFzQZipDYMvoFIYsi4PkmcVI9Z1tC3j/h5x8+QOvDDlXQADZWVjAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQXVmSmRrcGNOZThpeEN6QytOS2FwRHhlK1ZiUTB2Um8rREFYeDB2WmM2UmpSeFZpWEV1eEhYN0hJWW1jNWJWMjlnPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIxi2QNex1dVXmWeMU6msieRoFbvEjHoAPzsrO7KOdyBG2dv6I9gQvCXlpYQPkuL/MnmH6uIAMamkVyVrMfGkIu9B1xAs8d2tCI/pvgO6c4Yo+21QnKfhhyst5l/9phQgTwDffoqYn4ItM8LHEHDriJVlm2KEGzVHdCVArmDRnid0pdpL2DnBVmfQY1CVQ27LvWVftVM91zuXlKj1ThiYk523nX2aUxvssqMVxd6"},"aws_dbe_foot":{"B":"MDzUMd0vDclxExCvI1u6v47ne4rnKZ1BfKE/CagMMMzTTkz89rATgbhdWxuFQeOKMGUCMQC5rfBsZ3w6dYDR74UefYoORnvAThD05gcbyBMwWFRNeJW+Fm+aADcWYkb9yqNXQQwCMAFkZH3QT3G+UaCx/gIy9aowgVcwPGdXO91MdQMCFtSiqsZDUoSN2x+PhOi1nvvd7A=="},"Junk":{"B":"AAHJeMzNv078341ynafWr3bPNYqQm6JZMVg="},"RecNum":{"N":"1"},"Stuff":{"B":"AAHfZyojnGgjPikWhRM0Q6+JeuEboPjlSuFR"}}},"12":{"type":"positive-decrypt","description":"Basic encrypt V2 switching1","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Stuff":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Junk":"ENCRYPT_AND_SIGN","Thing2":"ENCRYPT_AND_SIGN","Thing3":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AgFg3umkfq2RR/UkZNVrv6oX948quQlWgTpVeJ0xCXKzfwADY2VjAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWxDNnFWQU82SG5jdmtwaFJra00zRXh4ZHZRYmhyTnZoZ0p2WHBQczFjNnZJaFUrdXJMVUlidTVnQlJmbGlBN1h3PT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIwJHF2yZNGi+O/glIqURPXypRyVrYyfqMLKgMFSU9GwEeR7YJGqv9U3EeFlNL1N+NF7aDyDju9Xn87DJXD9r9LpXmHM+X6Cig2x9ZEt6c4Yo+21QnKfhhyst5l/9pIHSH1kHpa6AcLiMQAUS+Jwkns3dPvd7/K+osZCX0YjsNks/oDwCSCILFoqc5M3xrrzKdZadrv/4b6nFiFHttp90OTmgyF+6T+auyfH8C50"},"aws_dbe_foot":{"B":"8/PyxIq2lWHmRkDJVB6hHFpCGk0/8ea72rbHhTbvv+0olpWcGs50haEmXUcr7+X1MGUCMQDCmIXgJGxwBla1DxUaRm900Fmc+OXfSZxl/HK1pUOT9hVIT9K1aZviNKNY5D7zFn4CMG3QDCHI9XYswLegkbzrNaJu8m8dq6tH/ByPH4S9QtYhDOUP7FqAd5SwwD8KZJpNew=="},"Junk":{"S":"JunkData"},"RecNum":{"N":"1"},"Stuff":{"B":"AAFZz66Ey59w3TvYmcw3mqf4zSd10DS/z0ZD"}}},"13":{"type":"positive-decrypt","description":"Basic encrypt V2 switching2","config":{"attributeActionsOnEncrypt":{"RecNum":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Stuff":"ENCRYPT_AND_SIGN","Junk":"SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT","Thing2":"ENCRYPT_AND_SIGN","Thing3":"ENCRYPT_AND_SIGN"}},"plaintext":{"RecNum":1,"Stuff":"StuffData","Junk":"JunkData"},"encrypted":{"aws_dbe_head":{"B":"AgFZOAYRr/Athc1OF+rm2wkl7Z6MbqLDOT42ojpcxVN3ywADZWNjAAEAFWF3cy1jcnlwdG8tcHVibGljLWtleQBEQWd4cEkyUjV0UTlWSndubTV3Zm5uS1hML0hRMVFQelN2anN0OUE0REtGazBHUWNMSHlnWDVaR1hXMHhxZGNhYWpRPT0BABFhd3Mta21zLWhpZXJhcmNoeQAkYmQzODQyZmYtMzA3Ni00MDkyLTk5MTgtNDM5NTczMDA1MGI4AIwPad+CJ2qi77n9fjsEromxivjrCkLK227rEoksjMpaBXNqcv9sG3p4w72zvJHhLgLUDPaGlDIqqTaZVU/EfDxs+E5FXlpkvRxt1TA16c4Yo+21QnKfhhyst5l/9mymgxMNnM2Du4ZWZqJFG/9CRehzYiGXYzs8Umc992lDa69+amaKXhvd15aCTKtFgmXOEuG1yHXe2xsUtpin+eZXcecy98Z3Da1H7hNvtIaI"},"aws_dbe_foot":{"B":"xjjjdiu2uU4R8dxh5omnbIe7lpd8fB0CynOcG5Iw8O0rcQhT7+34Aw9i7ScdQQ3LMGUCMQDe87icGX5gb8ivRggJl6m/Xr9IxEO7FNbtTLscWVFVn927L661n7jvxa5cuwmtXsICMC4rvrB+Arn+D+j9xVYK9cMedX7HKbwOw1+BwNBn2wM8XlkoTbszSOVCj0UDdCVsfQ=="},"Junk":{"B":"AAGu9zVTjW39pnnpNdixSqPzbikQDnyEqlE="},"RecNum":{"N":"1"},"Stuff":{"S":"StuffData"}}}}} \ No newline at end of file diff --git a/specification/changes/2024-02-29-encryption-context/background.md b/specification/changes/2024-02-29-encryption-context/background.md new file mode 100644 index 000000000..86a1e5bf2 --- /dev/null +++ b/specification/changes/2024-02-29-encryption-context/background.md @@ -0,0 +1,160 @@ +# Additional Encryption Context + +## Motivation +In the current design, the primary hash and sort keys are made available for branch key calculations. This is sufficient for any system specifically designed with branch keys in mind, but may be insufficient for some legacy systems. + +For example, imagine two tables : Users and Groups. +Each Group has a primary key of GroupID, which determines the branch key. +Each User has a primary key of UserID, and a non-key field holding the GroupID. +Thus when querying the User table, the GroupID is unavailable to the branch key calculation, and so the proper branch key cannot be determined. + +## Goal + +Designate some sign-only attributes to be available to customers for branch key calculations, KMS encryption contexts and such. + +### Where to handle the additions + +#### Option Taken : Add attributes to the encryption context. + +Advantages include : +* Simple user story +* Tiny change to API + +#### Option Not Taken : Pass attributes to the keyring. + +We could have extended the keyring interface to receive a set of key-value pairs, and then passed all signed attributes to the keyring, which would use that to choose the branch key. + + Advantages include : +* This feature is now available to all MPL-based products, not just the DBESDK. + +Drawbacks include : +* Versioning the keyring might be costly, especially for customers that already have a custom keyring. +* There is no way to enforce that the values passed in are in some way authenticated. + +### Which Attributes to Include + +#### Option Taken : Allow the user to configure which signed fields are included. + +Where the customer used to designate “sign only” they now specify a subset of those to be in the encryption context. + +#### Option Not Taken : Include all signed fields + +Simplest for the customer, but this could be very large in some cases, and the KMS limit on encryption context size is fairly small. + + +### Versioning + +We need some way, at decrypt time, to know which attributes were used in the encryption context. + +To do that, we change the legend in the header. Currently, the legend holds one of two values (encrypted or sign_only) for each signed attribute. To this we must add a third value for SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, but changing the header format requires us to bump the version number stored in the header. + +#### Option Taken : Only write new version if needed + +If no SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attributes are configured, then Encrypt writes a version 1 record, exactly like older versions. + +If any SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attributes are configured, then a version 2 record is written, which has a third possible value in the Legend; thus most customers stay at version 1 and are not impacted. As long as a customer distributes version 3.3 of the DBESDK everywhere before writing the first SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT record, all should be well. + +On decrypt, the version number and legend are examined to determine which fields are ENCRYPT_AND_SIGN, SIGN_ONLY OR SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT. As with version 1, the encrypt configuration and decrypt configuration must agree on which fields in the record are DO_NOTHING; otherwise the record will fail to decrypt. + +#### Option Not Taken : Always write new version + +Once we support version 2, we always write version 2. The downside to this is the usual two-phase update problem; that is, if one part of a customers system is updated, and starts writing version 2 records, then other parts of their system, not yet updated, will be unable to read them. + +### Cryptographic action for primary keys + +Currently, the primary hash and sort keys must be SIGN_ONLY, even though they behave as SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT. Going forward, the primary keys must continue to have the SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT functionality. + +#### Option Taken - If any attributes are marked SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT then primary keys must also be SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT. + +This keeps a consistent meaning for all the CryptoActions, while not requiring a version2 header for customers not using the new feature. + +#### Option Not Taken - Primary keys MUST still be SIGN_ONLY +When customers adopt SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, they don’t need to update their primary keys; however, this means that SIGN_ONLY sometimes means SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT leading to confusion + +#### Option Not Taken - Primary keys can be either SIGN_ONLY or SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT +Even easier on customers, as they can change or not, but this still means that SIGN_ONLY sometimes means SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT leading to confusion + +### Where to calculate the new encryption context entries? + +Currently, we generate the encryption context in the Item Encryptor, but only the Structure Encryptor has access to the header and its legend. + +#### Option Taken - Structure Encryptor +On Decrypt, the Structure Encryptor has the necessary context to determine which attributes were used in the encryption context. + +On encrypt, the Structure Encryptor adds to the required encryption context any attributes marked as SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT. + +On decrypt, the Structure Encryptor examines the legend in the header to determine which fields were SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, and performs the same operation as in encrypt. + +#### Option Not Taken - Item Encryptor +This would be simplest for Encrypt, as we already generate the encryption context in the Item Encryptor and pass it into the Structure Encryptor. Unfortunately, on Decrypt things are more complex, and we need to parse the header to find out which attributes were used in the encryption context. Trying to do this in the Item Encryptor would require too much back and forth across the Item/Structure boundary. + +# User Friendly Encryption Context + +## Background + +In the DBESDK, we include the values of the primary hash and sort keys in the encryption context. We serialize the AttributeValue into a a sequence of bytes (as per StructuredEncryption) and then Base64 encode the result. +Thus the string “key” is in the encryption context as “AAFrZXk=”. + +This is problematic in a number of ways + +* A customer looking around trying to figure out what is going on does not see anything helpful. Further, since we don’t surface the deserialization logic, there is no way for them to turn “AAFrZXk=” back into “key”. +* There is no straightforward way to use these values as part of a KMS key policy, which would be a valuable tool for customers. + +It would be nice to simply store “key”, but that has issues. + +The branch key selector function takes a map of AttributeName to AttributeValue. Thus we need to recreate the type of the value. If we see the value “123” is that a string or a number? If we see the string “AAFrZXk=”, is that a literal string or the base64 encoding of something else? + +Further, we can’t ameliorate this with something in the config file, or even the encrypted record’s version number, because the only input to the branch key selector function is the encryption context. + +#### Option Taken - plain strings, plus a legend. + +In the version 2 records, add a new entry to the encryption context : aws-crypto-legend. Much like the legend in the StructuredEncryption header, this holds one character per attribute in the encryption context. Sort the keys in the encryption context and the values in the legend are in that same order. + +The value in the legend tells us how to interpret the string values in the EC. + +* S - a string +* N - a number +* L - a literal : true, false or null +* B - everything else. All Map, List, Set and Binary types will continue to be encoded exactly as they are now. Thus they will still be ugly to look at, and difficult to use in KMS policies. + +The addition of the legend adds a few bytes (17 + 1 per attribute), but strings and numbers stored this way take up less space as they don’t have to pay the overhead of our serialization format and base64 encoding (e.g. “key” which will drop from 8 characters to 3). + +Whenever we generate an encryption context, we know what record version we’re dealing with. In the selector, we can trigger on the existence of the legend field. + +If a customer wants this new functionality, they can simply change their primary hash and sort keys to SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT. + +#### Option Not Taken : DynamoDB's JSON syntax + +We could do without the legend if we used the DynamoDB JSON syntax for values. Thus key would become {”S“ : ”key“} + +This has the advantage of simplicity, and is more user friendly then the current AAFrZXk= situation, but it has drawbacks. + +* Whitespace is meaningless in JSON, but would be significant to a key policy, leading to confusion. +* A key policy referring directly to {”S“ : ”key“} is still a suboptimal user experience +* This would increase the size of the encryption context. Eventually somebody’s going to bump up against the 4K barrier for encryption contexts in KMS. + +#### Option Not Taken - Change interface to branch key selector + +If this took a map of string to string, instead of an AttributeMap, then it would be ok to lose the type information. Unfortunately, this would break all the customers currently using one. + +#### Option Not Taken - Change interface to branch key selector for version 2 records + +The place where we construct the branchKeyIdSupplier, we don’t know the version. Once we know the version, we’ve lost the knowledge of which supplier we are using. + +#### Option Not Taken - Deduce the type + +Skip the legend. If it looks like a number or a literal, that’s what it is. If it ends with a ‘=’ it’s binary, otherwise it’s a string. +This would work 99% of the time, but we need 100%. + +#### Option Not Taken - Pass in everything as a string + +We could store everything in this new way, but don’t keep the legend. Then wrap everything up as an AttributeValue of type String. +Any customer with a binary key would be out of luck entirely. +Any customer that actually cares about the difference between String(123) and Number(123) would also be sad. + +Add type information to the config for every SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attribute. +This would be great, except that the branch key selector doesn’t have access to the config. + + + + diff --git a/specification/changes/2024-02-29-encryption-context/change.md b/specification/changes/2024-02-29-encryption-context/change.md new file mode 100644 index 000000000..58fdfdba6 --- /dev/null +++ b/specification/changes/2024-02-29-encryption-context/change.md @@ -0,0 +1,124 @@ +[//]: # "Copyright Amazon.com Inc. or its affiliates. All Rights Reserved." +[//]: # "SPDX-License-Identifier: CC-BY-SA-4.0" + +# Richer Encryption Context + +## Summary + +A new "version 2" record format will be introduced, to provide a better encryption context experience. + +In version 1 records, the values in the encryption context are somewhat obfuscated, e.g. `AAFrZXk=` instead of `key`, +which makes it difficult to use in a KMS Key Policy, and difficult for humans to inspect. +In a version 2 record, if the corresponding attribute value is a string, +then the encryption context value for that attribute will be exactly that same string. + +In version 1 records, the only field values stored in the encryption context are +the table's primary partition and sort keys. +As the encryption context is the only input to the Branch Key Selector, +this limits the Branch Key Selector to only those two fields. +In a version 2 record, a customer can designate additional fields to be included +in the encryption context, +and therefore available to the Branch Key Selector. + +## Customer Facing Changes + +### New Crypto Action + +A fourth Crypto Action will be made available : `SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT`, to join the existing `DO_NOTHING`, `SIGN_ONLY` and `ENCRYPT_AND_SIGN`. + +The presence of any SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attribute in the configuration +will cause a version 2 record to be written. + +If any SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attribute is configured, +then the primary partition and sort keys must also be SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT. + +And like the primary partition and sort keys, an attribute configured as SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT, +is required in any item to be encrypted. + +If any SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attribute is configured, +- All SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attributes will be included in the encryption context +- All SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT attributes will be available to the Branch Key Selector +- The values of the attributes in the encryption context will be more human readable. Specifically : + +| Type | attribute value | encryption context value | +| ----------- | ----------- | ----------- | +| String | "abc" | "abc" | +| Number | "123" | "123" | +| Null | | "null" | +| Boolean | true | "true" | +| Boolean | false | "false" | +| Anything else | n/a | the version 1 value, looking something like "AAFrZXk=" | + +Note that the values presented to the Branch Key Selector were always, and still are, DynamoDB AttributeValues. +The changes in encryption context values refer only to the raw encryption context. + +An additional item, "aws-crypto-legend", will also be included in the encryption context, +providing the types of the attributes in the encryption context. +This is necessary to distinguish the number "123" from the string "123"; +the string "false" from the boolean value false; and similar things. +This allows the Branch Key Supplier to determine the correct AttributeValues, +based solely on the encryption context -- since no configuration or version numbers are available to it. + +### Parsed Header + +For both record versions, the Parsed Header returned from Structured Encryption operations now +contains an additional field : the full encryption context. + +Similarly, the Parsed Header returned from Item Encryptor operations now +contains two additional fields : the full encryption context, +and the value map that would be passed to the Branch Key Selector. + +## Implementation Changes + +For version 1 records, only the Item Encryptor operations know which attributes should +be in the encryption context, +The logical table name, plus the names and values of the primary hash and sort keys, +and so the full encryption context, +along with the associated RequiredEncryptionContextCMM, +is constructed at the Item Encryptor level and passed through to the Structured Encryption. +This remains unchanged. + +For version 2 records, some things are known only to the Item Encryptor level, +and other things only to the Structured Encryption. +Thus the Item Encryptor constructs a slightly smaller encryption context, +The logical table name, plus just the names of the primary hash and sort keys, +and an associated RequiredEncryptionContextCMM. +Then Structured Encryption adds all of the appropriate values to the encryption context, +which includes at least the primary hash and sort keys, possibly others, +and wraps the RequiredEncryptionContextCMM passed in from the Item Encryptor in +another layer of RequiredEncryptionContextCMM to include those value. + +# Java Enhanced Client + +To use this new functionality with the DynamoDB Enhanced Client in Java, +tag your attribute with `@DynamoDbEncryptionSignAndIncludeInEncryptionContext` + +### Single Table Design + +To better handle [Single-Table Design](https://aws.amazon.com/blogs/compute/creating-a-single-table-design-with-amazon-dynamodb/), +one can now specify multiple schemas when building a DynamoDbEnhancedTableEncryptionConfig +as shown below. + +``` +TableSchema tableSchema1 = TableSchema.fromBean(Class1.class); +TableSchema tableSchema2 = TableSchema.fromBean(Class2.class); +TableSchema tableSchema3 = TableSchema.fromBean(Class3.class); + +DynamoDbEnhancedTableEncryptionConfig.builder() + .logicalTableName(MyTableName) + .schemaOnEncrypt(tableSchema1) + .schemaOnEncrypt(tableSchema2) + .schemaOnEncrypt(tableSchema3) + ... + .build()); + +DynamoDbTable table1 = enhancedClient.table(MyTableName, tableSchema1); +DynamoDbTable table2 = enhancedClient.table(MyTableName, tableSchema2); +DynamoDbTable table3 = enhancedClient.table(MyTableName, tableSchema3); + +TransactWriteItemsEnhancedRequest.builder() + .addPutItem(table1, item1) + .addPutItem(table2, item2) + .addPutItem(table3, item3) + .build(); +``` \ No newline at end of file diff --git a/specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md b/specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md index f9a48124e..c1926ddcc 100644 --- a/specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md +++ b/specification/dynamodb-encryption-client/ddb-encryption-branch-key-id-supplier.md @@ -28,20 +28,22 @@ This operation MUST take in a [DynamoDbKeyBranchKeyIdSupplier](#dynamodb-key-bra ### Output -This operation MUST output a string. -This string is the Branch Key Id that MUST be used by the hierarchical keyring for decryption of this item. +This operation MUST output a BranchKeyIdSupplierReference. ### Behavior -This operation MUST return an implementation of the `BranchKeyIdSupplier` that behaves in the following way on `GetBranchKeyId`: +The returned implementation of `BranchKeyIdSupplier` behaves in the following way on `GetBranchKeyId`: - It MUST deserialize the "aws-crypto-partition-name" value in the input encryption context to determine the partition name. - If this key does not exist in the encryption context, this operation MUST fail. +- If the partition name does not exist in the encryption context, this operation MUST fail. - It MUST get the serialized partition value by grabbing the `aws-crypto-attr.` from the encryption context. - If this key does not exist in the encryption context, this operation MUST fail. +- If the partition value does not exist in the encryption context, this operation MUST fail. - It MUST check for the existence of "aws-crypto-sort-name" in the input encryption context. - - If this key exists, it gets the serialized sort value by grabbing the `aws-crypto.attr:` from the encryption context. - If this does not exist in the context, this operation MUST fail. -- If MUST [deserialize the partition (and optionally sort) value](./ddb-attribute-serialization.md), and create a Key with these values. + - If this key exists, it MUST get the serialized sort value by grabbing the `aws-crypto.attr:` from the encryption context. + - If the sort value does not exist in the context, this operation MUST fail. +- If the field "aws-crypto-legend" exists in the encryption context, +it MUST [deserialize](./ddb-attribute-serialization.md), all values with keys beginning "aws-crypto-attr.", +and create a Key with these values, using names with the "aws-crypto-attr." removed. +- If the field "aws-crypto-legend" does not exist in the encryption context, it MUST [deserialize the partition (and optionally sort) value](./ddb-attribute-serialization.md), and create a Key with these values. - It passes this Key to the supplied DynamoDbKeyBranchKeyIdSupplier via the `GetBranchKeyIdFromDdbKey` operation. - If successful, the resulting string MUST be outputted by this operation. - Otherwise, this operation MUST fail. @@ -49,5 +51,5 @@ This operation MUST return an implementation of the `BranchKeyIdSupplier` that b ## DynamoDbKeyBranchKeyIdSupplier The DynamoDb Key Branch Key Id Supplier is an interface containing the `GetBranchKeyIdFromDdbKey` operation. -This operation MUST take in a DDB `Key` structure (and attribute map containing the partition and sort attributes) as input, -and return a branch key id (string) as output. +This operation MUST take in a DDB `Key` structure (and attribute map containing the partition and sort attributes) as input. +This operation MUST return a branch key id (string) as output. diff --git a/specification/dynamodb-encryption-client/ddb-table-encryption-config.md b/specification/dynamodb-encryption-client/ddb-table-encryption-config.md index 2d790c4f2..0708ea484 100644 --- a/specification/dynamodb-encryption-client/ddb-table-encryption-config.md +++ b/specification/dynamodb-encryption-client/ddb-table-encryption-config.md @@ -108,6 +108,19 @@ of the DynamoDB Table identified by the [input DynamoDB Table Name](#dynamodb-ta This Sort Key Name MUST be a valid DynamoDB Key Schema Attribute Name +### Configuration Version + +If any of the [Attribute Actions](#attribute-actions) are configured as +[SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT](../structured-encryption/structures.md#contextandsign) +then the configuration version MUST be 2; otherwise, +the configuration version MUST be 1. + +### Key Action + +if the [configuration version](#configuration-version) is 2, then +the key action MUST be [SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT](../structured-encryption/structures.md#contextandsign); +otherwise, the key action MUST be [SIGN_ONLY](../structured-encryption/structures.md#signonly). + ### Attribute Actions Attribute Actions is a map of attribute names to @@ -115,7 +128,7 @@ Attribute Actions is a map of attribute names to that describes what Crypto Action applies to a particular attribute (if it exists) during encryption. -The [SIGN_ONLY](../structured-encryption/structures.md#signonly) Crypto Action +The [Key Action](#key-action) MUST be configured to the partition attribute and, if present, sort attribute. ### CMM diff --git a/specification/dynamodb-encryption-client/decrypt-item.md b/specification/dynamodb-encryption-client/decrypt-item.md index 9e3dc442b..eddfac5ab 100644 --- a/specification/dynamodb-encryption-client/decrypt-item.md +++ b/specification/dynamodb-encryption-client/decrypt-item.md @@ -71,6 +71,9 @@ representing the deserialized form of the header of the input encrypted structur calculated using the Crypto Legend in the header, the signature scope used for decryption, and the data in the structure, converted into Attribute Actions. - [Encrypted Data Keys](./header.md#encrypted-data-keys): The Encrypted Data Keys stored in the header. +- [Stored Encryption Context](../structured-encryption/header.md#encryption-context): The Encryption Context stored in the header. +- [Encryption Context](../structured-encryption/decrypt-structure#encryption-context): The full Encryption Context used. +- Selector Context : the AttributeMap as passed to the [Branch Key Supplier](./ddb-encryption-branch-key-id-supplier.md) ## Behavior diff --git a/specification/dynamodb-encryption-client/encrypt-item.md b/specification/dynamodb-encryption-client/encrypt-item.md index e3a147a65..fc9fd2e50 100644 --- a/specification/dynamodb-encryption-client/encrypt-item.md +++ b/specification/dynamodb-encryption-client/encrypt-item.md @@ -45,6 +45,11 @@ has a [DynamoDB Sort Key Name](./ddb-table-encryption-config.md#dynamodb-sort-ke this item MUST include an Attribute with that name. Otherwise this operation MUST yield an error. +If the [DynamoDB Item Encryptor](./ddb-item-encryptor.md) +has any attribute configured as +[SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT](../structured-encryption/structures.md#contextandsign) +then this item MUST include an Attribute with that name. + ## Output This operation MUST output the following: @@ -131,6 +136,30 @@ into the [Encrypted DynamoDB Item](#encrypted-dynamodb-item). ### DynamoDB Item Base Context +If the [Configuration Version](./ddb-table-encryption-config.md#configuration-version) is 2, +then the base context MUST be the [version 2](#dynamodb-item-base-context-version-2) context; +otherwise, the base context MUST be the [version 1](#dynamodb-item-base-context-version-1) context. + +### DynamoDB Item Base Context Version 1 + +A DynamoDB Item Base Context is a map of string key-values pairs +that contains information related to a particular DynamoDB Item. + +The DynamoDB Item Base Context MUST contain: + - the key "aws-crypto-table-name" with a value equal to the configured + [logical table name](./ddb-table-encryption-config.md#logical-table-name). + - the key "aws-crypto-partition-name" with a value equal to the name of the Partition Key on this item. + - the [value](#base-context-value-version-1) of the Partition Key. + +If this item has a Sort Key attribute, the DynamoDB Item Base Context MUST contain: + - the key "aws-crypto-sort-name" with a value equal to the [DynamoDB Sort Key Name](#dynamodb-sort-key-name). + - the [value](#base-context-value-version-1) of the Sort Key. + +If this item does not have a sort key attribute, +the DynamoDB Item Context MUST NOT contain the key `aws-crypto-sort-name`. + +### DynamoDB Item Base Context Version 2 + A DynamoDB Item Base Context is a map of string key-values pairs that contains information related to a particular DynamoDB Item. @@ -138,16 +167,15 @@ The DynamoDB Item Base Context MUST contain: - the key "aws-crypto-table-name" with a value equal to the DynamoDB Table Name of the DynamoDB Table this item is stored in (or will be stored in). - the key "aws-crypto-partition-name" with a value equal to the name of the Partition Key on this item. - - the [value](#base-context-value) of the Partition Key. If this item has a Sort Key attribute, the DynamoDB Item Base Context MUST contain: - the key "aws-crypto-sort-name" with a value equal to the [DynamoDB Sort Key Name](#dynamodb-sort-key-name). - - the [value](#base-context-value) of the Sort Key. If this item does not have a sort key attribute, the DynamoDB Item Context MUST NOT contain the key `aws-crypto-sort-name`. -#### Base Context Value + +#### Base Context Value Version 1 The key MUST be the following concatenation, where `attributeName` is the name of the attribute: @@ -159,3 +187,15 @@ of the concatenation of the bytes `typeID + serializedValue` where `typeId` is the attribute's [type ID](./ddb-attribute-serialization.md#type-id) and `serializedValue` is the attribute's value serialized according to [Attribute Value Serialization](./ddb-attribute-serialization.md#attribute-value-serialization). + +#### Base Context Value Version 2 + +The key MUST be the following concatenation, +where `attributeName` is the name of the attribute: +"aws-crypto-attr." + `attributeName`. + +The value MUST be : +- If the type is Number or String, the unaltered (already utf8) bytes of the value +- If the type if Null, the string "null" +- If the type is Boolean, then the string "true" for true and the string "false" for false. +- Else, the value as defined in [Base Context Value Version 1](#base-context-value-version-1) diff --git a/specification/structured-encryption/decrypt-structure.md b/specification/structured-encryption/decrypt-structure.md index ecefe9f6d..0746d6e6a 100644 --- a/specification/structured-encryption/decrypt-structure.md +++ b/specification/structured-encryption/decrypt-structure.md @@ -106,6 +106,7 @@ representing the deserialized form of the header of the input encrypted structur calculated using the Crypto Legend in the header, the signature scope used for decryption, and the data in the input structure. - [Stored Encryption Context](./header.md#encryption-context): The Encryption Context stored in the header. - [Encrypted Data Keys](./header.md#encrypted-data-keys): The Encrypted Data Keys stored in the header. +- [Encryption Context](#encryption-context): The full Encryption Context used. ## Behavior @@ -155,16 +156,19 @@ in the [input Structured Data](#structured-data): ### Retrieve Decryption Materials +This operation MUST [calculate the appropriate CMM and encryption context](#create-new-encryption-context-and-cmm). + This operation MUST obtain a set of decryption materials by calling [Decrypt Materials](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cmm-interface.md#decrypt-materials) -on the [input CMM](#cmm). +on the [CMM](#cmm) calculated above. The call to the CMM's Decrypt Materials operation MUST be constructed as follows: - Encryption Context: The [Encryption Context parsed from the header](./header.md#encryption-context). - Algorithm Suite ID: The algorithm suite [indicated by the Message Format Flavor](./header.md#format-flavor) parsed in the header. +- Commitment Policy: DBE_COMMITMENT_POLICY - Encrypted Data Keys: The [Encrypted Data Keys parsed from the header](./header.md#encrypted-data-keys). -- Reproduced Encryption Context: This is the [input](#input) encryption context. +- Reproduced Encryption Context: This is the encryption context calculated above. The algorithm suite used in all further aspects of this operation MUST be the algorithm suite in the @@ -175,6 +179,25 @@ If this algorithm suite is not a [supported suite for DBE](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/algorithm-suites.md#supported-algorithm-suites-enum) this operation MUST yield an error. +#### Create New Encryption Context and CMM + +If the version stored in the header is 1, +then the input cmm and encryption context MUST be used unchanged. + +Otherwise, this operation MUST add an [entry](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2) to the encryption context for every +[SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) +[Terminal Data](./structures.md#terminal-data) +in the input record, plus the Legend. + +An error MUST be returned if any of the entries added to the encryption context in this step +have the same key as any entry already in the encryption context. + +Then, this operation MUST create a [Required Encryption Context CMM](https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/required-encryption-context-cmm.md) +with the following inputs: +- This input [CMM](./ddb-table-encryption-config.md#cmm) as the underlying CMM. +- The name of every entry added above. + + ### Verify Signatures A footer field MUST exist with the name `aws_dbe_foot` diff --git a/specification/structured-encryption/encrypt-structure.md b/specification/structured-encryption/encrypt-structure.md index 12fc44a58..39bf00931 100644 --- a/specification/structured-encryption/encrypt-structure.md +++ b/specification/structured-encryption/encrypt-structure.md @@ -90,7 +90,11 @@ The prefix `aws-crypto-` is reserved for internal use by the AWS Encryption SDK; [the Default CMM spec](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/default-cmm.md) for one such use. -## Outputs +## Output + +This operation MUST output the following: +- [Encrypted Structured Data](#encrypted-structured-data) +- [Parsed Header](./decrypt-structure.md#parsed-header) ### Encrypted Structured Data @@ -116,13 +120,14 @@ If any of these steps fails, this operation MUST halt and indicate a failure to ### Retrieve Encryption Materials +This operation MUST [calculate the appropriate CMM and encryption context](#create-new-encryption-context-and-cmm). + This operation MUST obtain a set of encryption materials by calling [Get Encryption Materials](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cmm-interface.md#get-encryption-materials) -on the input [CMM](#cmm). +on the [CMM](#cmm) calculated above. -The call to Get Encryption Materials is constructed as follows: -- Encryption Context: If provided, this MUST be the [input encryption context](#encryption-context); - otherwise, this is an empty encryption context. +This operation MUST call Get Encryption Materials on the CMM as follows. +- Encryption Context: This MUST be the encryption context calculated above. - Commitment Policy: This MUST be [REQUIRE_ENCRYPT_REQUIRE_DECRYPT](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/commitment-policy.md#esdkrequire_encrypt_require_decrypt). - Algorithm Suite: If provided, this is the [input algorithm suite](#algorithm-suite); @@ -145,6 +150,34 @@ If this algorithm suite is not a [supported suite for Database Encryption (DBE)](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/algorithm-suites.md#supported-algorithm-suites-enum), this operation MUST yield an error. +#### Create New Encryption Context and CMM + +If no [Crypto Action](./structures.md#crypto-action) is configured to be +[SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) +then the input cmm and encryption context MUST be used unchanged. + +Otherwise, this operation MUST add an [entry](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2) to the encryption context for every +[SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) +[Terminal Data](./structures.md#terminal-data) +in the input record, plus the Legend. + +An error MUST be returned if any of the entries added to the encryption context in this step +have the same key as any entry already in the encryption context. + +The Legend MUST be named "aws-crypto-legend" and be a string with one character per attribute added above, +with a one-to-one correspondence with the attributes sorted by their UTF8 encoding, +each character designating the original type of the attribute, +to allow reversing of the [encoding](../dynamodb-encryption-client/encrypt-item.md#base-context-value-version-2). +- 'S' if the attribute was of type String +- 'N' if the attribute was of type Number +- 'L' if the attribute was of type Null or Boolean +- 'B' otherwise + +Then, this operation MUST create a [Required Encryption Context CMM](https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/required-encryption-context-cmm.md) +with the following inputs: +- This input [CMM](./ddb-table-encryption-config.md#cmm) as the underlying CMM. +- The name of every entry added above. + ### Calculate Intermediate Encrypted Structured Data To construct the final Encrypted Structured Data, diff --git a/specification/structured-encryption/header.md b/specification/structured-encryption/header.md index ec0593ff5..7ed0d30b1 100644 --- a/specification/structured-encryption/header.md +++ b/specification/structured-encryption/header.md @@ -38,7 +38,11 @@ The value of the header MUST be The message format version dictates the cryptographic algorithms supported as well as the specific serialized header and footer format. -The Version MUST be `0x01`. +The Version MUST be `0x01` or `0x02`. + +If any [Crypto Action](./structures.md#crypto-action) is configured as +[SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT Crypto Action](./structures.md#sign_and_include_in_encryption_context) +the Version MUST be 0x02; otherwise, Version MUST be 0x01. ### Format Flavor @@ -82,6 +86,10 @@ Each Crypto Action MUST be encoded as follows - `0x73` (`s` in UTF-8, for "Sign Only") means that a particular field was not encrypted, but still included in the signature calculation. This indicates that this field MUST NOT be attempted to be decrypted during decryption. +- `0x63` (`c` in UTF-8, for "Context") means that a particular field was not encrypted, + but still included in the signature calculation, + as well as being included in the encryption context. + This indicates that this field MUST NOT be attempted to be decrypted during decryption. - no entry if the attribute is not signed The Encrypt Legend Bytes MUST be serialized as follows: diff --git a/specification/structured-encryption/structures.md b/specification/structured-encryption/structures.md index cc5e915f4..a294789d3 100644 --- a/specification/structured-encryption/structures.md +++ b/specification/structured-encryption/structures.md @@ -96,6 +96,14 @@ SIGN_ONLY indicates that the following actions apply to a [Terminal Data](#termi - [DO_NOT_ENCRYPT](#donotencrypt) - [SIGN](#sign) +##### SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT + +SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT indicates that the following actions apply to a [Terminal Data](#terminal-data): +- [DO_NOT_ENCRYPT](#donotencrypt) +- [SIGN](#sign) + +and further that the [Terminal Data](#terminal-data) MUST be included in the encryption context. + ##### DO_NOTHING DO_NOTHING indicates that the following actions apply to a [Terminal Data](#terminal-data): diff --git a/submodules/smithy-dafny b/submodules/smithy-dafny index cf4522f80..07b8dfe9d 160000 --- a/submodules/smithy-dafny +++ b/submodules/smithy-dafny @@ -1 +1 @@ -Subproject commit cf4522f804542b51bffd1ec204c69aa0fb1ba7e2 +Subproject commit 07b8dfe9d5d8d10233e86e6f850d9c4fded966c0