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

Support for SQLite Encryption Extension (SEE) #509

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions GRDB.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@
56176C6E1EACCCC9000F3F2B /* FTS5TableBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B964C21DA521450002DA19 /* FTS5TableBuilderTests.swift */; };
56176C6F1EACCCC9000F3F2B /* FTS5TokenizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5698ACCA1DA62A2D0056AF8C /* FTS5TokenizerTests.swift */; };
56176C701EACCCC9000F3F2B /* FTS5WrapperTokenizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56ED8A7E1DAB8D6800BD0ABC /* FTS5WrapperTokenizerTests.swift */; };
56176C7D1EACCD2D000F3F2B /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; };
56176C7F1EACCD2F000F3F2B /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; };
56193E8E1CD8A3E200F95862 /* FetchedRecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A7787C1C6A4DD600F507F6 /* FetchedRecordsController.swift */; };
562205F11E420E47005860AC /* DatabasePoolReleaseMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363CF1C943D13000BE133 /* DatabasePoolReleaseMemoryTests.swift */; };
562205F21E420E47005860AC /* DatabasePoolSchemaCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531281C908A5B00CF1A2B /* DatabasePoolSchemaCacheTests.swift */; };
Expand Down Expand Up @@ -797,6 +795,8 @@
56FF455A1D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift */; };
6340BF801E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; };
6340BF841E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; };
B501B3482254A5440071DCDC /* GRDBCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501B3472254A5440071DCDC /* GRDBCipherTests.swift */; };
B501B3492254A5440071DCDC /* GRDBCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501B3472254A5440071DCDC /* GRDBCipherTests.swift */; };
C96C0F2B2084A442006B2981 /* SQLiteDateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96C0F242084A442006B2981 /* SQLiteDateParser.swift */; };
C96C0F2C2084A459006B2981 /* SQLiteDateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96C0F242084A442006B2981 /* SQLiteDateParser.swift */; };
C96C0F2D2084A45A006B2981 /* SQLiteDateParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96C0F242084A442006B2981 /* SQLiteDateParser.swift */; };
Expand Down Expand Up @@ -1072,7 +1072,6 @@
56703290212B544F007D270F /* DatabaseUUIDEncodingStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUUIDEncodingStrategyTests.swift; sourceTree = "<group>"; };
567071FA208A509C006AD95A /* DateParsingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateParsingTests.swift; sourceTree = "<group>"; };
567156151CB142AA007DC145 /* DatabaseQueueReadOnlyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueReadOnlyTests.swift; sourceTree = "<group>"; };
567156701CB18050007DC145 /* EncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = "<group>"; };
5671FC1F1DA3CAC9003BF4FF /* FTS3TokenizerDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS3TokenizerDescriptor.swift; sourceTree = "<group>"; };
5672DE581CDB72520022BA81 /* DatabaseQueueBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueBackupTests.swift; sourceTree = "<group>"; };
5672DE661CDB751D0022BA81 /* DatabasePoolBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolBackupTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1268,6 +1267,7 @@
56FF453F1D2C23BA00F21EF9 /* MutablePersistableRecordDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutablePersistableRecordDeleteTests.swift; sourceTree = "<group>"; };
56FF45551D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordUniqueIndexTests.swift; sourceTree = "<group>"; };
6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordPersistenceConflictPolicy.swift; sourceTree = "<group>"; };
B501B3472254A5440071DCDC /* GRDBCipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRDBCipherTests.swift; sourceTree = "<group>"; };
C96C0F242084A442006B2981 /* SQLiteDateParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteDateParser.swift; sourceTree = "<group>"; };
DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GRDB-Bridging.h"; sourceTree = "<group>"; };
DC3773F319C8CBB3004FCF85 /* GRDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRDB.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -1507,7 +1507,7 @@
56176CA01EACEE2A000F3F2B /* GRDBCipher */ = {
isa = PBXGroup;
children = (
567156701CB18050007DC145 /* EncryptionTests.swift */,
B501B3472254A5440071DCDC /* GRDBCipherTests.swift */,
);
name = GRDBCipher;
sourceTree = "<group>";
Expand Down Expand Up @@ -2318,6 +2318,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
Expand Down Expand Up @@ -2843,6 +2844,7 @@
5657AB421D108BA9006283EF /* FoundationNSDataTests.swift in Sources */,
56CC922D201DFFB900CB597E /* DropWhileCursorTests.swift in Sources */,
56D507621F6BAE8600AE1C5B /* PrimaryKeyInfoTests.swift in Sources */,
B501B3492254A5440071DCDC /* GRDBCipherTests.swift in Sources */,
5653EADD20944B4F00F46237 /* AssociationBelongsToSQLTests.swift in Sources */,
56176C6B1EACCCC9000F3F2B /* FTS5CustomTokenizerTests.swift in Sources */,
56300B621C53C42C005A543B /* FetchableRecord+QueryInterfaceRequestTests.swift in Sources */,
Expand All @@ -2859,7 +2861,6 @@
56EB0AB31BCD787300A3DC55 /* DataMemoryTests.swift in Sources */,
56B6EF57208CB4E3002F0ACB /* ColumnExpressionTests.swift in Sources */,
563B071621862C4700B38F35 /* ValueObservationRecordTests.swift in Sources */,
56176C7F1EACCD2F000F3F2B /* EncryptionTests.swift in Sources */,
56741EAC1E66A8B3003E422D /* FetchRequestTests.swift in Sources */,
5672DE5C1CDB72520022BA81 /* DatabaseQueueBackupTests.swift in Sources */,
56A2385E1B9C74A90082EB20 /* RecordCopyTests.swift in Sources */,
Expand Down Expand Up @@ -3032,6 +3033,7 @@
56CC9251201E093F00CB597E /* PrefixCursorTests.swift in Sources */,
56D4965E1D81304E008276D7 /* FoundationNSStringTests.swift in Sources */,
5698AC891DA389380056AF8C /* FTS3TableBuilderTests.swift in Sources */,
B501B3482254A5440071DCDC /* GRDBCipherTests.swift in Sources */,
56CC922C201DFFB900CB597E /* DropWhileCursorTests.swift in Sources */,
5653EADC20944B4F00F46237 /* AssociationBelongsToSQLTests.swift in Sources */,
56D5075E1F6BAE8600AE1C5B /* PrimaryKeyInfoTests.swift in Sources */,
Expand All @@ -3050,7 +3052,6 @@
563B071521862C4700B38F35 /* ValueObservationRecordTests.swift in Sources */,
56B6EF56208CB4E3002F0ACB /* ColumnExpressionTests.swift in Sources */,
5698ACD71DA925420056AF8C /* RowTestCase.swift in Sources */,
56176C7D1EACCD2D000F3F2B /* EncryptionTests.swift in Sources */,
56741EA81E66A8B3003E422D /* FetchRequestTests.swift in Sources */,
56B86E79220FF4E000524C16 /* SQLLiteralTests.swift in Sources */,
56D496831D813147008276D7 /* DatabaseSavepointTests.swift in Sources */,
Expand Down
20 changes: 19 additions & 1 deletion GRDB/Core/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ public struct Configuration {

// MARK: - Encryption

#if SQLITE_HAS_CODEC
// For SQLCipher
#if SQLITE_HAS_CODEC && GRDBCIPHER
/// The passphrase for the encrypted database.
///
/// Default: nil
Expand All @@ -97,6 +98,23 @@ public struct Configuration {
public var kdfIterations: Int = 64000
#endif

// For SQLite-SEE
#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE

/// The key for an attempting to open an encrypted database.
///
/// This is ignored if set to nil
///
/// Default: nil
public var key: String?

/// Which algorithm to use when opening an encrypted database
///
/// This is only used if a key is set to non-nil
public var encryptionAlgorithm: Database.EncryptionAlgorithm = .AES128

#endif


// MARK: - Transactions

Expand Down
62 changes: 59 additions & 3 deletions GRDB/Core/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,19 @@ public final class Database {
let sqliteConnection = try Database.openConnection(path: path, flags: configuration.SQLiteOpenFlags)
do {
try Database.activateExtendedCodes(sqliteConnection)
#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
try Database.validateSQLCipher(sqliteConnection)
if let passphrase = configuration.passphrase {
try Database.set(passphrase: passphrase, forConnection: sqliteConnection)
try Database.set(cipherPageSize: configuration.cipherPageSize, forConnection: sqliteConnection)
try Database.set(kdfIterations: configuration.kdfIterations, forConnection: sqliteConnection)
}
#endif
#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
if let key = configuration.key {
try Database.set(key: key, withEncryptionAlgorithm: configuration.encryptionAlgorithm, forConnection: sqliteConnection)
}
#endif
try Database.validateDatabaseFormat(sqliteConnection)
} catch {
Database.closeConnection(sqliteConnection)
Expand Down Expand Up @@ -229,7 +234,7 @@ extension Database {
}
}

#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
private static func validateSQLCipher(_ sqliteConnection: SQLiteConnection) throws {
// https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688
//
Expand Down Expand Up @@ -301,6 +306,20 @@ extension Database {
}
#endif

// For SQLite-SEE
#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
private static func set(key: String, withEncryptionAlgorithm algorithm: EncryptionAlgorithm, forConnection sqliteConnection: SQLiteConnection) throws {
let prefixedKey = "\(algorithm.rawValue):\(key)"
let data = prefixedKey.data(using: .utf8)!
let code = data.withUnsafeBytes {
sqlite3_key_v2(sqliteConnection, nil, $0.baseAddress, Int32($0.count))
barnettben marked this conversation as resolved.
Show resolved Hide resolved
}
guard code == SQLITE_OK else {
throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
}
}
#endif

private static func validateDatabaseFormat(_ sqliteConnection: SQLiteConnection) throws {
// Users are surprised when they open a picture as a database and
// see no error (https://github.com/groue/GRDB.swift/issues/54).
Expand Down Expand Up @@ -914,7 +933,7 @@ extension Database {
}
}

#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
extension Database {

// MARK: - Encryption
Expand Down Expand Up @@ -946,6 +965,24 @@ extension Database {
}
#endif

#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
extension Database {

// MARK: - Encryption

func change(key: String, encryptionAlgorithm:EncryptionAlgorithm) throws {
let prefixedKey = "\(encryptionAlgorithm.rawValue):\(key)"
let data = prefixedKey.data(using: .utf8)!
let code = data.withUnsafeBytes {
sqlite3_rekey_v2(sqliteConnection, nil, $0.baseAddress, Int32($0.count))
barnettben marked this conversation as resolved.
Show resolved Hide resolved
}
guard code == SQLITE_OK else {
throw DatabaseError(resultCode: code, message: lastErrorMessage)
}
}
}
#endif

extension Database {

/// See BusyMode and https://www.sqlite.org/c3ref/busy_handler.html
Expand Down Expand Up @@ -1128,3 +1165,22 @@ extension Database {
}
}
}

#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
extension Database {
/// An SQLite Encryption Extension encryption type
///
/// The raw value is the string to prefix a key with
public enum EncryptionAlgorithm: String {

/// - note: AES128 is the default choice for new files
case AES128 = "aes128"


case AES256 = "aes256"

/// - warning: Use of RC4 for new files is not recommended
case RC4 = "rc4"
}
}
#endif
17 changes: 16 additions & 1 deletion GRDB/Core/DatabasePool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ extension DatabasePool {
#endif
}

#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
extension DatabasePool {

// MARK: - Encryption
Expand All @@ -229,6 +229,21 @@ extension DatabasePool {
}
}
#endif
#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
extension DatabasePool {

// MARK: - Encryption

/// Changes the key of an encrypted database
public func change(key: String, encryptionAlgorithm: Database.EncryptionAlgorithm) throws {
try readerPool.clear(andThen: {
try writer.sync { try $0.change(key: key, encryptionAlgorithm: encryptionAlgorithm) }
readerConfig.key = key
readerConfig.encryptionAlgorithm = encryptionAlgorithm
})
}
}
#endif

extension DatabasePool : DatabaseReader {

Expand Down
13 changes: 12 additions & 1 deletion GRDB/Core/DatabaseQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ extension DatabaseQueue {
#endif
}

#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
extension DatabaseQueue {

// MARK: - Encryption
Expand All @@ -143,6 +143,17 @@ extension DatabaseQueue {
}
}
#endif
#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
extension DatabaseQueue {

// MARK: - Encryption

/// Changes the key of an encrypted database
public func change(key: String, encryptionAlgorithm: Database.EncryptionAlgorithm) throws {
try writer.sync { try $0.change(key: key, encryptionAlgorithm: encryptionAlgorithm) }
}
}
#endif

extension DatabaseQueue {

Expand Down
2 changes: 1 addition & 1 deletion GRDB/Core/DatabaseWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ extension DatabaseWriter {
/// - precondition: database is not accessed concurrently during the
/// execution of this method.
public func erase() throws {
#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
// SQLCipher does not support the backup API: https://discuss.zetetic.net/t/using-the-sqlite-online-backup-api/2631
// So we'll drop all database objects one after the other.
try writeWithoutTransaction { db in
Expand Down
Loading