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 all 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
27 changes: 27 additions & 0 deletions Documentation/CustomSQLiteBuilds.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,30 @@ import GRDBCustomSQLite

let dbQueue = try DatabaseQueue(...)
```


**To install GRDB with a custom SQLite with Encryption Extension:**

**Note:** You will need to use Swift 5 or above to use GRDB with the SQLite Encryption Extension

1. Follow the steps above for installing a regular custom SQLite build

2. Locate your copy of the SEE code on your system

3. In your `GRDBCustomSQLite-USER.xcconfig`, add the following entries:

Append `-D SQLITE_HAS_CODEC -D GRDB_SQLITE_SEE` to your `CUSTOM_OTHER_SWIFT_FLAGS`

Add the following to the end of the file:
```SQLITE_SEE_PREFIX = /path/to/your/see-code/see-prefix.txt
SQLITE_SEE_CODE = /path/to/your/see-code/see.c
```

4. In your `GRDBCustomSQLite-USER.h`, add the following lines:
```#define SQLITE_HAS_CODEC
#define GRDB_SQLITE_SEE
```

This allows GRDB to include the needed API around the encryption extension.

5. You can now use GRDB around the SQLite Encryption Extension by specifying a key in your database configuration
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
19 changes: 18 additions & 1 deletion GRDB/Core/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,30 @@ public struct Configuration {

// MARK: - Encryption

#if SQLITE_HAS_CODEC
// For SQLCipher
#if SQLITE_HAS_CODEC && GRDBCIPHER
/// The passphrase for the encrypted database.
///
/// Default: nil
public var passphrase: String?

#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

/// If set, allows custom configuration to be run every time
/// a new connection is opened.
Expand Down
62 changes: 59 additions & 3 deletions GRDB/Core/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,18 @@ extension Database {
observationBroker.installCommitAndRollbackHooks()
try activateExtendedCodes()

#if SQLITE_HAS_CODEC
#if SQLITE_HAS_CODEC && GRDBCIPHER
try validateSQLCipher()
if let passphrase = configuration.passphrase {
try setCipherPassphrase(passphrase)
}
#endif

#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
if let key = configuration.key {
try setEncryptionKey(key, algorithm: configuration.encryptionAlgorithm)
}
#endif

// Last step before we can start accessing the database.
// This is the opportunity to run SQLCipher configuration
Expand Down Expand Up @@ -344,7 +350,7 @@ extension Database {
}
}

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

#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE
private func setEncryptionKey(_ key: String, algorithm: EncryptionAlgorithm) 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))
}
guard code == SQLITE_OK else {
throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection)))
}
}
#endif

private func validateFormat() 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 @@ -869,7 +888,7 @@ extension Database {
}
}

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

// MARK: - Encryption
Expand Down Expand Up @@ -901,6 +920,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 @@ -1083,3 +1120,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