Add support for key specs.

This commit is contained in:
Matthew Chen 2018-01-24 16:05:28 -05:00
parent 224c24e685
commit c5079ed3d7
10 changed files with 370 additions and 121 deletions

View File

@ -5,6 +5,7 @@ use_frameworks!
def shared_pods
# OWS Pods
# pod 'SQLCipher', path: '../sqlcipher2'
pod 'SQLCipher', :git => 'https://github.com/sqlcipher/sqlcipher.git', :commit => 'd5c2bec'
pod 'YapDatabase/SQLCipher', path: '../YapDatabase'
# pod 'YapDatabase/SQLCipher', :git => 'https://github.com/WhisperSystems/YapDatabase.git', branch: 'charlesmchen/signal'

View File

@ -217,6 +217,6 @@ SPEC CHECKSUMS:
YapDatabase: 299a32de9d350d37a9ac5b0532609d87d5d2a5de
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
PODFILE CHECKSUM: 5d70451d47917767e5120e09e4b5967959971dd6
PODFILE CHECKSUM: f53f05410c2a8e39055a1513f5d9fd7289f62e0d
COCOAPODS: 1.3.1

View File

@ -3146,11 +3146,7 @@
"DEBUG=1",
"$(inherited)",
);
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
"DEBUG=1",
"$(inherited)",
"SSK_BUILDING_FOR_TESTS=1",
);
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1";
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -52,17 +52,23 @@ NS_ASSUME_NONNULL_BEGIN
return [Randomness generateRandomBytes:(int)kSQLCipherSaltLength];
}
- (NSData *)randomDatabaseKeySpec
{
return [Randomness generateRandomBytes:(int)kSQLCipherKeySpecLength];
}
// * Open a YapDatabase.
// * Do some work with a block.
// * Close the database.
// * Verify that the database is closed.
- (void)openYapDatabase:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
databasePassword:(NSData *_Nullable)databasePassword
databaseSalt:(NSData *_Nullable)databaseSalt
databaseKeySpec:(NSData *_Nullable)databaseKeySpec
databaseBlock:(void (^_Nonnull)(YapDatabase *))databaseBlock
{
OWSAssert(databaseFilePath.length > 0);
OWSAssert(databasePassword.length > 0);
OWSAssert(databasePassword.length > 0 || databaseKeySpec.length > 0);
OWSAssert(databaseBlock);
DDLogVerbose(@"openYapDatabase: %@", databaseFilePath);
@ -75,9 +81,12 @@ NS_ASSUME_NONNULL_BEGIN
@autoreleasepool {
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
options.corruptAction = YapDatabaseCorruptAction_Fail;
options.cipherKeyBlock = ^{
return databasePassword;
};
if (databasePassword) {
DDLogInfo(@"%@ Using password.", self.logTag);
options.cipherKeyBlock = ^{
return databasePassword;
};
}
options.enableMultiProcessSupport = YES;
if (databaseSalt) {
@ -86,6 +95,12 @@ NS_ASSUME_NONNULL_BEGIN
return databaseSalt;
};
options.cipherUnencryptedHeaderLength = kSqliteHeaderLength;
} else if (databaseKeySpec) {
DDLogInfo(@"%@ Using key spec & unencrypted header.", self.logTag);
options.cipherKeySpecBlock = ^{
return databaseKeySpec;
};
options.cipherUnencryptedHeaderLength = kSqliteHeaderLength;
}
OWSAssert(options.cipherDefaultkdfIterNumber == 0);
@ -147,17 +162,20 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssert(!strongDatabase);
}
- (void)createTestDatabase:(NSString *)databaseFilePath databasePassword:(NSData *)databasePassword
- (void)createTestDatabase:(NSString *)databaseFilePath
databasePassword:(NSData *_Nullable)databasePassword
databaseSalt:(NSData *_Nullable)databaseSalt
databaseKeySpec:(NSData *_Nullable)databaseKeySpec
{
OWSAssert(databaseFilePath.length > 0);
OWSAssert(databasePassword.length > 0);
OWSAssert(databasePassword.length > 0 || databaseKeySpec.length > 0);
OWSAssert(![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
[self openYapDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:databaseKeySpec
databaseBlock:^(YapDatabase *database) {
[self logHeaderOfDatabaseFile:databaseFilePath
label:@"mid-creation"];
@ -177,11 +195,12 @@ NS_ASSUME_NONNULL_BEGIN
}
- (BOOL)verifyTestDatabase:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
databasePassword:(NSData *_Nullable)databasePassword
databaseSalt:(NSData *_Nullable)databaseSalt
databaseKeySpec:(NSData *_Nullable)databaseKeySpec
{
OWSAssert(databaseFilePath.length > 0);
OWSAssert(databasePassword.length > 0);
OWSAssert(databasePassword.length > 0 || databaseKeySpec.length > 0);
OWSAssert([[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
@ -189,6 +208,7 @@ NS_ASSUME_NONNULL_BEGIN
[self openYapDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:databaseKeySpec
databaseBlock:^(YapDatabase *database) {
YapDatabaseConnection *dbConnection = database.newConnection;
id _Nullable value = [dbConnection objectForKey:@"test_key_name" inCollection:@"test_collection_name"];
@ -202,8 +222,7 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSString *)createUnconvertedDatabase:(NSData *)databasePassword
{
return [self createDatabase:databasePassword
databaseSalt:nil];
return [self createDatabase:databasePassword databaseSalt:nil databaseKeySpec:nil];
}
- (NSString *)createTempDatabaseFilePath
@ -218,19 +237,28 @@ NS_ASSUME_NONNULL_BEGIN
return databaseFilePath;
}
// If databaseSalt is nil, creates a non-converted database.
// If databaseSalt and databaseKeySpec are both nil, creates a non-converted database.
// Otherwise creates a pre-converted database.
- (nullable NSString *)createDatabase:(NSData *)databasePassword
- (nullable NSString *)createDatabase:(NSData *_Nullable)databasePassword
databaseSalt:(NSData *_Nullable)databaseSalt
databaseKeySpec:(NSData *_Nullable)databaseKeySpec
{
OWSAssert(databasePassword.length > 0 || databaseKeySpec.length > 0);
NSString *databaseFilePath = [self createTempDatabaseFilePath];
[self createTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
[self createTestDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:databaseKeySpec];
[self logHeaderOfDatabaseFile:databaseFilePath
label:@"created"];
BOOL isValid = [self verifyTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:databaseKeySpec];
OWSAssert(isValid);
return databaseFilePath;
@ -245,17 +273,70 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
}
- (void)testDoesDatabaseNeedToBeConverted_Converted
- (void)testDoesDatabaseNeedToBeConverted_ConvertedWithoutKeyspec
{
NSData *databasePassword = [self randomDatabasePassword];
NSData *databaseSalt = [self randomDatabaseSalt];
NSString *_Nullable databaseFilePath = [self createDatabase:databasePassword
databaseSalt:databaseSalt];
NSData *_Nullable databaseKeySpec = nil;
NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
}
- (void)testDoesDatabaseNeedToBeConverted_ConvertedWithKeyspec
{
NSData *_Nullable databasePassword = nil;
NSData *_Nullable databaseSalt = nil;
NSData *databaseKeySpec = [self randomDatabaseKeySpec];
NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
}
// Verifies that legacy users with non-converted databases can convert.
- (void)testDatabaseConversion
- (void)testDatabaseConversion_WithoutKeyspec
{
NSData *databasePassword = [self randomDatabasePassword];
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
__block NSData *_Nullable databaseSalt = nil;
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
OWSAssert(!databaseSalt);
OWSAssert(saltData);
databaseSalt = saltData;
};
__block NSData *_Nullable databaseKeySpec = nil;
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(!databaseKeySpec);
OWSAssert(keySpecData);
databaseKeySpec = keySpecData;
};
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
}
XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
XCTAssertNotNil(databaseSalt);
XCTAssertEqual(databaseSalt.length, kSQLCipherSaltLength);
XCTAssertNotNil(databaseKeySpec);
XCTAssertEqual(databaseKeySpec.length, kSQLCipherKeySpecLength);
BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:nil];
XCTAssertTrue(isValid);
}
// Verifies that legacy users with non-converted databases can convert.
- (void)testDatabaseConversion_WithKeyspec
{
NSData *databasePassword = [self randomDatabasePassword];
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
@ -268,27 +349,80 @@ NS_ASSUME_NONNULL_BEGIN
databaseSalt = saltData;
};
__block NSData *_Nullable databaseKeySpec = nil;
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(!databaseKeySpec);
OWSAssert(keySpecData);
databaseKeySpec = keySpecData;
};
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock];
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
}
XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
BOOL isValid =
[self verifyTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
XCTAssertNotNil(databaseSalt);
XCTAssertEqual(databaseSalt.length, kSQLCipherSaltLength);
XCTAssertNotNil(databaseKeySpec);
XCTAssertEqual(databaseKeySpec.length, kSQLCipherKeySpecLength);
BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:nil
databaseSalt:nil
databaseKeySpec:databaseKeySpec];
XCTAssertTrue(isValid);
}
// Verifies new users who create new pre-converted databases.
- (void)testDatabaseCreation
- (void)testDatabaseCreation_WithoutKeySpec
{
NSData *databasePassword = [self randomDatabasePassword];
NSData *databaseSalt = [self randomDatabaseSalt];
NSString *_Nullable databaseFilePath = [self createDatabase:databasePassword
databaseSalt:databaseSalt];
NSData *_Nullable databaseKeySpec = nil;
NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
OWSAssert(saltData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
};
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(keySpecData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
};
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
}
XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:databaseKeySpec];
XCTAssertTrue(isValid);
}
// Verifies new users who create new pre-converted databases.
- (void)testDatabaseCreation_WithKeySpec
{
NSData *_Nullable databasePassword = nil;
NSData *_Nullable databaseSalt = nil;
NSData *databaseKeySpec = [self randomDatabaseKeySpec];
NSString *_Nullable databaseFilePath =
[self createDatabase:databasePassword databaseSalt:databaseSalt databaseKeySpec:databaseKeySpec];
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
@ -296,17 +430,26 @@ NS_ASSUME_NONNULL_BEGIN
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
};
OWSDatabaseSaltBlock keySpecBlock = ^(NSData *keySpecData) {
OWSAssert(keySpecData);
XCTFail(@"%s No conversion should be necessary", __PRETTY_FUNCTION__);
};
NSError *_Nullable error = [OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock];
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
if (error) {
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
}
XCTAssertNil(error);
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
BOOL isValid =
[self verifyTestDatabase:databaseFilePath databasePassword:databasePassword databaseSalt:databaseSalt];
BOOL isValid = [self verifyTestDatabase:databaseFilePath
databasePassword:databasePassword
databaseSalt:databaseSalt
databaseKeySpec:databaseKeySpec];
XCTAssertTrue(isValid);
}

