Refactor storeEncryptedDatabase method to separate metadata set (#771)

This commit is contained in:
Leendert de Borst
2025-05-03 18:08:22 +02:00
parent 616705b80d
commit 1cc6567d6f
9 changed files with 152 additions and 38 deletions

View File

@@ -49,11 +49,9 @@ export const DbProvider: React.FC<{ children: React.ReactNode }> = ({ children }
vaultRevisionNumber: vaultResponse.vault.currentRevisionNumber,
};
// Initialize the SQLite client with both database and metadata
await sqliteClient.storeEncryptedDatabase(
vaultResponse.vault.blob,
metadata
);
// Store the encrypted database and metadata (metadata is stored in plain text in UserDefaults)
await sqliteClient.storeEncryptedDatabase(vaultResponse.vault.blob);
await sqliteClient.storeMetadata(JSON.stringify(metadata));
// Initialize the database in the native module
await unlockVault();

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
@@ -208,7 +208,7 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@@ -218,11 +218,62 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKit; sourceTree = "<group>"; };
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKitTests; sourceTree = "<group>"; };
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUI; sourceTree = "<group>"; };
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultModels; sourceTree = "<group>"; };
CEE909812DA548C7008D568F /* Autofill */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Autofill; sourceTree = "<group>"; };
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultStoreKit;
sourceTree = "<group>";
};
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultStoreKitTests;
sourceTree = "<group>";
};
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultUI;
sourceTree = "<group>";
};
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultModels;
sourceTree = "<group>";
};
CEE909812DA548C7008D568F /* Autofill */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = Autofill;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -1204,7 +1255,10 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -1259,7 +1313,10 @@
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;

View File

@@ -12,10 +12,6 @@ NS_ASSUME_NONNULL_BEGIN
@interface RCTNativeVaultManager : NSObject <NativeVaultManagerSpec>
- (void)getEncryptedDatabase:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)getCurrentVaultRevisionNumber:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
- (void)setCurrentVaultRevisionNumber:(double)revisionNumber resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
@end
NS_ASSUME_NONNULL_END

View File

