Skip to content

Commit dd28262

Browse files
committed
fix: issue when a DynamoDB Set attribute is marked as SIGN_ONLY in th… (#560)
DB-ESDK for DynamoDB supports SIGN_ONLY and ENCRYPT_AND_SIGN attribute actions. In version 3.1.0 and below, when a Set type is assigned a SIGN_ONLY attribute action, there is a chance that signature validation of the record containing a Set will fail on read, even if the Set attributes contain the same values. The probability of a failure depends on the order of the elements in the Set combined with how DynamoDB returns this data, which is undefined. This update addresses the issue by ensuring that any Set values are canonicalized in the same order while written to DynamoDB as when read back from DynamoDB. See: https://github.com/aws/aws-database-encryption-sdk-dynamodb-java/tree/v3.1.1/DecryptWithPermute for additional details
1 parent e3aa016 commit dd28262

File tree

23 files changed

+1043
-52
lines changed

23 files changed

+1043
-52
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## 3.1.1 2023-11-07
4+
5+
### Fix
6+
7+
Issue when a DynamoDB Set attribute is marked as SIGN_ONLY in the AWS Database Encryption SDK (DB-ESDK) for DynamoDB.
8+
9+
DB-ESDK for DynamoDB supports SIGN_ONLY and ENCRYPT_AND_SIGN attribute actions. In version 3.1.0 and below, when a Set type is assigned a SIGN_ONLY attribute action, there is a chance that signature validation of the record containing a Set will fail on read, even if the Set attributes contain the same values. The probability of a failure depends on the order of the elements in the Set combined with how DynamoDB returns this data, which is undefined.
10+
11+
This update addresses the issue by ensuring that any Set values are canonicalized in the same order while written to DynamoDB as when read back from DynamoDB.
12+
13+
See: https://github.com/aws/aws-database-encryption-sdk-dynamodb-java/tree/v3.1.1/DecryptWithPermute for additional details for additional details
14+
15+
316
## 3.1.0 2023-09-07
417

518
### Features

DynamoDbEncryption/dafny/DynamoDbEncryption/src/ConfigToInfo.dfy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ module SearchConfigToInfo {
328328
if |badNames| == 0 then
329329
None
330330
else
331+
// We happen to order these values, but this ordering MUST NOT be relied upon.
331332
var badSeq := SortedSets.ComputeSetToOrderedSequence2(badNames, CharLess);
332333
Some(badSeq[0])
333334
}
@@ -399,6 +400,7 @@ module SearchConfigToInfo {
399400
if |badNames| == 0 then
400401
None
401402
else
403+
// We happen to order these values, but this ordering MUST NOT be relied upon.
402404
var badSeq := SortedSets.ComputeSetToOrderedSequence2(badNames, CharLess);
403405
Some(badSeq[0])
404406
}

DynamoDbEncryption/dafny/DynamoDbEncryption/src/DDBSupport.dfy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module DynamoDBSupport {
4444
Success(true)
4545
else
4646
var bad := set k <- item | ReservedPrefix <= k;
47+
// We happen to order these values, but this ordering MUST NOT be relied upon.
4748
var badSeq := SortedSets.ComputeSetToOrderedSequence2(bad, CharLess);
4849
if |badSeq| == 0 then
4950
Failure("")
@@ -173,6 +174,7 @@ module DynamoDBSupport {
173174
var both := newAttrs.Keys * item.Keys;
174175
var bad := set k <- both | newAttrs[k] != item[k];
175176
if 0 < |bad| {
177+
// We happen to order these values, but this ordering MUST NOT be relied upon.
176178
var badSeq := SortedSets.ComputeSetToOrderedSequence2(bad, CharLess);
177179
return Failure(E("Supplied Beacons do not match calculated beacons : " + Join(badSeq, ", ")));
178180
}

DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,8 @@ module DynamoToStruct {
2323
type StructuredDataTerminalType = x : StructuredData | x.content.Terminal? witness *
2424
type TerminalDataMap = map<AttributeName, StructuredDataTerminalType>
2525

26-
//= specification/dynamodb-encryption-client/ddb-item-conversion.md#overview
27-
//= type=TODO
28-
//# The conversion from DDB Item to Structured Data must be lossless,
29-
//# meaning that converting a DDB Item to
30-
//# a Structured Data and back to a DDB Item again
31-
//# MUST result in the exact same DDB Item.
32-
3326
// This file exists for these two functions : ItemToStructured and StructuredToItem
34-
// which provide lossless conversion between an AttributeMap and a StructuredDataMap
27+
// which provide conversion between an AttributeMap and a StructuredDataMap
3528

3629
// Convert AttributeMap to StructuredDataMap
3730
//= specification/dynamodb-encryption-client/ddb-item-conversion.md#convert-ddb-item-to-structured-data
@@ -107,6 +100,7 @@ module DynamoToStruct {
107100
else
108101
var badNames := set k <- s | !IsValid_AttributeName(k) :: k;
109102
OneBadKey(s, badNames, IsValid_AttributeName);
103+
// We happen to order these values, but this ordering MUST NOT be relied upon.
110104
var orderedAttrNames := SetToOrderedSequence(badNames, CharLess);
111105
var attrNameList := Join(orderedAttrNames, ",");
112106
MakeError("Not valid attribute names : " + attrNameList)
@@ -317,7 +311,11 @@ module DynamoToStruct {
317311

318312
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#number
319313
//= type=implication
320-
//# Number MUST be serialized as UTF-8 encoded bytes.
314+
//# This value MUST be normalized in the same way as DynamoDB normalizes numbers.
315+
316+
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#number
317+
//= type=implication
318+
//# This normalized value MUST then be serialized as UTF-8 encoded bytes.
321319
ensures a.N? && ret.Success? && !prefix ==>
322320
&& Norm.NormalizeNumber(a.N).Success?
323321
&& var nn := Norm.NormalizeNumber(a.N).value;
@@ -488,30 +486,52 @@ module DynamoToStruct {
488486
function method StringSetAttrToBytes(ss: StringSetAttributeValue): (ret: Result<seq<uint8>, string>)
489487
ensures ret.Success? ==> Seq.HasNoDuplicates(ss)
490488
{
491-
:- Need(|Seq.ToSet(ss)| == |ss|, "String Set had duplicate values");
489+
var asSet := Seq.ToSet(ss);
490+
:- Need(|asSet| == |ss|, "String Set had duplicate values");
492491
Seq.LemmaNoDuplicatesCardinalityOfSet(ss);
493-
var count :- U32ToBigEndian(|ss|);
494-
var body :- CollectString(ss);
492+
493+
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#set-entries
494+
//# Entries in a String Set MUST be ordered in ascending [UTF-16 binary order](./string-ordering.md#utf-16-binary-order).
495+
var sortedList := SortedSets.ComputeSetToOrderedSequence2(asSet, CharLess);
496+
var count :- U32ToBigEndian(|sortedList|);
497+
var body :- CollectString(sortedList);
495498
Success(count + body)
496499
}
497500

498501
function method NumberSetAttrToBytes(ns: NumberSetAttributeValue): (ret: Result<seq<uint8>, string>)
499502
ensures ret.Success? ==> Seq.HasNoDuplicates(ns)
500503
{
501-
:- Need(|Seq.ToSet(ns)| == |ns|, "Number Set had duplicate values");
504+
var asSet := Seq.ToSet(ns);
505+
:- Need(|asSet| == |ns|, "Number Set had duplicate values");
502506
Seq.LemmaNoDuplicatesCardinalityOfSet(ns);
503-
var count :- U32ToBigEndian(|ns|);
504-
var body :- CollectString(ns);
507+
508+
var normList :- Seq.MapWithResult(n => Norm.NormalizeNumber(n), ns);
509+
var asSet := Seq.ToSet(normList);
510+
:- Need(|asSet| == |normList|, "Number Set had duplicate values after normalization.");
511+
512+
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#set-entries
513+
//# Entries in a Number Set MUST be ordered in ascending [UTF-16 binary order](./string-ordering.md#utf-16-binary-order).
514+
515+
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#set-entries
516+
//# This ordering MUST be applied after normalization of the number value.
517+
var sortedList := SortedSets.ComputeSetToOrderedSequence2(asSet, CharLess);
518+
var count :- U32ToBigEndian(|sortedList|);
519+
var body :- CollectString(sortedList);
505520
Success(count + body)
506521
}
507522

508523
function method BinarySetAttrToBytes(bs: BinarySetAttributeValue): (ret: Result<seq<uint8>, string>)
509524
ensures ret.Success? ==> Seq.HasNoDuplicates(bs)
510525
{
511-
:- Need(|Seq.ToSet(bs)| == |bs|, "Binary Set had duplicate values");
526+
var asSet := Seq.ToSet(bs);
527+
:- Need(|asSet| == |bs|, "Binary Set had duplicate values");
512528
Seq.LemmaNoDuplicatesCardinalityOfSet(bs);
513-
var count :- U32ToBigEndian(|bs|);
514-
var body :- CollectBinary(bs);
529+
530+
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#set-entries
531+
//# Entries in a Binary Set MUST be ordered lexicographically by their underlying bytes in ascending order.
532+
var sortedList := SortedSets.ComputeSetToOrderedSequence2(asSet, ByteLess);
533+
var count :- U32ToBigEndian(|sortedList|);
534+
var body :- CollectBinary(sortedList);
515535
Success(count + body)
516536
}
517537

@@ -749,6 +769,9 @@ module DynamoToStruct {
749769
ensures (ret.Success? && |mapToSerialize| == 0) ==> (ret.value == serialized)
750770
ensures (ret.Success? && |mapToSerialize| == 0) ==> (|ret.value| == |serialized|)
751771
{
772+
//= specification/dynamodb-encryption-client/ddb-attribute-serialization.md#key-value-pair-entries
773+
//# Entries in a serialized Map MUST be ordered by key value,
774+
//# ordered in ascending [UTF-16 binary order](./string-ordering.md#utf-16-binary-order).
752775
var keys := SortedSets.ComputeSetToOrderedSequence2(mapToSerialize.Keys, CharLess);
753776
CollectOrderedMapSubset(keys, mapToSerialize, serialized)
754777
}
@@ -1136,6 +1159,7 @@ module DynamoToStruct {
11361159
OneBadResult(m);
11371160
var badValues := FlattenErrors(m);
11381161
assert(|badValues| > 0);
1162+
// We happen to order these values, but this ordering MUST NOT be relied upon.
11391163
var badValueSeq := SetToOrderedSequence(badValues, CharLess);
11401164
Failure(Join(badValueSeq, "\n"))
11411165
}

DynamoDbEncryption/dafny/DynamoDbEncryption/src/SearchInfo.dfy

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,6 @@ module SearchableEncryptionInfo {
344344
versions[currWrite].IsVirtualField(field)
345345
}
346346

347-
function method GenerateClosure(fields : seq<string>) : seq<string>
348-
requires ValidState()
349-
{
350-
versions[currWrite].GenerateClosure(fields)
351-
}
352-
353347
method GeneratePlainBeacons(item : DDB.AttributeMap) returns (output : Result<DDB.AttributeMap, Error>)
354348
requires ValidState()
355349
{
@@ -559,6 +553,7 @@ module SearchableEncryptionInfo {
559553
requires version == 1
560554
requires keySource.ValidState()
561555
{
556+
// We happen to order these values, but this ordering MUST NOT be relied upon.
562557
var beaconNames := SortedSets.ComputeSetToOrderedSequence2(beacons.Keys, CharLess);
563558
var stdKeys := Seq.Filter((k : string) => k in beacons && beacons[k].Standard?, beaconNames);
564559
FilterPreservesHasNoDuplicates((k : string) => k in beacons && beacons[k].Standard?, beaconNames);
@@ -575,6 +570,7 @@ module SearchableEncryptionInfo {
575570
keySource : KeySource,
576571
virtualFields : VirtualFieldMap,
577572
beacons : BeaconMap,
573+
// The ordering of `beaconNames` MUST NOT be relied upon.
578574
beaconNames : seq<string>,
579575
stdNames : seq<string>,
580576
encryptedFields : set<string>
@@ -614,13 +610,6 @@ module SearchableEncryptionInfo {
614610
[field]
615611
}
616612

617-
function method GenerateClosure(fields : seq<string>) : seq<string>
618-
{
619-
var fieldLists := Seq.Map((s : string) => GetFields(s), fields);
620-
var fieldSet := set f <- fieldLists, g <- f :: g;
621-
SortedSets.ComputeSetToOrderedSequence2(fieldSet, CharLess)
622-
}
623-
624613
method getKeyMap(keyId : MaybeKeyId) returns (output : Result<MaybeKeyMap, Error>)
625614
requires ValidState()
626615
ensures ValidState()

0 commit comments

Comments
 (0)