View File

@ -6,8 +6,11 @@ NS_ASSUME_NONNULL_BEGIN
extern const NSUInteger kSqliteHeaderLength;
extern const NSUInteger kSQLCipherSaltLength;
extern const NSUInteger kSQLCipherDerivedKeyLength;
extern const NSUInteger kSQLCipherKeySpecLength;
typedef void (^OWSDatabaseSaltBlock)(NSData *saltData);
typedef void (^OWSDatabaseKeySpecBlock)(NSData *keySpecData);
// Used to convert YapDatabase/SQLCipher databases whose header is encrypted
// to databases whose first 32 bytes are unencrypted so that iOS can determine
@ -20,7 +23,11 @@ typedef void (^OWSDatabaseSaltBlock)(NSData *saltData);
+ (nullable NSError *)convertDatabaseIfNecessary;
+ (nullable NSError *)convertDatabaseIfNecessary:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
saltBlock:(OWSDatabaseSaltBlock)saltBlock;
saltBlock:(OWSDatabaseSaltBlock)saltBlock
keySpecBlock:(OWSDatabaseKeySpecBlock)keySpecBlock;
+ (nullable NSData *)deriveDatabaseKeyForPassword:(NSData *)passwordData saltData:(NSData *)saltData;
+ (nullable NSData *)databaseKeySpecForPassword:(NSData *)passwordData saltData:(NSData *)saltData;
@end

