Skip to content

Commit 5af21c3

Browse files
committed
feat: more test vectors
1 parent 3f22abc commit 5af21c3

File tree

8 files changed

+253
-19
lines changed

8 files changed

+253
-19
lines changed

DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations.dfy

+56-8
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
160160
DDBEncode(SE.ATTR_PREFIX + k)
161161
}
162162

163-
function method MakeEncryptionContext(
163+
function method MakeEncryptionContextForEncrypt(
164164
config : InternalConfig,
165165
item : DynamoToStruct.TerminalDataMap)
166166
: (ret : Result<CMP.EncryptionContext, Error>)
@@ -179,6 +179,36 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
179179
MakeEncryptionContextV1(config, item)
180180
}
181181

182+
function method MakeEncryptionContextForDecrypt(
183+
config : InternalConfig,
184+
header : seq<uint8>,
185+
item : DynamoToStruct.TerminalDataMap)
186+
: (ret : Result<CMP.EncryptionContext, Error>)
187+
requires 0 < |header|
188+
ensures ret.Success? ==>
189+
//= specification/dynamodb-encryption-client/decrypt-item.md#dynamodb-item-base-context
190+
//= type=implication
191+
//# If the Version Number is 2, then the base context MUST be the [version 2](./encrypt-item.md#dynamodb-item-base-context-version-2) context.
192+
&& (header[0] == 2 ==> ret == MakeEncryptionContextV2(config, item))
193+
//= specification/dynamodb-encryption-client/decrypt-item.md#dynamodb-item-base-context
194+
//= type=implication
195+
//# If the Version Number is 1, the base context MUST be the [version 1](./encrypt-item.md#dynamodb-item-base-context-version-1) context.
196+
&& (header[0] == 1 ==> ret == MakeEncryptionContextV1(config, item))
197+
&& ((header[0] == 1) || (header[0] == 2))
198+
199+
//= specification/dynamodb-encryption-client/decrypt-item.md#dynamodb-item-base-context
200+
//= type=implication
201+
//# If the Version Number is not 1 or 2, the operation MUST return an error.
202+
ensures ((header[0] != 1) && (header[0] != 2)) ==> ret.Failure?
203+
{
204+
if header[0] == 2 then
205+
MakeEncryptionContextV2(config, item)
206+
else if header[0] == 1 then
207+
MakeEncryptionContextV1(config, item)
208+
else
209+
Failure(E("Header attribute has unexpected version number"))
210+
}
211+
182212
function method {:opaque} {:vcs_split_on_every_assert} MakeEncryptionContextV1(
183213
config : InternalConfig,
184214
item : DynamoToStruct.TerminalDataMap)
@@ -770,9 +800,9 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
770800
//= specification/dynamodb-encryption-client/encrypt-item.md#behavior
771801
//= type=implication
772802
//# - Encryption Context MUST be this input Item's [DynamoDB Item Base Context](#dynamodb-item-base-context).
773-
&& MakeEncryptionContext(config, plaintextStructure).Success?
803+
&& MakeEncryptionContextForEncrypt(config, plaintextStructure).Success?
774804
&& Seq.Last(config.structuredEncryption.History.EncryptStructure).input.encryptionContext
775-
== Some(MakeEncryptionContext(config, plaintextStructure).value)
805+
== Some(MakeEncryptionContextForEncrypt(config, plaintextStructure).value)
776806

777807
&& output.value.parsedHeader.Some?
778808
&& var structuredEncParsed := Seq.Last(config.structuredEncryption.History.EncryptStructure).output.value.parsedHeader;
@@ -847,7 +877,7 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
847877

848878
var plaintextStructure :- DynamoToStruct.ItemToStructured(input.plaintextItem)
849879
.MapFailure(e => Error.AwsCryptographyDbEncryptionSdkDynamoDb(e));
850-
var context :- MakeEncryptionContext(config, plaintextStructure);
880+
var context :- MakeEncryptionContextForEncrypt(config, plaintextStructure);
851881
var cryptoSchema :- ConfigToCryptoSchema(config, input.plaintextItem)
852882
.MapFailure(e => Error.AwsCryptographyDbEncryptionSdkDynamoDb(e));
853883
var wrappedStruct := CSE.StructuredData(
@@ -977,12 +1007,25 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
9771007
content := CSE.StructuredDataContent.DataMap(plaintextStructure),
9781008
attributes := None)
9791009

