Skip to content

Commit 46076f8

Browse files
authored
feat(SharedCache): Shared Cache for Searchable Encryption (#1476)
1 parent 98724d3 commit 46076f8

File tree

25 files changed

+1312
-178
lines changed

25 files changed

+1312
-178
lines changed

DynamoDbEncryption/dafny/DynamoDbEncryption/Model/AwsCryptographyDbEncryptionSdkDynamoDbTypes.dfy

+5-2
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.internald
354354
datatype MultiKeyStore = | MultiKeyStore (
355355
nameonly keyFieldName: string ,
356356
nameonly cacheTTL: int32 ,
357-
nameonly cache: Option<AwsCryptographyMaterialProvidersTypes.CacheType> := Option.None
357+
nameonly cache: Option<AwsCryptographyMaterialProvidersTypes.CacheType> := Option.None ,
358+
nameonly partitionId: Option<string> := Option.None
358359
)
359360
datatype PartOnly = | PartOnly (
360361

@@ -388,7 +389,9 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.internald
388389
}
389390
datatype SingleKeyStore = | SingleKeyStore (
390391
nameonly keyId: string ,
391-
nameonly cacheTTL: int32
392+
nameonly cacheTTL: int32 ,
393+
nameonly cache: Option<AwsCryptographyMaterialProvidersTypes.CacheType> := Option.None ,
394+
nameonly partitionId: Option<string> := Option.None
392395
)
393396
datatype StandardBeacon = | StandardBeacon (
394397
nameonly name: string ,

DynamoDbEncryption/dafny/DynamoDbEncryption/Model/DynamoDbEncryption.smithy

+11-2
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,8 @@ structure KeyStoreReference {}
708708
//# On initialization of a Single Key Store, the caller MUST provide:
709709
//# - [Beacon Key Id](#beacon-key-id)
710710
//# - [cacheTTL](#cachettl)
711+
//# - [cache](#key-store-cache)
712+
//# - [partition-id](#partition-id)
711713

712714
@javadoc("The configuration for using a single Beacon Key.")
713715
structure SingleKeyStore {
@@ -717,14 +719,19 @@ structure SingleKeyStore {
717719
@required
718720
@javadoc("How long (in seconds) the beacon key material is cached locally before it is re-retrieved from DynamoDB and re-authed with AWS KMS.")
719721
cacheTTL: Integer,
722+
@documentation("Which type of local cache to use. Please see the [spec](https://github.com/aws/aws-database-encryption-sdk-dynamodb/blob/main/specification/searchable-encryption/search-config.md#key-store-cache) on how to provide a cache for a SingleKeyStore.")
723+
cache : CacheType,
724+
@documentation("Partition ID to distinguish Beacon Key Sources writing to a Shared cache. If the Partition ID is the same for two Beacon Key Sources, they can share the same cache entries in the Shared cache.")
725+
partitionId: String
720726
}
721727

722728
//= specification/searchable-encryption/search-config.md#multi-key-store-initialization
723729
//= type=implication
724730
//# On initialization of a Multi Key Store, the caller MUST provide:
725731
//# - [Beacon Key Field Name](#beacon-key-field-name)
726732
//# - [cacheTTL](#cachettl)
727-
//# - [max cache size](#max-cache-size)
733+
//# - [cache](#key-store-cache)
734+
//# - [partition-id](#partition-id)
728735

729736
@javadoc("The configuration for using multiple Beacon Keys.")
730737
structure MultiKeyStore {
@@ -735,7 +742,9 @@ structure MultiKeyStore {
735742
@javadoc("How long (in seconds) the beacon key material is cached locally before it is re-retrieved from DynamoDB and re-authed with AWS KMS.")
736743
cacheTTL: Integer,
737744
@javadoc("Which type of local cache to use.")
738-
cache : CacheType
745+
cache : CacheType,
746+
@documentation("Partition ID to distinguish Beacon Key Sources writing to a Shared cache. If the Partition ID is the same for two Beacon Key Sources, they can share the same cache entries in the Shared cache.")
747+
partitionId: String
739748
}
740749

741750
//= specification/searchable-encryption/search-config.md#beacon-key-source

DynamoDbEncryption/dafny/DynamoDbEncryption/src/ConfigToInfo.dfy

+80-21
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ module SearchConfigToInfo {
3838
method Convert(outer : DynamoDbTableEncryptionConfig)
3939
returns (output : Result<Option<I.ValidSearchInfo>, Error>)
4040
requires ValidSearchConfig(outer.search)
41+
requires outer.search.Some? ==> ValidSharedCache(outer.search.value.versions[0].keySource)
42+
modifies if outer.search.Some? then outer.search.value.versions[0].keyStore.Modifies else {}
43+
ensures outer.search.Some? ==> ValidSharedCache(outer.search.value.versions[0].keySource)
4144
ensures output.Success? && output.value.Some? ==>
4245
&& output.value.value.ValidState()
4346
&& fresh(output.value.value.versions[0].keySource.client)
@@ -56,7 +59,8 @@ module SearchConfigToInfo {
5659
} else {
5760
:- Need(outer.search.value.writeVersion == 1, E("writeVersion must be '1'."));
5861
:- Need(|outer.search.value.versions| == 1, E("search config must be have exactly one version."));
59-
var version :- ConvertVersion(outer, outer.search.value.versions[0]);
62+
var beaconVersionConfig := outer.search.value.versions[0];
63+
var version :- ConvertVersion(outer, beaconVersionConfig);
6064
var info := I.MakeSearchInfo(version);
6165
return Success(Some(info));
6266
}
@@ -74,6 +78,19 @@ module SearchConfigToInfo {
7478
forall b <- config.value.versions :: ValidBeaconVersion(b)
7579
}
7680

81+
// Valid state of the provided shared cache, if it exists
82+
predicate {:opaque} ValidSharedCache(config: BeaconKeySource)
83+
{
84+
&& (&& config.single?
85+
&& config.single.cache.Some?
86+
&& config.single.cache.value.Shared?
87+
==> && config.single.cache.value.Shared.ValidState())
88+
&& (&& config.multi?
89+
&& config.multi.cache.Some?
90+
&& config.multi.cache.value.Shared?
91+
==> && config.multi.cache.value.Shared.ValidState())
92+
}
93+
7794
// return true if, `keyFieldName` should be deleted from an item before writing
7895
function method ShouldDeleteKeyField(outer : DynamoDbTableEncryptionConfig, keyFieldName : string)
7996
: (ret : Result<bool, Error>)
@@ -101,7 +118,10 @@ module SearchConfigToInfo {
101118
)
102119
returns (output : Result<I.KeySource, Error>)
103120
modifies client.Modifies
121+
modifies keyStore.Modifies
104122
requires client.ValidState()
123+
requires ValidSharedCache(config)
124+
ensures ValidSharedCache(config)
105125
ensures client.ValidState()
106126
ensures output.Success? ==>
107127
&& output.value.ValidState()
@@ -119,18 +139,6 @@ module SearchConfigToInfo {
119139
&& config.multi.keyFieldName in outer.attributeActionsOnEncrypt
120140
&& outer.attributeActionsOnEncrypt[config.multi.keyFieldName] == SE.ENCRYPT_AND_SIGN
121141
==> output.Failure?
122-
// Not in Spec, but for now, SE does not support the Shared Cache Type
123-
ensures
124-
&& config.multi?
125-
&& config.multi.cache.Some?
126-
&& config.multi.cache.value.Shared?
127-
==>
128-
&& output.Failure?
129-
// If the failure was NOT caused by booting up the MPL
130-
&& !output.error.AwsCryptographyMaterialProviders?
131-
==>
132-
&& output.error.DynamoDbEncryptionException?
133-
&& output.error.message == "Searchable Encryption does not support the Shared Cache type at this time."
134142
{
135143
// TODO-FutureCleanUp : https://github.com/aws/aws-database-encryption-sdk-dynamodb/issues/1510
136144
// It is not-good that the MPL is initialized here;
@@ -140,23 +148,35 @@ module SearchConfigToInfo {
140148
var mpl :- mplR.MapFailure(e => AwsCryptographyMaterialProviders(e));
141149

142150
//= specification/searchable-encryption/search-config.md#key-store-cache
143-
//# For a [Single Key Store](#single-key-store-initialization) the [Entry Capacity](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cryptographic-materials-cache.md#entry-capacity)
144-
//# MUST be 1
145-
//# For a [Multi Key Store](#multi-key-store-initialization) the [Entry Capacity](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cryptographic-materials-cache.md#entry-capacity)
146-
//# MUST be key store's max cache size.
151+
//# For a Beacon Key Source a [CMC](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cryptographic-materials-cache.md)
152+
//# MUST be created.
153+
//# For a [Single Key Store](#single-key-store-initialization), either the user provides a cache, or we create a cache that has [Entry Capacity](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cryptographic-materials-cache.md#entry-capacity)
154+
//# equal to 1.
155+
//# For a [Multi Key Store](#multi-key-store-initialization), either the user provides a cache, or we create a cache that has [Entry Capacity](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cryptographic-materials-cache.md#entry-capacity)
156+
//# equal to 1000.
147157
var cacheType : MPT.CacheType :=
148158
if config.multi? then
149159
if config.multi.cache.Some? then
150160
config.multi.cache.value
151161
else
152162
MPT.Default(Default := MPT.DefaultCache(entryCapacity := 1000))
163+
else
164+
if config.single.cache.Some? then
165+
// If the user provides a CacheType, and it is NOT Shared,
166+
// we SHOULD only allow an entryCapacity = 1
167+
// because the SingleKeyStore only ever caches one value.
168+
// That is, we SHOULD add a check here for entryCapacity = 1.
169+
// However, that requires us to write an if block for each CacheType.
170+
// Also, it does NOT matter what the entryCapacity is, because the cache
171+
// can only hold one element at a time.
172+
config.single.cache.value
153173
else
154174
MPT.Default(Default := MPT.DefaultCache(entryCapacity := 1));
155175

156176
var cache;
157177
if cacheType.Shared? {
158-
return Failure(DynamoDbEncryptionException(message:="Searchable Encryption does not support the Shared Cache type at this time."));
159-
// cache := cacheType.Shared;
178+
cache := cacheType.Shared;
179+
reveal ValidSharedCache(config);
160180
} else {
161181
//= specification/searchable-encryption/search-config.md#key-store-cache
162182
//# For a Beacon Key Source a [CMC](../../submodules/MaterialProviders/aws-encryption-sdk-specification/framework/cryptographic-materials-cache.md)
@@ -168,20 +188,59 @@ module SearchConfigToInfo {
168188
cache :- maybeCache.MapFailure(e => AwsCryptographyMaterialProviders(e));
169189
}
170190

191+
var partitionIdBytes : seq<uint8>;
192+
193+
if config.multi? && config.multi.partitionId.Some? {
194+
partitionIdBytes :- UTF8.Encode(config.multi.partitionId.value)
195+
.MapFailure(
196+
e => Error.DynamoDbEncryptionException(
197+
message := "Could not UTF-8 Encode Partition ID from MultiKeyStore: " + e
198+
)
199+
);
200+
}
201+
else if config.single? && config.single.partitionId.Some? {
202+
partitionIdBytes :- UTF8.Encode(config.single.partitionId.value)
203+
.MapFailure(
204+
e => Error.DynamoDbEncryptionException(
205+
message := "Could not UTF-8 Encode Partition ID from SingleKeyStore: " + e
206+
)
207+
);
208+
}
209+
else {
210+
partitionIdBytes :- I.GenerateUuidBytes();
211+
}
212+
var getKeyStoreInfoOutput? := keyStore.GetKeyStoreInfo();
213+
var getKeyStoreInfoOutput :- getKeyStoreInfoOutput?.MapFailure(e => Error.AwsCryptographyKeyStore(e));
214+
var logicalKeyStoreName : string := getKeyStoreInfoOutput.logicalKeyStoreName;
215+
var logicalKeyStoreNameBytes : seq<uint8> :- UTF8.Encode(logicalKeyStoreName)
216+
.MapFailure(
217+
e => Error.DynamoDbEncryptionException(
218+
message := "Could not UTF-8 Encode Logical Key Store Name: " + e
219+
)
220+
);
221+
171222
if config.multi? {
172223
:- Need(0 < config.multi.cacheTTL, E("Beacon Cache TTL must be at least 1."));
173224
var deleteKey :- ShouldDeleteKeyField(outer, config.multi.keyFieldName);
174-
output := Success(I.KeySource(client, keyStore, I.MultiLoc(config.multi.keyFieldName, deleteKey), cache, config.multi.cacheTTL as uint32));
225+
output := Success(I.KeySource(client, keyStore, I.MultiLoc(config.multi.keyFieldName, deleteKey), cache, config.multi.cacheTTL as uint32, partitionIdBytes, logicalKeyStoreNameBytes));
175226
} else {
176227
:- Need(0 < config.single.cacheTTL, E("Beacon Cache TTL must be at least 1."));
177-
output := Success(I.KeySource(client, keyStore, I.SingleLoc(config.single.keyId), cache, config.single.cacheTTL as uint32));
228+
output := Success(I.KeySource(client, keyStore, I.SingleLoc(config.single.keyId), cache, config.single.cacheTTL as uint32, partitionIdBytes, logicalKeyStoreNameBytes));
229+
}
230+
assert output.value.ValidState() by {
231+
// This axiom is important because it is not easy to prove
232+
// keyStore.Modifies !! cache.Modifies for a shared cache.
233+
assume {:axiom} keyStore.Modifies !! cache.Modifies;
178234
}
179235
}
180236

181237
// convert configured BeaconVersion to internal BeaconVersion
182238
method ConvertVersion(outer : DynamoDbTableEncryptionConfig, config : BeaconVersion)
183239
returns (output : Result<I.ValidBeaconVersion, Error>)
184240
requires ValidBeaconVersion(config)
241+
requires ValidSharedCache(config.keySource)
242+
modifies config.keyStore.Modifies
243+
ensures ValidSharedCache(config.keySource)
185244
ensures output.Success? ==>
186245
&& output.value.ValidState()
187246
&& fresh(output.value.keySource.client)

0 commit comments

Comments
 (0)