@@ -96,8 +96,12 @@
[vaultManager setAutoLockTimeout:timeout resolver:resolve rejecter:reject];
}
- (void)storeDatabase:(NSString *)base64EncryptedDb metadata:(NSString *)metadata resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
[vaultManager storeDatabase:base64EncryptedDb metadata:metadata resolver:resolve rejecter:reject];
- (void)storeDatabase:(NSString *)base64EncryptedDb resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
[vaultManager storeDatabase:base64EncryptedDb resolver:resolve rejecter:reject];
}
- (void)storeMetadata:(NSString *)metadata resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
[vaultManager storeMetadata:metadata resolver:resolve rejecter:reject];
}
- (void)storeEncryptionKey:(NSString *)base64EncryptionKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {

View File

@@ -19,17 +19,28 @@ public class VaultManager: NSObject {
@objc
func storeDatabase(_ base64EncryptedDb: String,
metadata: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
do {
try vaultStore.storeEncryptedDatabase(base64EncryptedDb, metadata: metadata)
try vaultStore.storeEncryptedDatabase(base64EncryptedDb)
resolve(nil)
} catch {
reject("DB_ERROR", "Failed to store database: \(error.localizedDescription)", error)
}
}
@objc
func storeMetadata(_ metadata: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
do {
try vaultStore.storeMetadata(metadata)
resolve(nil)
} catch {
reject("METADATA_ERROR", "Failed to store metadata: \(error.localizedDescription)", error)
}
}
@objc
func setAuthMethods(_ authMethods: [String],
resolver resolve: @escaping RCTPromiseResolveBlock,

View File

@@ -237,14 +237,15 @@ public class VaultStore {
}
// Store the encrypted database (base64 encoded) in the app's documents directory
// and metadata in UserDefaults
// TODO: refactor metadata save and retrieve calls to separate calls for better clarity throughout
// full call chain?
public func storeEncryptedDatabase(_ base64EncryptedDb: String, metadata: String) throws {
// Store the encrypted database (base64 encoded) in the app's documents directory
public func storeEncryptedDatabase(_ base64EncryptedDb: String) throws {
try base64EncryptedDb.write(to: getEncryptedDbPath(), atomically: true, encoding: .utf8)
}
// Store metadata in UserDefaults
// Store metadata in UserDefaults
// Metadata is stored in plain text in UserDefaults. The metadata consists of the following:
// - public and private email domains
// - vault revision number
public func storeMetadata(_ metadata: String) throws {
userDefaults.set(metadata, forKey: vaultMetadataKey)
userDefaults.synchronize()
}
@@ -742,7 +743,8 @@ public class VaultStore {
let encryptedBase64String = encryptedBase64Data.base64EncodedString()
// Persist the encrypted base64 string.
try storeEncryptedDatabase(encryptedBase64String, metadata: getVaultMetadata()!)
try storeEncryptedDatabase(encryptedBase64String)
try storeMetadata(getVaultMetadata()!)
// Cleanup
try FileManager.default.removeItem(at: tempDbPath)

View File

@@ -29,8 +29,19 @@ final class VaultStoreKitTests: XCTestCase {
// Load and store the encrypted database
let encryptedDb = try loadTestDatabase()
// TODO: get metadata via vault generation and pass it here so we have all info we need for all tests.
try vaultStore.storeEncryptedDatabase(encryptedDb, metadata: "")
// Store the database
try vaultStore.storeEncryptedDatabase(encryptedDb)
// Store metadata with example domains
let metadata = """
{
"publicEmailDomains": ["spamok.com", "spamok.nl"],
"privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"],
"vaultRevisionNumber": 1
}
"""
try vaultStore.storeMetadata(metadata)
// Unlock the vault (which loads, decrypts and opens the database in memory)
try vaultStore.unlockVault()
@@ -45,8 +56,17 @@ final class VaultStoreKitTests: XCTestCase {
// Load and store the encrypted database
let encryptedDb = try loadTestDatabase()
// TODO: get metadata via vault generation and pass it here so we have all info we need for all tests.
try vaultStore.storeEncryptedDatabase(encryptedDb, metadata: "")
try vaultStore.storeEncryptedDatabase(encryptedDb)
// Store metadata with example domains
let metadata = """
{
"publicEmailDomains": ["spamok.com", "spamok.nl"],
"privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"],
"vaultRevisionNumber": 1
}
"""
try vaultStore.storeMetadata(metadata)
// Unlock the vault (which loads, decrypts and opens the database in memory)
try vaultStore.unlockVault()
@@ -75,7 +95,17 @@ final class VaultStoreKitTests: XCTestCase {
// Load and store the encrypted database
let encryptedDb = try loadTestDatabase()
try vaultStore.storeEncryptedDatabase(encryptedDb, metadata: "")
try vaultStore.storeEncryptedDatabase(encryptedDb)
// Store metadata with example domains
let metadata = """
{
"publicEmailDomains": ["spamok.com", "spamok.nl"],
"privateEmailDomains": ["aliasvault.net", "main.aliasvault.net"],
"vaultRevisionNumber": 1
}
"""
try vaultStore.storeMetadata(metadata)
// Unlock the vault (which loads, decrypts and opens the database in memory)
try vaultStore.unlockVault()

View File

@@ -13,7 +13,8 @@ export interface Spec extends TurboModule {
unlockVault(): Promise<boolean>;
// Database operations
storeDatabase(base64EncryptedDb: string, metadata: string): Promise<void>;
storeDatabase(base64EncryptedDb: string): Promise<void>;
storeMetadata(metadata: string): Promise<void>;
setAuthMethods(authMethods: string[]): Promise<void>;
storeEncryptionKey(base64EncryptionKey: string): Promise<void>;
getEncryptedDatabase(): Promise<string | null>;

View File

@@ -16,16 +16,31 @@ class SqliteClient {
/**
* Store the encrypted database via the native code implementation.
*/
async storeEncryptedDatabase(base64EncryptedDb: string, metadata: VaultMetadata): Promise<void> {
async storeEncryptedDatabase(base64EncryptedDb: string): Promise<void> {
try {
const metadataJson = JSON.stringify(metadata);
await NativeVaultManager.storeDatabase(base64EncryptedDb, metadataJson);
await NativeVaultManager.storeDatabase(base64EncryptedDb);
} catch (error) {
console.error('Error initializing SQLite database:', error);
throw error;
}
}
/**
* Store the vault metadata via the native code implementation.
*
* Metadata is stored in plain text in UserDefaults. The metadata consists of the following:
* - public and private email domains
* - vault revision number
*/
async storeMetadata(metadata: string): Promise<void> {
try {
await NativeVaultManager.storeMetadata(metadata);
} catch (error) {
console.error('Error storing vault metadata:', error);
throw error;
}
}
/**
* Retrieve the vault metadata from native storage
* @returns The parsed VaultMetadata object