1010+
//= specification/dynamodb-encryption-client/decrypt-item.md#dynamodb-item-base-context
1011+
//= type=implication
1012+
//# The item to be encrypted MUST have an attribute named `aws_dbe_head`.
1013+
&& SE.HeaderField in input.encryptedItem
1014+
&& var header := input.encryptedItem[SE.HeaderField];
1015+
1016+
//= specification/dynamodb-encryption-client/decrypt-item.md#dynamodb-item-base-context
1017+
//= type=implication
1018+
//# The attribute named `aws_dbe_head` MUST be of type `B` Binary.
1019+
&& header.B?
1020+
&& 0 < |header.B|
1021+
9801022
//= specification/dynamodb-encryption-client/decrypt-item.md#behavior
9811023
//= type=implication
9821024
//# - Encryption Context MUST be the input Item's [DynamoDB Item Base Context](./encrypt-item.md#dynamodb-item-base-context).
983-
&& MakeEncryptionContext(config, plaintextStructure).Success?
1025+
&& MakeEncryptionContextForDecrypt(config, header.B, plaintextStructure).Success?
1026+
9841027
&& Seq.Last(config.structuredEncryption.History.DecryptStructure).input.encryptionContext
985-
== Some(MakeEncryptionContext(config, plaintextStructure).value)
1028+
== Some(MakeEncryptionContextForDecrypt(config, header.B, plaintextStructure).value)
9861029

9871030
//= specification/dynamodb-encryption-client/decrypt-item.md#output
9881031
//= type=implication
@@ -1077,7 +1120,12 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
10771120

