Add support for key specs.

This commit is contained in:
Matthew Chen 2018-01-24 16:05:18 -05:00
parent 302a1c86e4
commit 40ab4d2fe2
3 changed files with 99 additions and 38 deletions

View File

@ -820,15 +820,26 @@ static int connectionBusyHandler(void *ptr, int count) {
**/
- (BOOL)configureEncryptionForDatabase:(sqlite3 *)sqlite
{
if (options.cipherKeyBlock)
if (options.cipherKeyBlock ||
options.cipherKeySpecBlock)
{
NSData *keyData = options.cipherKeyBlock();
if (keyData == nil)
{
NSAssert(NO, @"YapDatabaseOptions.cipherKeyBlock cannot return nil!");
return NO;
}
NSData *_Nullable keyData = nil;
if (options.cipherKeySpecBlock)
{
keyData = options.cipherKeySpecBlock();
if (!keyData)
{
NSAssert(NO, @"YapDatabaseOptions.cipherKeySpecBlock cannot return nil!");
return NO;
}
} else {
keyData = options.cipherKeyBlock();
if (!keyData)
{
NSAssert(NO, @"YapDatabaseOptions.cipherKeyBlock cannot return nil!");
return NO;
}
}
//Setting the PBKDF2 default iteration number (this will have effect next time database is opened)
if (options.cipherDefaultkdfIterNumber > 0) {
@ -863,38 +874,56 @@ static int connectionBusyHandler(void *ptr, int count) {
}
}
int status = sqlite3_key(sqlite, [keyData bytes], (int)[keyData length]);
if (status != SQLITE_OK)
{
YDBLogError(@"Error setting SQLCipher key: %d %s", status, sqlite3_errmsg(sqlite));
return NO;
}
if (!options.cipherSaltBlock &&
options.cipherUnencryptedHeaderLength == 0) {
// Custom salt + unencrypted header not activated.
} else if (options.cipherSaltBlock &&
options.cipherUnencryptedHeaderLength > 0) {
YDBLogInfo(@"YapDatabase using cipher salt and unencrypted header.");
NSData *_Nullable saltData = options.cipherSaltBlock();
if (saltData == nil)
if (options.cipherKeySpecBlock) {
// Use a raw key spec, where the 96 hexadecimal digits are provided
// (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'
NSString *keySpecString = [NSString stringWithFormat:@"x'%@'", [self hexadecimalStringForData:keyData]];
NSData *keySpecStringData = [keySpecString dataUsingEncoding:NSUTF8StringEncoding];
int status = sqlite3_key(sqlite, [keySpecStringData bytes], (int)[keySpecStringData length]);
if (status != SQLITE_OK)
{
NSAssert(NO, @"YapDatabaseOptions.cipherSaltBlock cannot return nil!");
YDBLogError(@"Error setting SQLCipher key: %d %s", status, sqlite3_errmsg(sqlite));
return NO;
}
} else {
int status = sqlite3_key(sqlite, [keyData bytes], (int)[keyData length]);
if (status != SQLITE_OK)
{
char *errorMsg;
// Example: PRAGMA cipher_salt = "x'01010101010101010101010101010101';";
NSString *pragmaSql = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", [self hexadecimalStringForData:saltData]];
if (sqlite3_exec(sqlite, [pragmaSql UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK)
YDBLogError(@"Error setting SQLCipher key: %d %s", status, sqlite3_errmsg(sqlite));
return NO;
}
}
if (options.cipherUnencryptedHeaderLength > 0 &&
(options.cipherKeySpecBlock ||
options.cipherSaltBlock)) {
if (options.cipherKeySpecBlock) {
YDBLogInfo(@"YapDatabase using cipher key spec and unencrypted header.");
} else {
YDBLogInfo(@"YapDatabase using cipher salt and unencrypted header.");
NSData *_Nullable saltData = options.cipherSaltBlock();
if (saltData == nil)
{
YDBLogError(@"failed to set database cipher_default_kdf_iter: %s", errorMsg);
NSAssert(NO, @"YapDatabaseOptions.cipherSaltBlock cannot return nil!");
return NO;
}
{
char *errorMsg;
// Example: PRAGMA cipher_salt = "x'01010101010101010101010101010101';";
NSString *pragmaSql = [NSString stringWithFormat:@"PRAGMA cipher_salt = \"x'%@'\";", [self hexadecimalStringForData:saltData]];
if (sqlite3_exec(sqlite, [pragmaSql UTF8String], NULL, NULL, &errorMsg) != SQLITE_OK)
{
YDBLogError(@"failed to set database cipher_default_kdf_iter: %s", errorMsg);
return NO;
}
}
}
{
@ -912,8 +941,18 @@ static int connectionBusyHandler(void *ptr, int count) {
}
}
} else {
NSAssert(NO, @"Either both YapDatabaseOptions.cipherSaltBlock and YapDatabaseOptions.cipherUnencryptedHeaderLength should be set or neither should be set!");
return NO;
if (options.cipherUnencryptedHeaderLength > 0) {
NSAssert(NO, @"YapDatabaseOptions.cipherUnencryptedHeaderLength should not be used without cipherKeySpecBlock or cipherSaltBlock!");
return NO;
}
if (options.cipherKeySpecBlock) {
NSAssert(NO, @"YapDatabaseOptions.cipherKeySpecBlock should not be used without setting cipherUnencryptedHeaderLength!");
return NO;
}
if (options.cipherSaltBlock) {
NSAssert(NO, @"YapDatabaseOptions.cipherSaltBlock should not be used without setting cipherUnencryptedHeaderLength!");
return NO;
}
}
}

View File

@ -242,11 +242,29 @@ typedef NSData *_Nonnull (^YapDatabaseCipherKeyBlock)(void);
**/
@property (nonatomic, copy, readwrite) YapDatabaseCipherKeyBlock cipherSaltBlock;
/**
* Set a block here that returns the key spec (not the key) for the SQLCipher database.
*
* This key spec incorporates the "derived key" and the "salt".
*
* This block allows you to fetch the key spec from the keychain (or elsewhere)
* only when you need it, instead of persisting it in memory.
*
* You must use the 'YapDatabase/SQLCipher' subspec
* in your Podfile for this option to take effect.
*
* cipherKeySpecBlock will be ignored if you don't also set cipherUnencryptedHeaderLength.
*
* See comments on cipherUnencryptedHeaderLength. These two properties are
* intended to be used in conjunction.
**/
@property (nonatomic, copy, readwrite) YapDatabaseCipherKeyBlock cipherKeySpecBlock;
/**
* If set, this many bytes at the start of the first page of the database will _NOT_
* be encrypted.
*
* This salt that will be passed to SQLCipher via `PRAGMA cipher_plaintext_header_size`.
* This value will be passed to SQLCipher via `PRAGMA cipher_plaintext_header_size`.
*
* iOS will terminate suspended apps which hold a file lock on files in the shared
* container. An exception is made for certain kinds of Sqlite files, so that iOS apps
@ -273,9 +291,11 @@ typedef NSData *_Nonnull (^YapDatabaseCipherKeyBlock)(void);
* You must use the 'YapDatabase/SQLCipher' subspec
* in your Podfile for this option to take effect.
*
* cipherUnencryptedHeaderLength will be ignored if you don't also set cipherKeyBlock and cipherSaltBlock.
* cipherUnencryptedHeaderLength will be ignored if you don't also set
* ((cipherKeyBlock AND cipherSaltBlock) OR cipherKeySpecBlock).
*
* See comments on cipherSaltBlock. These two properties are intended to be used in conjunction.
* See comments on cipherSaltBlock and cipherKeySpecBlock. These properties are
* intended to be used in conjunction.
**/
@property (nonatomic, assign, readwrite) NSUInteger cipherUnencryptedHeaderLength;

View File

@ -25,6 +25,7 @@
@synthesize cipherDefaultkdfIterNumber = cipherDefaultkdfIterNumber;
@synthesize cipherPageSize = cipherPageSize;
@synthesize cipherSaltBlock = cipherSaltBlock;
@synthesize cipherKeySpecBlock = cipherKeySpecBlock;
@synthesize cipherUnencryptedHeaderLength = cipherUnencryptedHeaderLength;
#endif
@ -60,6 +61,7 @@
copy->cipherDefaultkdfIterNumber = cipherDefaultkdfIterNumber;
copy->cipherPageSize = cipherPageSize;
copy->cipherSaltBlock = cipherSaltBlock;
copy->cipherKeySpecBlock = cipherKeySpecBlock;
copy->cipherUnencryptedHeaderLength = cipherUnencryptedHeaderLength;
#endif
copy->aggressiveWALTruncationSize = aggressiveWALTruncationSize;