createEncryptionKeyValidationHook function
Creates a hook that validates encryption key on first access.
Attempts to decrypt a test entry to verify key is valid. If test fails, calls onKeyInvalid callback.
NOTE: This hook accesses storage directly to avoid infinite recursion. The test entry must be created separately before using this hook.
Implementation
PVCacheHook createEncryptionKeyValidationHook({
required String testKey,
String testValue = '_pvcache_key_test',
String? encryptionKey,
String keyName = '_pvcache_encryption_key',
Future<void> Function()? onKeyInvalid,
int priority = -100,
}) {
bool validated = false;
return PVCacheHook(
eventString: 'encryption_key_validation',
eventFlow: EventFlow.preProcess,
priority: priority,
actionTypes: [ActionType.get, ActionType.put],
hookFunction: (ctx) async {
// Only validate once per cache instance
if (validated) return;
// Skip validation for the test key itself to avoid recursion
if (ctx.resolvedKey == testKey) {
validated = true;
return;
}
// Access storage directly to avoid recursion
final bridge = PVBridge();
final entryDb = await bridge.getDatabaseForType(
ctx.cache.entryStorageType,
heavy: ctx.cache.heavy,
env: ctx.cache.env,
);
final entryStore = bridge.getStore(
ctx.cache.env,
ctx.cache.entryStorageType,
);
// Read test entry directly from storage
final encryptedTestValue = await entryStore.record(testKey).get(entryDb);
if (encryptedTestValue == null) {
// Test entry doesn't exist, this is first run - mark as validated
// The test entry will be created by normal cache operations later
validated = true;
return;
}
// Read metadata to check if it's encrypted
final metaDb = await bridge.getDatabaseForType(
ctx.cache.metadataStorageType,
heavy: ctx.cache.heavy,
env: ctx.cache.env,
);
final metaStoreName = ctx.cache.metadataNameFunction!(ctx.cache.env);
final metaStore = bridge.getStore(
metaStoreName,
ctx.cache.metadataStorageType,
);
final metadata = await metaStore.record(testKey).get(metaDb);
// If test entry exists and is encrypted, try to decrypt it
if (metadata?['_encrypted'] == true) {
try {
final key = encryptionKey ?? await getOrCreateEncryptionKey(keyName);
final cipher = AESCipher(key);
final decrypted = cipher.decryptString(encryptedTestValue as String);
final value = jsonDecode(decrypted);
if (value == testValue) {
// Key is valid
validated = true;
return;
}
// Value doesn't match - key changed
if (onKeyInvalid != null) {
await onKeyInvalid();
}
} catch (e) {
// Decryption failed - key changed
if (onKeyInvalid != null) {
await onKeyInvalid();
}
}
}
validated = true;
},
);
}