10781121
var encryptedStructure :- DynamoToStruct.ItemToStructured(input.encryptedItem)
10791122
.MapFailure(e => Error.AwsCryptographyDbEncryptionSdkDynamoDb(e));
1080-
var context :- MakeEncryptionContext(config, encryptedStructure);
1123+
:- Need(SE.HeaderField in input.encryptedItem, E("header field not in item."));
1124+
var header := input.encryptedItem[SE.HeaderField];
1125+
:- Need(header.B?, E("Header field not binary"));
1126+
assert header.B?;
1127+
:- Need(0 < |header.B|, E("Unexpected empty header field."));
1128+
var context :- MakeEncryptionContextForDecrypt(config, header.B, encryptedStructure);
10811129
var authenticateSchema := ConfigToAuthenticateSchema(config, input.encryptedItem);
10821130
var wrappedStruct := CSE.StructuredData(
10831131
content := CSE.StructuredDataContent.DataMap(encryptedStructure),
@@ -1088,7 +1136,7 @@ module AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorOperations refines Abs
10881136
//# [Required Encryption Context CMM](https://github.com/awslabs/private-aws-encryption-sdk-specification-staging/blob/dafny-verified/framework/required-encryption-context-cmm.md)
10891137
//# with the following inputs:
10901138
//# - This item encryptor's [CMM](./ddb-table-encryption-config.md#cmm) as the underlying CMM.
1091-
//# - The keys from the [DynamoDB Item Base Context](./encrypt-item.md#dynamodb-item-base-context).
1139+
//# - The keys from the [DynamoDB Item Base Context](#dynamodb-item-base-context).
10921140

10931141
var reqCMMR := config.cmpClient.CreateRequiredEncryptionContextCMM(
10941142
CMP.CreateRequiredEncryptionContextCMMInput(

TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ module {:options "-functionSyntax:4"} DecryptManifest {
5353
method OneTest(name : string, value : JSON) returns (output : Result<bool, string>)
5454
{
5555
:- Need(value.Object?, "Test must be an object");
56-
print "Decrypting ", name, "\n";
5756

5857
var types : Option<string> := None;
5958
var description : Option<string> := None;
@@ -99,6 +98,7 @@ module {:options "-functionSyntax:4"} DecryptManifest {
9998

10099
method Decrypt(inFile : string) returns (output : Result<bool, string>)
101100
{
101+
print "Decrypt : ", inFile, "\n";
102102
var configBv :- expect FileIO.ReadBytesFromFile(inFile);
103103
var configBytes := BvToBytes(configBv);
104104
var json :- expect API.Deserialize(configBytes);

TestVectors/dafny/DDBEncryption/src/EncryptManifest.dfy

+2-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ module {:options "-functionSyntax:4"} EncryptManifest {
8282
method OneTest(name : string, value : JSON) returns (output : Result<Option<(string, JSON)>, string>)
8383
{
8484
:- Need(value.Object?, "Test must be an object");
85-
print "Examining ", name, "\n";
8685

8786
var types : Option<string> := None;
8887
var description : Option<string> := None;
@@ -132,6 +131,8 @@ module {:options "-functionSyntax:4"} EncryptManifest {
132131

133132
method Encrypt(inFile : string, outFile : string, lang : string, version : string) returns (output : Result<bool, string>)
134133
{
134+
print "Encrypt : ", inFile, "\n";
135+
135136
var configBv :- expect FileIO.ReadBytesFromFile(inFile);
136137
var configBytes := BvToBytes(configBv);
137138
var json :- expect API.Deserialize(configBytes);

TestVectors/dafny/DDBEncryption/src/TestVectors.dfy

+3-1
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ module {:options "-functionSyntax:4"} DdbEncryptionTestVectors {
9090
var _ :- expect DecryptManifest.Decrypt("decrypt_java_32.json");
9191
var _ :- expect DecryptManifest.Decrypt("decrypt_dotnet_33.json");
9292
var _ :- expect DecryptManifest.Decrypt("decrypt_java_33.json");
93+
var _ :- expect DecryptManifest.Decrypt("decrypt_dotnet_33a.json");
94+
var _ :- expect DecryptManifest.Decrypt("decrypt_java_33a.json");
9395
var _ :- expect WriteManifest.Write("encrypt.json");
94-
var _ :- expect EncryptManifest.Encrypt("encrypt.json", "decrypt.json", "java", "3.2");
96+
var _ :- expect EncryptManifest.Encrypt("encrypt.json", "decrypt.json", "java", "3.3");
9597
var _ :- expect DecryptManifest.Decrypt("decrypt.json");
9698
if |globalRecords| + |tableEncryptionConfigs| + |queries| == 0 {
9799
print "\nRunning no tests\n";

TestVectors/dafny/DDBEncryption/src/WriteManifest.dfy

+174-7
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,24 @@ module {:options "-functionSyntax:4"} WriteManifest {
9898
},
9999
""allowedUnsignedAttributes"" : [""Stuff"", ""Junk""]
100100
}"
101-
101+
102102
method TextToJson(x: string) returns (output : JSON)
103103
{
104104
var str :- expect UTF8.Encode(x);
105105
var json :- expect API.Deserialize(str);
106106
return json;
107107
}
108108

109+
const QUOTE : string := "\""
110+
111+
const ConfigRecord :=
112+
"{\"RecNum\": 1,"
113+
+ QUOTE + Attr1 + QUOTE + ": \"aaa\","
114+
+ QUOTE + Attr2 + QUOTE + ": {\"M\" : {\"A\":\"B\", \"C\":\"D\"}},"
115+
+ QUOTE + Attr3 + QUOTE + ": {\"L\" : [{\"M\" : {\"A\":\"B\", \"C\":\"D\"}}, {\"NS\":[\"00.0011\", \"0000\", \"2000.000\", \"10.01\"]}, {\"SS\":[\"00.0011\", \"0000\", \"2000.000\", \"10.01\"]}]},"
116+
+ QUOTE + Attr4 + QUOTE + ": {\"SS\":[\"00.0011\", \"0000\", \"2000.000\", \"10.01\"]},"
117+
+ QUOTE + Attr5 + QUOTE + ": {\"NS\":[\"00.0011\", \"0000\", \"2000.000\", \"10.01\"]}}"
118+
109119
const BasicRecord := @"{
110120
""RecNum"": 1,
111121
""Stuff"": ""StuffData"",
@@ -122,11 +132,11 @@ module {:options "-functionSyntax:4"} WriteManifest {
122132
}"
123133

124134
method MakeTest(
125-
name : string,
126-
typ : string,
127-
desc : string,
128-
configJson : string,
129-
recordJson : string,
135+
name : string,
136+
typ : string,
137+
desc : string,
138+
configJson : string,
139+
recordJson : string,
130140
decryptConfigJson : Option<string> := None) returns (output : (string, JSON))
131141
{
132142
var config := TextToJson(configJson);
@@ -145,8 +155,164 @@ module {:options "-functionSyntax:4"} WriteManifest {
145155
return(name, Object(result));
146156
}
147157

158+
type CryptoAction = x : int | 0 <= x < 4
159+
type CryptoActions = x : seq<CryptoAction> | |x| == 6 witness [0,0,0,0,0,0]
160+
const CryptoActionStr : seq<string> := ["0", "1", "2", "3"]
161+
const MyActionNames : seq<string> := ["ENCRYPT_AND_SIGN", "SIGN_ONLY", "SIGN_AND_INCLUDE_IN_ENCRYPTION_CONTEXT", "DO_NOTHING"]
162+
const Encrypt : CryptoAction := 0
163+
const SignOnly : CryptoAction := 1
164+
const SignInclude : CryptoAction := 2
165+
const DoNothing : CryptoAction := 3
166+
167+
const A : string := "A"
168+
const B : string := "퀀" // Ud000"
169+
const C : string := "﹌" // Ufe4c"
170+
const D : string := "𐀁" // U10001
171+
const E : string := "𐀂" // U10002 - same high surrogate as D
172+
const F : string := "𠀂" // U20002 - different high surrogate as D
173+
174+
lemma CheckLengths()
175+
ensures |A| == 1
176+
ensures |B| == 1
177+
ensures |C| == 1
178+
ensures |D| == 2
179+
ensures |E| == 2
180+
ensures |F| == 2
181+
{}
182+
183+
// Let's make attribute names with complex characters.
184+
// It shouldn't matter, but let's make sure
185+
const Attr1 : string := F+D
186+
const Attr2 : string := A+B+C+D
187+
const Attr3 : string := E+D
188+
const Attr4 : string := C+D
189+
const Attr5 : string := D+C+B+A
190+
const MyAttrNames : seq<string> := ["RecNum", Attr1, Attr2, Attr3, Attr4, Attr5]
191+
192+
193+
method MakeConfig(actions : CryptoActions) returns (output : JSON)
194+
ensures output.Object?
195+
{
196+
var configActions : seq<(string, JSON)> := [];
197+
var notSigned : seq<JSON> := [];
198+
for i := 0 to 6 {
199+
var myAction : CryptoAction := actions[i];
200+
configActions := configActions + [(MyAttrNames[i], String(MyActionNames[myAction]))];
201+
if myAction == DoNothing {
202+
notSigned := notSigned + [String(MyAttrNames[i])];
203+
}
204+
}
205+
var result : seq<(string, JSON)> := [
206+
("attributeActionsOnEncrypt", Object(configActions)),
207+
("allowedUnsignedAttributes", Array(notSigned))
208+
];
209+
210+
return Object(result);
211+
}
212+
213+
method MakeConfigTest(
214+
name : string,
215+
config : JSON,
216+
record : JSON,
217+
decryptConfigJson : Option<JSON> := None) returns (output : (string, JSON))
218+
{
219+
var result : seq<(string, JSON)> :=
220+
[
221+
("type", String("positive-encrypt")),
222+
("description", String("config test")),
223+
("config", config),
224+
("record", record)
225+
];
226+
if decryptConfigJson.Some? {
227+
var decryptConfig := decryptConfigJson.value;
228+
result := result + [("decryptConfig", decryptConfig)];
229+
}
230+
return(name, Object(result));
231+
}
232+
233+
function Increment(x : CryptoActions) : CryptoActions
234+
{
235+
if x[5] < 3 then
236+
[x[0], x[1], x[2], x[3], x[4], x[5]+1]
237+
else if x[4] < 3 then
238+
[x[0], x[1], x[2], x[3], x[4]+1, 0]
239+
else if x[3] < 3 then
240+
[x[0], x[1], x[2], x[3]+1, 0, 0]
241+
else if x[2] < 3 then
242+
[x[0], x[1], x[2]+1, 0, 0, 0]
243+
else if x[1] < 3 then
244+
[x[0], x[1]+1, 0, 0, 0, 0]
245+
else if x[0] < 3 then
246+
[x[0]+1, 0, 0, 0, 0, 0]
247+
else
248+
[0, 0, 0, 0, 0, 0]
249+
}
250+
251+
predicate IsConsistent(actions : CryptoActions)
252+
{
253+
if actions[0] in [DoNothing, Encrypt] then
254+
false
255+
else if actions[0] == SignOnly && (exists x | x in actions :: x == SignInclude) then
256+
false
257+
else
258+
true
259+
}
260+
261+
predicate IsConsistentWith(oldActions : CryptoActions, newActions : CryptoActions)
262+
{
263+
if !IsConsistent(oldActions) || !IsConsistent(newActions) then
264+
false
265+
else
266+
!exists i | 0 <= i < 6 :: (oldActions[i] == DoNothing) != (newActions[i] == DoNothing)
267+
}
268+
269+
// make a test for every valid combination of Crypto Actions
270+
method MakeConfigTests() returns (output : seq<(string, JSON)>)
271+
{
272+
var actions : CryptoActions := [0,0,0,0,0,0];
273+
var result : seq<(string, JSON)> := [];
274+
var record := TextToJson(ConfigRecord);
275+
var actionWrittenOuter := 0;
276+
var actionWrittenInner := 0;
277+
for i := 0 to 4096 {
278+
actions := Increment(actions);
279+
if IsConsistent(actions) {
280+
var name := "ConfigTest_";
281+
for j := 0 to 6 {
282+
name := name + CryptoActionStr[actions[j]];
283+
}
284+
var config := MakeConfig(actions);
285+
var theTest := MakeConfigTest(name, config, record);
286+
result := result + [theTest];
287+
actionWrittenOuter := actionWrittenOuter + 1;
288+
289+
// for a subset of these,
290+
// make a test to decrypt with every possible valid combination of Crypto Actions
291+
if (actionWrittenOuter % 100) == 0 {
292+
var otherActions : CryptoActions := [0,0,0,0,0,0];
293+
for j := 0 to 4096 {
294+
otherActions := Increment(otherActions);
295+
if IsConsistentWith(actions, otherActions) {
296+
var newConfig := MakeConfig(otherActions);
297+
var newName := name + "_";
298+
for k := 0 to 6 {
299+
newName := newName + CryptoActionStr[otherActions[k]];
300+
}
301+
var newTest := MakeConfigTest(newName, config, record, Some(newConfig));
302+
result := result + [newTest];
303+
actionWrittenInner := actionWrittenInner + 1;
304+
}
305+
}
306+
}
307+
}
308+
}
309+
print "MakeConfigTests : ", actionWrittenOuter, " outer and ", actionWrittenInner, " inner for ", |result|, " total.\n";
310+
return result;
311+
}
312+
148313
method Write(fileName : string) returns (output : Result<bool, string>)
149314
{
315+
print "Write : ", fileName, "\n";
150316
var result : seq<(string, JSON)> :=
151317
[Manifest(), ("keys", String("file://keys.json"))];
152318

@@ -163,7 +329,8 @@ module {:options "-functionSyntax:4"} WriteManifest {
163329
var test11 := MakeTest("11", "positive-encrypt", "Basic encrypt V2", BasicV2Config, BasicRecord);
164330
var test12 := MakeTest("12", "positive-encrypt", "Basic encrypt V2 switching1", LongerV2Config1, BasicRecord, Some(LongerV2Config2));
165331
var test13 := MakeTest("13", "positive-encrypt", "Basic encrypt V2 switching2", LongerV2Config2, BasicRecord, Some(LongerV2Config1));
166-
var tests : seq<(string, JSON)> := [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12, test13];
332+
var configTests := MakeConfigTests();
333+
var tests : seq<(string, JSON)> := [test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12, test13] + configTests;
167334
var final := Object(result + [("tests", Object(tests))]);
168335

169336
var jsonBytes :- expect API.Serialize(final);

TestVectors/runtimes/java/decrypt_dotnet_33a.json

+1
Large diffs are not rendered by default.

TestVectors/runtimes/java/decrypt_java_33a.json

+1
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)