View File

@ -4,6 +4,7 @@
#import "OWSDatabaseConverter.h"
#import "sqlite3.h"
#import <CommonCrypto/CommonCrypto.h>
#import <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/OWSFileSystem.h>
@ -14,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
const NSUInteger kSqliteHeaderLength = 32;
const NSUInteger kSQLCipherSaltLength = 16;
const NSUInteger kSQLCipherDerivedKeyLength = 32;
const NSUInteger kSQLCipherKeySpecLength = 48;
@interface OWSStorage (OWSDatabaseConverter)
@ -91,8 +94,14 @@ const NSUInteger kSQLCipherSaltLength = 16;
OWSDatabaseSaltBlock saltBlock = ^(NSData *saltData) {
[OWSStorage storeDatabaseSalt:saltData];
};
OWSDatabaseKeySpecBlock keySpecBlock = ^(NSData *keySpecData) {
[OWSStorage storeDatabaseKeySpec:keySpecData];
};
return [self convertDatabaseIfNecessary:databaseFilePath databasePassword:databasePassword saltBlock:saltBlock];
return [self convertDatabaseIfNecessary:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
}
// TODO upon failure show user error UI
@ -100,38 +109,61 @@ const NSUInteger kSQLCipherSaltLength = 16;
+ (nullable NSError *)convertDatabaseIfNecessary:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
saltBlock:(OWSDatabaseSaltBlock)saltBlock
keySpecBlock:(OWSDatabaseKeySpecBlock)keySpecBlock
{
if (![self doesDatabaseNeedToBeConverted:databaseFilePath]) {
return nil;
}
return [self convertDatabase:(NSString *)databaseFilePath databasePassword:databasePassword saltBlock:saltBlock];
return [self convertDatabase:databaseFilePath
databasePassword:databasePassword
saltBlock:saltBlock
keySpecBlock:keySpecBlock];
}
+ (nullable NSError *)convertDatabase:(NSString *)databaseFilePath
databasePassword:(NSData *)databasePassword
saltBlock:(OWSDatabaseSaltBlock)saltBlock
keySpecBlock:(OWSDatabaseKeySpecBlock)keySpecBlock
{
OWSAssert(databaseFilePath.length > 0);
OWSAssert(databasePassword.length > 0);
OWSAssert(saltBlock);
OWSAssert(keySpecBlock);
DDLogVerbose(@"%@ databasePassword: %@", self.logTag, databasePassword.hexadecimalString);
NSData *sqlCipherSaltData;
NSData *saltData;
{
NSData *headerData = [self readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
OWSAssert(headerData);
OWSAssert(headerData.length >= kSQLCipherSaltLength);
sqlCipherSaltData = [headerData subdataWithRange:NSMakeRange(0, kSQLCipherSaltLength)];
saltData = [headerData subdataWithRange:NSMakeRange(0, kSQLCipherSaltLength)];
DDLogVerbose(@"%@ sqlCipherSaltData: %@", self.logTag, sqlCipherSaltData.hexadecimalString);
DDLogVerbose(@"%@ saltData: %@", self.logTag, saltData.hexadecimalString);
// Make sure we successfully persist the salt (persumably in the keychain) before
// proceeding with the database conversion or we could leave the app in an
// unrecoverable state.
saltBlock(sqlCipherSaltData);
saltBlock(saltData);
}
{
NSData *_Nullable keySpecData = [self databaseKeySpecForPassword:databasePassword saltData:saltData];
if (!keySpecData || keySpecData.length != kSQLCipherKeySpecLength) {
DDLogError(@"Error deriving key spec");
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Invalid key spec");
}
DDLogVerbose(@"%@ keySpecData: %@", self.logTag, keySpecData.hexadecimalString);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
// Make sure we successfully persist the key spec (persumably in the keychain) before
// proceeding with the database conversion or we could leave the app in an
// unrecoverable state.
keySpecBlock(keySpecData);
}
// -----------------------------------------------------------
@ -237,72 +269,7 @@ const NSUInteger kSQLCipherSaltLength = 16;
DDLogVerbose(@"%@ saltString: %@", self.logTag, saltString);
OWSAssert([sqlCipherSaltData.hexadecimalString isEqualToString:saltString]);
}
// We can obtain the database salt in two ways: by reading the first 16 bytes of the encrypted
// header OR by using "PRAGMA cipher_salt". In DEBUG builds, we verify that these two values
// match.
{
//
// In practice, for a real application, the other changes we talked about on the phone need occur, i.e.
// to provide the salt to the application explicitly. The application can use a raw key spec, where the
// 96 hex are provide (i.e. 64 hex for the 256 bit key, followed by 32 hex for the 128 bit salt) using
// explicit BLOB syntax, e.g.
//
// x'98483C6EB40B6C31A448C22A66DED3B5E5E8D5119CAC8327B655C8B5C483648101010101010101010101010101010101'
extern void sqlite3CodecGetKey(sqlite3 * db, int nDb, void **zKey, int *nKey);
char *keySpecBytes = NULL;
int keySpecLength = 0;
sqlite3CodecGetKey(db, 0, (void **)&keySpecBytes, &keySpecLength);
if (!keySpecBytes || keySpecLength < 1) {
DDLogError(@"Error extracting key spec.");
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Failed to extract key spec");
}
NSData *_Nullable keySpecData = [NSData dataWithBytes:keySpecBytes length:keySpecLength];
if (!keySpecData) {
DDLogError(@"Invalid key spec.");
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Invalid key spec");
}
DDLogInfo(@"keySpecData: %@", keySpecData.hexadecimalString);
// SQLITE_PRIVATE void sqlite3CodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) {
// struct Db *pDb = &db->aDb[nDb];
// CODEC_TRACE("sqlite3CodecGetKey: entered db=%p, nDb=%d\n", db, nDb);
// if( pDb->pBt ) {
// codec_ctx *ctx;
// sqlite3pager_get_codec(pDb->pBt->pBt->pPager, (void **) &ctx);
// if(ctx) {
// if(sqlcipher_codec_get_store_pass(ctx) == 1) {
// sqlcipher_codec_get_pass(ctx, zKey, nKey);
// } else {
// sqlcipher_codec_get_keyspec(ctx, zKey, nKey);
// }
// } else {
// *zKey = NULL;
// *nKey = 0;
// }
// }
// }
//
// codec_ctx *ctx;
//
// char *keySpecBytes = NULL;
// int keySpecLength = 0;
//// extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*);
// sqlcipher_codec_get_keyspec(ctx, (void**)&keySpecBytes, &keySpecLength);
// NSString *saltString =
// [[NSString alloc] initWithBytes:valueBytes length:(NSUInteger) valueLength
// encoding:NSUTF8StringEncoding];
//
// sqlite3_finalize(statement);
// statement = NULL;
//
// DDLogVerbose(@"%@ saltString: %@", self.logTag, saltString);
//
// OWSAssert([sqlCipherSaltData.hexadecimalString isEqualToString:saltString]);
OWSAssert([saltData.hexadecimalString isEqualToString:saltString]);
}
#endif
@ -398,6 +365,63 @@ const NSUInteger kSQLCipherSaltLength = 16;
return result;
}
+ (nullable NSData *)deriveDatabaseKeyForPassword:(NSData *)passwordData saltData:(NSData *)saltData
{
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
unsigned char *derivedKeyBytes = malloc((size_t)kSQLCipherDerivedKeyLength);
OWSAssert(derivedKeyBytes);
// See: PBKDF2_ITER.
const unsigned int workfactor = 64000;
int result = CCKeyDerivationPBKDF(kCCPBKDF2,
passwordData.bytes,
(size_t)passwordData.length,
saltData.bytes,
(size_t)saltData.length,
kCCPRFHmacAlgSHA1,
workfactor,
derivedKeyBytes,
kSQLCipherDerivedKeyLength);
if (result != kCCSuccess) {
DDLogError(@"Error deriving key: %d", result);
return nil;
}
NSData *_Nullable derivedKeyData = [NSData dataWithBytes:derivedKeyBytes length:kSQLCipherDerivedKeyLength];
if (!derivedKeyData || derivedKeyData.length != kSQLCipherDerivedKeyLength) {
DDLogError(@"Invalid derived key: %d", result);
return nil;
}
DDLogVerbose(@"%@ derivedKeyData: %@", self.logTag, derivedKeyData.hexadecimalString);
return derivedKeyData;
}
+ (nullable NSData *)databaseKeySpecForPassword:(NSData *)passwordData saltData:(NSData *)saltData
{
OWSAssert(passwordData.length > 0);
OWSAssert(saltData.length == kSQLCipherSaltLength);
NSData *_Nullable derivedKeyData = [self deriveDatabaseKeyForPassword:passwordData saltData:saltData];
if (!derivedKeyData || derivedKeyData.length != kSQLCipherDerivedKeyLength) {
DDLogError(@"Error deriving key");
return nil;
}
DDLogVerbose(@"%@ derivedKeyData: %@", self.logTag, derivedKeyData.hexadecimalString);
NSMutableData *keySpecData = [NSMutableData new];
[keySpecData appendData:derivedKeyData];
[keySpecData appendData:saltData];
DDLogVerbose(@"%@ keySpecData: %@", self.logTag, keySpecData.hexadecimalString);
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
return keySpecData;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,11 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "sqlite3.h"
NS_ASSUME_NONNULL_BEGIN
NSData *_Nullable ExtractDatabaseKeySpec(sqlite3 *db);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,29 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "OWSDatabaseUtils.h"
NS_ASSUME_NONNULL_BEGIN
extern "C" {
extern void sqlite3CodecGetKey(sqlite3 *db, int nDb, void **zKey, int *nKey);
}
NSData *_Nullable ExtractDatabaseKeySpec(sqlite3 *db)
{
char *keySpecBytes = NULL;
int keySpecLength = 0;
sqlite3CodecGetKey(db, 0, (void **)&keySpecBytes, &keySpecLength);
if (!keySpecBytes || keySpecLength < 1) {
return nil;
}
NSData *_Nullable keySpecData = [NSData dataWithBytes:keySpecBytes length:(NSUInteger)keySpecLength];
if (!keySpecData) {
return nil;
}
return keySpecData;
}
NS_ASSUME_NONNULL_END

View File

@ -60,6 +60,9 @@ extern NSString *const StorageIsReadyNotification;
+ (nullable NSData *)tryToLoadDatabaseSalt:(NSError **)errorHandle;
+ (void)storeDatabaseSalt:(NSData *)saltData;
+ (nullable NSData *)tryToLoadDatabaseKeySpec:(NSError **)errorHandle;
+ (void)storeDatabaseKeySpec:(NSData *)keySpecData;
@end
NS_ASSUME_NONNULL_END

View File

@ -28,10 +28,13 @@ NSString *const OWSResetStorageNotification = @"OWSResetStorageNotification";
static NSString *keychainService = @"TSKeyChainService";
static NSString *keychainDBPassAccount = @"TSDatabasePass";
static NSString *keychainDBSalt = @"OWSDatabaseSalt";
static NSString *keychainDBKeySpec = @"OWSDatabaseKeySpec";
// TODO: Move this to YapDatabase.
// TODO: Move these constants to YapDatabase.
const NSUInteger kSqliteHeaderLength = 32;
const NSUInteger kSQLCipherSaltLength = 16;
const NSUInteger kSQLCipherKeySpecLength = 48;
const NSUInteger kDatabasePasswordLength = 30;
typedef NSData *_Nullable (^LoadDatabaseMetadataBlock)(NSError **_Nullable);
@ -382,23 +385,17 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
- (BOOL)tryToLoadDatabase
{
// We determine the database password first, since a side effect of
// We determine the database password / key spec first, since a side effect of
// this can be deleting any existing database file (if we're recovering
// from a corrupt keychain).
NSData *databasePassword = [self databasePassword];
OWSAssert(databasePassword.length > 0);
NSData *databaseSalt = [self databaseSalt];
OWSAssert(databaseSalt.length > 0);
NSData *databaseKeySpec = [self databaseKeySpec];
OWSAssert(databaseKeySpec.length == kSQLCipherKeySpecLength);
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
options.corruptAction = YapDatabaseCorruptAction_Fail;
options.cipherKeyBlock = ^{
return databasePassword;
};
options.enableMultiProcessSupport = YES;
options.cipherSaltBlock = ^{
return databaseSalt;
options.cipherKeySpecBlock = ^{
return databaseKeySpec;
};
options.cipherUnencryptedHeaderLength = kSqliteHeaderLength;
@ -555,6 +552,11 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
return [self tryToLoadKeyChainValue:keychainDBSalt errorHandle:errorHandle];
}
+ (nullable NSData *)tryToLoadDatabaseKeySpec:(NSError **)errorHandle
{
return [self tryToLoadKeyChainValue:keychainDBKeySpec errorHandle:errorHandle];
}
- (NSData *)databasePassword
{
return [self loadMetadataOrClearDatabase:^(NSError **_Nullable errorHandle) {
@ -577,6 +579,17 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
label:@"Database salt"];
}
- (NSData *)databaseKeySpec
{
return [self loadMetadataOrClearDatabase:^(NSError **_Nullable errorHandle) {
return [OWSStorage tryToLoadDatabaseKeySpec:errorHandle];
}
createDataBlock:^{
return [self createAndSetNewDatabaseKeySpec];
}
label:@"Database key spec"];
}
- (NSData *)loadMetadataOrClearDatabase:(LoadDatabaseMetadataBlock)loadDataBlock
createDataBlock:(CreateDatabaseMetadataBlock)createDataBlock
label:(NSString *)label
@ -653,6 +666,21 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
return saltData;
}
- (NSData *)createAndSetNewDatabaseKeySpec
{
NSData *databasePassword = [self databasePassword];
OWSAssert(databasePassword.length > 0);
NSData *databaseSalt = [self databaseSalt];
OWSAssert(databaseSalt.length == kSQLCipherSaltLength);
NSData *keySpecData = [OWSDatabaseConverter databaseKeySpecForPassword:databasePassword saltData:databaseSalt];
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
[OWSStorage storeDatabaseKeySpec:keySpecData];
return keySpecData;
}
- (void)backgroundedAppDatabasePasswordInaccessibleWithErrorDescription:(NSString *)errorDescription
{
OWSAssert(CurrentAppContext().isMainApp && CurrentAppContext().isInBackground);
@ -722,6 +750,13 @@ typedef NSData *_Nullable (^CreateDatabaseMetadataBlock)(void);
[self storeKeyChainValue:saltData keychainKey:keychainDBSalt];
}
+ (void)storeDatabaseKeySpec:(NSData *)keySpecData
{
OWSAssert(keySpecData.length == kSQLCipherKeySpecLength);
[self storeKeyChainValue:keySpecData keychainKey:keychainDBKeySpec];
}
@end
NS_ASSUME_NONNULL_END