Skip to content

feat(SharedCache): Shared Cache for Searchable Encryption #1476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
58f411e
first commit
RitvikKapila Nov 20, 2024
57f6ea6
update smithy models
RitvikKapila Nov 20, 2024
b7a6159
add partition ID and cache
RitvikKapila Nov 28, 2024
071cf6d
fix tests
RitvikKapila Nov 30, 2024
421a648
format
RitvikKapila Nov 30, 2024
d30024e
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Nov 30, 2024
c226970
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Dec 4, 2024
cda1fdf
fix
RitvikKapila Dec 4, 2024
071263d
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Dec 13, 2024
9f84eb9
updates
RitvikKapila Dec 13, 2024
77abfc2
m
RitvikKapila Dec 13, 2024
5ababd4
fix Dafny verification
RitvikKapila Dec 14, 2024
ee868b0
format
RitvikKapila Dec 14, 2024
2fac140
almost fix verification
RitvikKapila Dec 18, 2024
2ef5869
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Dec 18, 2024
c90ec36
fix main merge
RitvikKapila Dec 18, 2024
59862ee
format
RitvikKapila Dec 18, 2024
be3e77c
add logicalKeyStoreName
RitvikKapila Dec 23, 2024
6d5da16
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Dec 23, 2024
c675502
format
RitvikKapila Dec 24, 2024
fe14024
add tests
RitvikKapila Jan 13, 2025
94c421e
update
RitvikKapila Jan 13, 2025
38b93dd
increase stack size
RitvikKapila Jan 13, 2025
ccb525d
hash cache id
RitvikKapila Jan 14, 2025
453f0fa
m
RitvikKapila Jan 14, 2025
320de24
fix Duvet
RitvikKapila Jan 14, 2025
ca446e3
fix
RitvikKapila Jan 14, 2025
ed67691
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Jan 15, 2025
155a74c
address comments
RitvikKapila Jan 17, 2025
10555e1
format
RitvikKapila Jan 17, 2025
20e0259
use ubuntu-22.04
RitvikKapila Jan 17, 2025
1868de5
resolve comments
RitvikKapila Jan 17, 2025
326baaf
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Jan 17, 2025
5e4a115
fix(verification): Verify cache identifier (#1578)
RitvikKapila Jan 22, 2025
2d30327
Merge branch 'main' into rkapila/shared-cache-beacons
RitvikKapila Jan 22, 2025
7d3d211
fix spec
RitvikKapila Jan 23, 2025
48b33e5
point resource and scope id to mpl spec
RitvikKapila Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,8 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.internald
datatype MultiKeyStore = | MultiKeyStore (
nameonly keyFieldName: string ,
nameonly cacheTTL: int32 ,
nameonly cache: Option<AwsCryptographyMaterialProvidersTypes.CacheType> := Option.None
nameonly cache: Option<AwsCryptographyMaterialProvidersTypes.CacheType> := Option.None ,
nameonly partitionId: Option<string> := Option.None
)
datatype PartOnly = | PartOnly (

Expand Down Expand Up @@ -388,7 +389,9 @@ module {:extern "software.amazon.cryptography.dbencryptionsdk.dynamodb.internald
}
datatype SingleKeyStore = | SingleKeyStore (
nameonly keyId: string ,
nameonly cacheTTL: int32
nameonly cacheTTL: int32 ,
nameonly cache: Option<AwsCryptographyMaterialProvidersTypes.CacheType> := Option.None ,
nameonly partitionId: Option<string> := Option.None
)
datatype StandardBeacon = | StandardBeacon (
nameonly name: string ,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,10 @@ structure SingleKeyStore {
@required
@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.")
cacheTTL: Integer,
@documentation("Provide the Shared Cache for Searchable Encryption.")
cache : CacheType,
@documentation("Partition ID to distinguish Beacon Key Sources writing to a cache. If the Partition ID is the same for two Beacon Key Sources, they can share the same cache entries in the cache.")
partitionId: String
}

//= specification/searchable-encryption/search-config.md#multi-key-store-initialization
Expand All @@ -734,7 +738,9 @@ structure MultiKeyStore {
@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.")
cacheTTL: Integer,
@javadoc("Which type of local cache to use.")
cache : CacheType
cache : CacheType,
@documentation("Partition ID to distinguish Beacon Key Sources writing to a cache. If the Partition ID is the same for two Beacon Key Sources, they can share the same cache entries in the cache.")
partitionId: String
}

//= specification/searchable-encryption/search-config.md#beacon-key-source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module SearchConfigToInfo {
import SE = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes
import MPT = AwsCryptographyMaterialProvidersTypes
import Primitives = AtomicPrimitives
import UUID

// convert configured SearchConfig to internal SearchInfo
method Convert(outer : DynamoDbTableEncryptionConfig)
Expand Down Expand Up @@ -134,6 +135,9 @@ module SearchConfigToInfo {
config.multi.cache.value
else
MPT.Default(Default := MPT.DefaultCache(entryCapacity := 1000))
else
if config.single.cache.Some? then
config.single.cache.value
else
MPT.Default(Default := MPT.DefaultCache(entryCapacity := 1));

Expand All @@ -151,13 +155,41 @@ module SearchConfigToInfo {
cache :- maybeCache.MapFailure(e => AwsCryptographyMaterialProviders(e));
}

var partitionIdBytes : seq<uint8>;

if config.multi? && config.multi.partitionId.Some? {
partitionIdBytes :- UTF8.Encode(config.multi.partitionId.value)
.MapFailure(
e => Error.DynamoDbEncryptionException(
message := "Could not UTF-8 Encode Partition ID from MultiKeyStore: " + e
)
);
}
if config.single? && config.single.partitionId.Some? {
partitionIdBytes :- UTF8.Encode(config.single.partitionId.value)
.MapFailure(
e => Error.DynamoDbEncryptionException(
message := "Could not UTF-8 Encode Partition ID from SingleKeyStore: " + e
)
);
}
else {
var uuid? := UUID.GenerateUUID();

var uuid :- uuid?
.MapFailure(e => Error.DynamoDbEncryptionException(message := e));

partitionIdBytes :- UUID.ToByteArray(uuid)
.MapFailure(e => Error.DynamoDbEncryptionException(message := e));
}

if config.multi? {
:- Need(0 < config.multi.cacheTTL, E("Beacon Cache TTL must be at least 1."));
var deleteKey :- ShouldDeleteKeyField(outer, config.multi.keyFieldName);
output := Success(I.KeySource(client, keyStore, I.MultiLoc(config.multi.keyFieldName, deleteKey), cache, config.multi.cacheTTL as uint32));
output := Success(I.KeySource(client, keyStore, I.MultiLoc(config.multi.keyFieldName, deleteKey), cache, config.multi.cacheTTL as uint32, partitionIdBytes));
} else {
:- Need(0 < config.single.cacheTTL, E("Beacon Cache TTL must be at least 1."));
output := Success(I.KeySource(client, keyStore, I.SingleLoc(config.single.keyId), cache, config.single.cacheTTL as uint32));
output := Success(I.KeySource(client, keyStore, I.SingleLoc(config.single.keyId), cache, config.single.cacheTTL as uint32, partitionIdBytes));
}
}

Expand Down
53 changes: 48 additions & 5 deletions DynamoDbEncryption/dafny/DynamoDbEncryption/src/SearchInfo.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module SearchableEncryptionInfo {
import MP = AwsCryptographyMaterialProvidersTypes
import KeyStoreTypes = AwsCryptographyKeyStoreTypes
import SE = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes
import opened CacheConstants

//= specification/searchable-encryption/search-config.md#version-number
//= type=implication
Expand Down Expand Up @@ -137,7 +138,8 @@ module SearchableEncryptionInfo {
store : ValidStore,
keyLoc : KeyLocation,
cache : MP.ICryptographicMaterialsCache,
cacheTTL : uint32
cacheTTL : uint32,
partitionIdBytes : seq<uint8>
) {
function Modifies() : set<object> {
client.Modifies + store.Modifies
Expand All @@ -153,7 +155,7 @@ module SearchableEncryptionInfo {
{
if keyLoc.SingleLoc? {
:- Need(keyId.DontUseKeyId?, E("KeyID should not be supplied with a SingleKeyStore"));
var theMap :- getKeysCache(stdNames, keyLoc.keyId);
var theMap :- getKeysCache(stdNames, keyLoc.keyId, cacheTTL as MP.PositiveLong, partitionIdBytes);
return Success(Keys(theMap));
} else if keyLoc.LiteralLoc? {
:- Need(keyId.DontUseKeyId?, E("KeyID should not be supplied with a LiteralKeyStore"));
Expand All @@ -163,7 +165,7 @@ module SearchableEncryptionInfo {
match keyId {
case DontUseKeyId => return Failure(E("KeyID must not be supplied with a MultiKeyStore"));
case ShouldHaveKeyId => return Success(ShouldHaveKeys);
case KeyId(id) => var theMap :- getKeysCache(stdNames, id); return Success(Keys(theMap));
case KeyId(id) => var theMap :- getKeysCache(stdNames, id, cacheTTL as MP.PositiveLong, partitionIdBytes); return Success(Keys(theMap));
}
}
}
Expand All @@ -180,9 +182,23 @@ module SearchableEncryptionInfo {
return Success(keyLoc.keys);
}

// Checks if (time_now - cache creation time of the extracted cache entry) is less than the allowed
// TTL of the current Beacon Key Source calling the getEntry method from the cache.
// Mitigates risk if another Beacon Key Source wrote the entry with a longer TTL.
predicate method cacheEntryWithinLimits(
creationTime: MP.PositiveLong,
now: MP.PositiveLong,
ttlSeconds: MP.PositiveLong
): (output: bool)
{
now - creationTime <= ttlSeconds as MP.PositiveLong
}

method getKeysCache(
stdNames : seq<string>,
keyId : string
keyId : string,
cacheTTL : MP.PositiveLong,
partitionIdBytes : seq<uint8>
)
returns (output : Result<HmacKeyMap, Error>)
requires Seq.HasNoDuplicates(stdNames)
Expand Down Expand Up @@ -241,8 +257,21 @@ module SearchableEncryptionInfo {

)
{

// Resource ID: Searchable Encryption [0x02]
var resourceId : seq<uint8> := RESOURCE_ID_HIERARCHICAL_KEYRING;

// Scope ID: Searchable Encryption [0x03]
var scopeId : seq<uint8> := SCOPE_ID_SEARCHABLE_ENCRYPTION;

// Create the Suffix
var keyIdBytesR := UTF8.Encode(keyId);
var keyIdBytes :- keyIdBytesR.MapFailure(e => E(e));
var suffix : seq<uint8> := keyIdBytes;

// Append Resource Id, Scope Id, Partition Id, and Suffix to create the cache identifier
var identifier := resourceId + NULL_BYTE + scopeId + NULL_BYTE + partitionIdBytes + NULL_BYTE + suffix;

var getCacheInput := MP.GetCacheEntryInput(identifier := keyIdBytes, bytesUsed := None);
verifyValidStateCache(cache);
assume {:axiom} cache.Modifies == {};
Expand All @@ -253,7 +282,21 @@ module SearchableEncryptionInfo {
return Failure(AwsCryptographyMaterialProviders(AwsCryptographyMaterialProviders:=getCacheOutput.error));
}

if getCacheOutput.Failure? {
var now := Time.GetCurrent();

// //= specification/searchable-encryption/search-config.md#<heading>
//# If using a `Shared` cache across multiple Beacon Key Sources,
//# different Key Sources having the same `beaconKey` can have different TTLs.
//# In such a case, the expiry time in the cache is set according to the Beacon Key Source that populated the cache.
//# There MUST be a check (cacheEntryWithinLimits) to make sure that for the cache entry found, who's TTL has NOT expired,
//# `time.now() - cacheEntryCreationTime <= ttlSeconds` is true and
//# valid for TTL of the Beacon Key Source getting the cache entry.
//# If this is NOT true, then we MUST treat the cache entry as expired.
if getCacheOutput.Failure? || !cacheEntryWithinLimits(
creationTime := getCacheOutput.value.creationTime,
now := now,
ttlSeconds := cacheTTL
) {
//= specification/searchable-encryption/search-config.md#beacon-keys
//# Beacon keys MUST be obtained from the configured [Beacon Key Source](#beacon-key-source).
var maybeRawBeaconKeyMaterials := store.GetBeaconKey(
Expand Down
2 changes: 1 addition & 1 deletion DynamoDbEncryption/dafny/DynamoDbEncryption/src/Util.dfy
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module DynamoDbEncryptionUtil {
//
// Counterintuitively, DontUseKeys and DontUseKeyId are very different things.
// DontUseKeyId is the usual thing for single tenant, meaning to use the pre-configured
// KeyId, rather than fnd a new one from somewhere.
// KeyId, rather than find a new one from somewhere.
// DontUseKeys means to not hash the values into beacons,
// but to leave them plaintext, which is necessary for the post-query filtering.
datatype MaybeKeyMap =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ module BeaconTestFixtures {
cache := MPT.Default(Default := MPT.DefaultCache(entryCapacity := 3))
);
var cache :- expect mpl.CreateCryptographicMaterialsCache(input);
return SI.KeySource(client, version.keyStore, SI.LiteralLoc(keys), cache, 0);
// Create a test partitionIdBytes
var partitionIdBytes : seq<uint8> := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
return SI.KeySource(client, version.keyStore, SI.LiteralLoc(keys), cache, 0, partitionIdBytes);
}

method GetMultiSource(keyName : string, version : BeaconVersion) returns (output : SI.KeySource)
Expand All @@ -257,7 +259,9 @@ module BeaconTestFixtures {
cache := MPT.Default(Default := MPT.DefaultCache(entryCapacity := 3))
);
var cache :- expect mpl.CreateCryptographicMaterialsCache(input);
return SI.KeySource(client, version.keyStore, SI.MultiLoc(keyName, false), cache, 0);
// Create a test partitionIdBytes
var partitionIdBytes : seq<uint8> := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
return SI.KeySource(client, version.keyStore, SI.MultiLoc(keyName, false), cache, 0, partitionIdBytes);
}

const SimpleItem : DDB.AttributeMap := map[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,10 +764,10 @@ abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbTransformsService
tmp5.search.Some? ==>
var tmps6 := set t6 | t6 in tmp5.search.value.versions;
forall tmp6 :: tmp6 in tmps6 ==>
tmp6.keySource.multi? ==>
tmp6.keySource.multi.cache.Some? ==>
tmp6.keySource.multi.cache.value.Shared? ==>
tmp6.keySource.multi.cache.value.Shared.ValidState()
tmp6.keySource.single? ==>
tmp6.keySource.single.cache.Some? ==>
tmp6.keySource.single.cache.value.Shared? ==>
tmp6.keySource.single.cache.value.Shared.ValidState()
modifies set tmps7 <- set t7 <- config.tableEncryptionConfigs.Values | true
&& t7.keyring.Some?
:: t7.keyring.value,
Expand All @@ -788,10 +788,10 @@ abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbTransformsService
modifies set tmps12 <- set t12 <- config.tableEncryptionConfigs.Values | true
&& t12.search.Some?
, t13 <- t12.search.value.versions | true
&& t13.keySource.multi?
&& t13.keySource.multi.cache.Some?
&& t13.keySource.multi.cache.value.Shared?
:: t13.keySource.multi.cache.value.Shared,
&& t13.keySource.single?
&& t13.keySource.single.cache.Some?
&& t13.keySource.single.cache.value.Shared?
:: t13.keySource.single.cache.value.Shared,
obj <- tmps12.Modifies | obj in tmps12.Modifies :: obj
ensures res.Success? ==>
&& fresh(res.value)
Expand All @@ -816,10 +816,10 @@ abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbTransformsService
) - ( set tmps19 <- set t19 <- config.tableEncryptionConfigs.Values | true
&& t19.search.Some?
, t20 <- t19.search.value.versions | true
&& t20.keySource.multi?
&& t20.keySource.multi.cache.Some?
&& t20.keySource.multi.cache.value.Shared?
:: t20.keySource.multi.cache.value.Shared,
&& t20.keySource.single?
&& t20.keySource.single.cache.Some?
&& t20.keySource.single.cache.value.Shared?
:: t20.keySource.single.cache.value.Shared,
obj <- tmps19.Modifies | obj in tmps19.Modifies :: obj
) )
&& fresh(res.value.History)
Expand Down Expand Up @@ -847,10 +847,10 @@ abstract module AbstractAwsCryptographyDbEncryptionSdkDynamoDbTransformsService
tmp26.search.Some? ==>
var tmps27 := set t27 | t27 in tmp26.search.value.versions;
forall tmp27 :: tmp27 in tmps27 ==>
tmp27.keySource.multi? ==>
tmp27.keySource.multi.cache.Some? ==>
tmp27.keySource.multi.cache.value.Shared? ==>
tmp27.keySource.multi.cache.value.Shared.ValidState()
tmp27.keySource.single? ==>
tmp27.keySource.single.cache.Some? ==>
tmp27.keySource.single.cache.value.Shared? ==>
tmp27.keySource.single.cache.value.Shared.ValidState()

// Helper functions for the benefit of native code to create a Success(client) without referring to Dafny internals
function method CreateSuccessOfClient(client: IDynamoDbEncryptionTransformsClient): Result<IDynamoDbEncryptionTransformsClient, Error> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ module
var tableName: string := tableNamesSeq[i];

var inputConfig := config.tableEncryptionConfigs[tableName];
:- Need(inputConfig.logicalTableName !in allLogicalTableNames, E("Duplicate logical table maped to multipule physical tables: " + inputConfig.logicalTableName));
:- Need(inputConfig.logicalTableName !in allLogicalTableNames, E("Duplicate logical table mapped to multiple physical tables: " + inputConfig.logicalTableName));

assert SearchConfigToInfo.ValidSearchConfig(inputConfig.search);
SearchInModifies(config, tableName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,19 @@ public static MultiKeyStore MultiKeyStore(
)
)
: Option.create_None(CacheType._typeDescriptor());
return new MultiKeyStore(keyFieldName, cacheTTL, cache);
Option<DafnySequence<? extends Character>> partitionId;
partitionId =
Objects.nonNull(nativeValue.partitionId())
? Option.create_Some(
DafnySequence._typeDescriptor(TypeDescriptor.CHAR),
software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence(
nativeValue.partitionId()
)
)
: Option.create_None(
DafnySequence._typeDescriptor(TypeDescriptor.CHAR)
);
return new MultiKeyStore(keyFieldName, cacheTTL, cache, partitionId);
}

public static PartOnly PartOnly(
Expand Down Expand Up @@ -747,7 +759,29 @@ public static SingleKeyStore SingleKeyStore(
);
Integer cacheTTL;
cacheTTL = (nativeValue.cacheTTL());
return new SingleKeyStore(keyId, cacheTTL);
Option<CacheType> cache;
cache =
Objects.nonNull(nativeValue.cache())
? Option.create_Some(
CacheType._typeDescriptor(),
software.amazon.cryptography.materialproviders.ToDafny.CacheType(
nativeValue.cache()
)
)
: Option.create_None(CacheType._typeDescriptor());
Option<DafnySequence<? extends Character>> partitionId;
partitionId =
Objects.nonNull(nativeValue.partitionId())
? Option.create_Some(
DafnySequence._typeDescriptor(TypeDescriptor.CHAR),
software.amazon.smithy.dafny.conversion.ToDafny.Simple.CharacterSequence(
nativeValue.partitionId()
)
)
: Option.create_None(
DafnySequence._typeDescriptor(TypeDescriptor.CHAR)
);
return new SingleKeyStore(keyId, cacheTTL, cache, partitionId);
}

public static StandardBeacon StandardBeacon(
Expand Down
Loading
Loading