Skip to content
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

fix: fixed issues related to DBEncryption in RSDBPersistentManager and enhanced error handling #403

Merged
merged 11 commits into from
Oct 9, 2023
223 changes: 150 additions & 73 deletions Sources/Classes/RSDBPersistentManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
NSString* _Nonnull const COL_DM_PROCESSED = @"dm_processed";
NSString* _Nonnull const ENCRYPTED_DB_NAME = @"rl_persistence_encrypted.sqlite";
NSString* _Nonnull const UNENCRYPTED_DB_NAME = @"rl_persistence.sqlite";
NSString* _Nonnull const SQLCIPHER_TEST_DB_NAME = @"rl_sqlcipher_test_db.sqlite";

@implementation RSDBPersistentManager {
NSLock* lock;
Expand All @@ -34,9 +35,13 @@ @implementation RSDBPersistentManager {
- (instancetype)initWithDBEncryption:(RSDBEncryption * __nullable)dbEncryption {
self = [super init];
if (self) {
self->lock = [[NSLock alloc] init];
self->database = [[self getDatabaseProvider:dbEncryption] getDatabase];

self->lock = [[NSLock alloc] init];
[self->lock lock];
[self createDB:dbEncryption];
[self->lock unlock];

isReturnClauseSupported = [self doesReturnClauseExists];
if(isReturnClauseSupported) {
[RSLogger logVerbose:@"RSDBPersistentManager: init: SQLiteVersion is >=3.35.0, hence return clause can be used"];
Expand All @@ -48,19 +53,24 @@ - (instancetype)initWithDBEncryption:(RSDBEncryption * __nullable)dbEncryption {
}

- (id<RSDatabaseProvider>)getDatabaseProvider:(RSDBEncryption * __nullable)dbEncryption {
if (dbEncryption == nil) {
if (dbEncryption == nil || !dbEncryption.enable) {
return [RSDefaultDatabaseProvider new];
} else {
return dbEncryption.databaseProvider;
}
}

- (void)createDB:(RSDBEncryption * __nullable)dbEncryption {
[self->lock lock];
BOOL isEncryptedDBExists = [RSUtils isFileExists:ENCRYPTED_DB_NAME];
BOOL isUnencryptedDBExists = [RSUtils isFileExists:UNENCRYPTED_DB_NAME];
BOOL isEncryptionNeeded = [self isEncryptionNeeded:dbEncryption];


if (isEncryptionNeeded && ![self isSQLCipherAvailable]) {
[RSLogger logError:@"RSDBPersistentManager: createDB: Cannot encrypt the Database as SQLCipher wasn't linked correctly"];
isEncryptionNeeded = NO;
}

// when both encrypted and unencrypted db doesn't exists
if (!isEncryptedDBExists && !isUnencryptedDBExists) {
// fresh Install
if (isEncryptionNeeded) {
Expand All @@ -71,86 +81,50 @@ - (void)createDB:(RSDBEncryption * __nullable)dbEncryption {
[self openUnencryptedDB];
}
} else if (isEncryptedDBExists) {
if (isEncryptionNeeded) {
// open encrypted database with key
int code = [self openEncryptedDB:dbEncryption.key];
if (code == SQLITE_NOTADB) {
// when key is wrong
// delete encrypted database; then open new encrypted database
// all previous events will be deleted
[RSLogger logError:@"RSDBPersistentManager: createDB: Wrong key is provided. Deleting encrypted DB and creating a new unencrypted DB"];
[self closeDB];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openEncryptedDB:dbEncryption.key];
}
} else {
if (dbEncryption == nil || dbEncryption.key == nil) {
// no key is provided
// delete encrypted database; then open unencrypted database
// all previous events will be deleted
[RSLogger logError:@"RSDBPersistentManager: createDB: No key is provided. Deleting encrypted DB and creating a new unencrypted DB"];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openUnencryptedDB];
} else {
int code = [self openEncryptedDB:dbEncryption.key];
switch (code) {
// when key is correct
// decyprt database; then open unencrypted database
case SQLITE_OK: {
code = [self decryptDB:dbEncryption.key];
if (code == SQLITE_OK) {
[self closeDB];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openUnencryptedDB];
} else {
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: createDB: Failed to decrypt, error code: %d", code]];
}
}
break;
// when key is wrong
// delete encrypted database; then open unencrypted database
// all previous events will be deleted
case SQLITE_NOTADB: {
[RSLogger logError:@"RSDBPersistentManager: createDB: Wrong key is provided. Deleting encrypted DB and creating a new unencrypted DB"];
[self closeDB];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openUnencryptedDB];
}
break;
default:
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: createDB: Failed to decrypt, error code: %d", code]];
break;
}
}
}
// when only encrypted db exists
[self handleWhenEncryptedDBExists:dbEncryption isEncryptionNeeded:isEncryptionNeeded];
} else {
if (isEncryptionNeeded) {
// encyprt database; then open encrypted database
[self openUnencryptedDB];
int code = [self encryptDB:dbEncryption.key];
if (code == SQLITE_OK) {
[self closeDB];
[RSUtils removeFile:UNENCRYPTED_DB_NAME];
[self openEncryptedDB:dbEncryption.key];
} else {
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: createDB: Failed to encrypt, error code: %d", code]];
}
} else {
// open unencrypted database
[self openUnencryptedDB];
}
// when only unencrypted db exists
[self handleWhenUnencryptedDBExists:dbEncryption isEncryptionNeeded:isEncryptionNeeded];
}
[self->lock unlock];
}

- (BOOL)isEncryptionNeeded:(RSDBEncryption * __nullable)dbEncryption {
if (dbEncryption == nil)
return NO;
if (dbEncryption.enable && [dbEncryption.key length] > 0)
if (dbEncryption.enable && [self doesEncryptionKeyExists:dbEncryption])
return YES;
return NO;
}

- (BOOL)doesEncryptionKeyExists:(RSDBEncryption * __nullable)dbEncryption {
if (dbEncryption == nil || dbEncryption.key == nil || [dbEncryption.key length] == 0)
return NO;
return YES;
}

- (BOOL) isSQLCipherAvailable {
BOOL isSQLCipherAvailable = NO;
@try {
void *stmt = nil;
if ([database open_v2:[[self getSQLCipherTestDBPath] UTF8String] flags:SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX zVfs:NULL] == SQLITE_OK && [database prepare_v2:"PRAGMA cipher_version;" nBytes:-1 ppStmt:&stmt pzTail:NULL] == SQLITE_OK) {
if ([database step:stmt] == SQLITE_ROW) {
const unsigned char *ver = [database column_text:stmt i:0];
if(ver != NULL) {
isSQLCipherAvailable = YES;
}
}
[database finalize:stmt];
}
[self closeDB];
pallabmaiti marked this conversation as resolved.
Show resolved Hide resolved
}
@catch (NSException *exception) {
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: isSQLCipherAvailable: Failed to check if SQLCipher is available, reason: %@", exception.reason]];
}
[RSUtils removeFile:SQLCIPHER_TEST_DB_NAME];
return isSQLCipherAvailable;
}

- (void)openUnencryptedDB {
int executeCode = [database open_v2:[[self getUnencryptedDBPath] UTF8String] flags:SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX zVfs:NULL];
if (executeCode == SQLITE_OK) {
Expand All @@ -176,6 +150,105 @@ - (int)openEncryptedDB:(NSString *)encryptionKey {
return executeCode;
}

- (void)handleWhenEncryptedDBExists:(RSDBEncryption * _Nullable)dbEncryption isEncryptionNeeded:(BOOL)isEncryptionNeeded {
if (isEncryptionNeeded) {
// open encrypted database with key
int code = [self openEncryptedDB:dbEncryption.key];
if (code == SQLITE_NOTADB) {
// when key is wrong
// delete encrypted database; then open new encrypted database
// all previous events will be deleted
[RSLogger logError:@"RSDBPersistentManager: createDB: Wrong key is provided. Deleting previous encrypted DB and creating a new encrypted DB"];
[self closeDB];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openEncryptedDB:dbEncryption.key];
}
return;
}

// when encryption is not needed and no key is provided
// delete encrypted database; then open unencrypted database
// all previous events will be deleted
if (![self doesEncryptionKeyExists:dbEncryption]) {
[RSLogger logError:@"RSDBPersistentManager: createDB: No key is provided. Deleting encrypted DB and creating a new unencrypted DB"];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openUnencryptedDB];
return;
}

// when encryption is not needed and a key is provided
int code = [self openEncryptedDB:dbEncryption.key];
switch (code) {
// when key is correct
// decyprt database; then open unencrypted database
case SQLITE_OK: {
code = [self decryptDB:dbEncryption.key];
if (code == SQLITE_OK) {
[self deleteEncryptedAndOpenUnEncryptedDB];
} else {
// when decryption of encrypted database failed
// delete encrypted database; then open unencrypted database
// all previous events will be deleted
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: createDB: Failed to decrypt the existing encrypted db, creating a new unencrypted db, error code: %d", code]];
[self deleteEncryptedAndOpenUnEncryptedDB];
}
}
break;

// when key is wrong
// delete encrypted database; then open unencrypted database
// all previous events will be deleted
case SQLITE_NOTADB: {
[RSLogger logError:@"RSDBPersistentManager: createDB: Wrong key is provided. Deleting encrypted DB and creating a new unencrypted DB"];
[self deleteEncryptedAndOpenUnEncryptedDB];
}
break;

default:
// when failed to open encrypted database due to any reason
// delete encrypted database; then open unencrypted database
// all previous events will be deleted
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: createDB: Failed to open the existing encrypted DB, creating a new unencrypted db, error code: %d", code]];
[self deleteEncryptedAndOpenUnEncryptedDB];
break;
}

}

- (void)handleWhenUnencryptedDBExists:(RSDBEncryption * _Nullable)dbEncryption isEncryptionNeeded:(BOOL)isEncryptionNeeded {
if (isEncryptionNeeded) {
// encrypt database; then open encrypted database
[self openUnencryptedDB];
int code = [self encryptDB:dbEncryption.key];
if (code == SQLITE_OK) {
[self deleteUnEncryptedAndOpenEncryptedDB:dbEncryption];
} else {
// when encryption failed
// delete unencrypted database; then create new encrypted database
// all previous events will be deleted
[RSLogger logError:[NSString stringWithFormat:@"RSDBPersistentManager: createDB: Failed to encrypt the existing unecnrypted db, creating a new encrypted db, error code: %d", code]];
[self deleteUnEncryptedAndOpenEncryptedDB:dbEncryption];
}
} else {
// open unencrypted database
[self openUnencryptedDB];
}
}


- (void)deleteEncryptedAndOpenUnEncryptedDB {
[self closeDB];
[RSUtils removeFile:ENCRYPTED_DB_NAME];
[self openUnencryptedDB];
}

- (void)deleteUnEncryptedAndOpenEncryptedDB:(RSDBEncryption * _Nullable)dbEncryption {
[self closeDB];
[RSUtils removeFile:UNENCRYPTED_DB_NAME];
[self openEncryptedDB:dbEncryption.key];
}


- (int)encryptDB:(NSString *)key {
const char* attachDBSQL = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS rl_persistence_encrypted KEY '%@';", [self getEncryptedDBPath], key] UTF8String];

Expand Down Expand Up @@ -218,6 +291,10 @@ - (int)decryptDB:(NSString *)key {
return code;
}

- (NSString *)getSQLCipherTestDBPath {
return [RSUtils getFilePath:SQLCIPHER_TEST_DB_NAME];
}

- (NSString *)getEncryptedDBPath {
return [RSUtils getFilePath:ENCRYPTED_DB_NAME];
}
Expand Down
Loading