-
-
Notifications
You must be signed in to change notification settings - Fork 795
SQLCipher (Official) Swift Package Manager Integration #1827
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
Open
R4N
wants to merge
17
commits into
groue:development
Choose a base branch
from
R4N:sqlcipher-spm
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,214
−261
Open
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
a1fbf0d
Adds SQLCipher.swift Swift Package Manager integration when SQLCipher…
R4N a404421
Updates README.md documentation for SQLCipher Swift Package Manager i…
R4N 843b6f2
Adjusts Import Statements to import SQLCipher when SQLCipher trait is…
R4N b11f4e2
- Adds SQLCipherConfig import to Database when SQLCipher trait is ena…
R4N 4d79988
Switches #if GRDBCIPHER in EncryptionTests to #if SQLITE_HAS_CODEC
R4N 011fc7d
Adds test_SPM_SQLCipher as depdendent task of test_framework_darwin
R4N 6666a35
- Adds Tests/SPM/sqlcipher test project + sample AppDependencies SPM …
R4N ad2d06c
- Avoids hiding errors in high-level accessors in Database+SQLCipher
R4N a6593b3
Switches https://www.sqlcipher.net link over to https://www.zetetic.n…
R4N 92ec14c
DatabaseSnapshotPool creates its snapshot with the provided configura…
groue be8ba22
Enable snapshots in the SQLCipher trait
groue e20799d
Database.cipherVersion is not optional
groue 4dfe014
Rename SQLCipherConfig to GRDBSQLCipher
groue ece9f9e
Duplicate the SQLCipher3 test database
groue 988fcfa
Delete ignored file
groue c7b4ac5
Support SQLCipher 4.11+
groue 4683362
Remove Swiftlint rules that warn against our new SQLite import boiler…
groue File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| #if SQLITE_HAS_CODEC | ||
| import SQLCipher | ||
|
|
||
| extension Database { | ||
|
|
||
| /// Granularitly of SQLCipher log outputs | ||
| /// Each log level is more verbose than the last | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log_level | ||
| public enum CipherLogLevel: String { | ||
| case none | ||
| case error | ||
| case warn | ||
| case info | ||
| case debug | ||
| case trace | ||
| } | ||
|
|
||
| /// - Returns: the SQLCipher version | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_version | ||
| public var cipherVersion: String? { | ||
| get throws { try String.fetchOne(self, sql: "PRAGMA cipher_version") } | ||
| } | ||
|
|
||
| /// - Returns: the SQLCipher fips status: 1 for fips mode, 0 for non-fips mode | ||
| /// The FIPS status will not be initialized until the database connection has been keyed | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_fips_status | ||
| public var cipherFipsStatus: String? { | ||
| get throws { try String.fetchOne(self, sql: "PRAGMA cipher_fips_status") } | ||
| } | ||
|
|
||
| /// - Returns: The compiled crypto provider. | ||
| /// The database must be keyed before requesting the name of the crypto provider. | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_provider | ||
| public var cipherProvider: String? { | ||
| get throws { try String.fetchOne(self, sql: "PRAGMA cipher_provider") } | ||
| } | ||
|
|
||
| /// - Returns: the version number provided from the compiled crypto provider. | ||
| /// This value, if known, is available only after the database has been keyed. | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_provider_version | ||
| public var cipherProviderVersion: String? { | ||
| get throws { try String.fetchOne(self, sql: "PRAGMA cipher_provider_version") } | ||
| } | ||
|
|
||
| /// Sets the passphrase used to crypt and decrypt an SQLCipher database. | ||
| /// | ||
| /// Call this method from `Configuration.prepareDatabase`, | ||
| /// as in the example below: | ||
| /// | ||
| /// var config = Configuration() | ||
| /// config.prepareDatabase { db in | ||
| /// try db.usePassphrase("secret") | ||
| /// } | ||
| public func usePassphrase(_ passphrase: String) throws { | ||
| guard var data = passphrase.data(using: .utf8) else { | ||
| throw DatabaseError(message: "invalid passphrase") | ||
| } | ||
| defer { | ||
| data.resetBytes(in: 0..<data.count) | ||
| } | ||
| try usePassphrase(data) | ||
| } | ||
|
|
||
| /// Sets the passphrase used to crypt and decrypt an SQLCipher database. | ||
| /// | ||
| /// Call this method from `Configuration.prepareDatabase`, | ||
| /// as in the example below: | ||
| /// | ||
| /// var config = Configuration() | ||
| /// config.prepareDatabase { db in | ||
| /// try db.usePassphrase(passphraseData) | ||
| /// } | ||
| public func usePassphrase(_ passphrase: Data) throws { | ||
| let code = passphrase.withUnsafeBytes { | ||
| sqlite3_key(sqliteConnection, $0.baseAddress, CInt($0.count)) | ||
| } | ||
| guard code == SQLITE_OK else { | ||
| throw DatabaseError(resultCode: code, message: String(cString: sqlite3_errmsg(sqliteConnection))) | ||
| } | ||
| } | ||
|
|
||
| /// Changes the passphrase used by an SQLCipher encrypted database. | ||
| public func changePassphrase(_ passphrase: String) throws { | ||
| guard var data = passphrase.data(using: .utf8) else { | ||
| throw DatabaseError(message: "invalid passphrase") | ||
| } | ||
| defer { | ||
| data.resetBytes(in: 0..<data.count) | ||
| } | ||
| try changePassphrase(data) | ||
| } | ||
|
|
||
| /// Changes the passphrase used by an SQLCipher encrypted database. | ||
| public func changePassphrase(_ passphrase: Data) throws { | ||
| // FIXME: sqlite3_rekey is discouraged. | ||
| // | ||
| // https://github.com/ccgus/fmdb/issues/547#issuecomment-259219320 | ||
| // | ||
| // > We (Zetetic) have been discouraging the use of sqlite3_rekey in | ||
| // > favor of attaching a new database with the desired encryption | ||
| // > options and using sqlcipher_export() to migrate the contents and | ||
| // > schema of the original db into the new one: | ||
| // > https://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-a-database-errors/ | ||
| let code = passphrase.withUnsafeBytes { | ||
| sqlite3_rekey(sqliteConnection, $0.baseAddress, CInt($0.count)) | ||
| } | ||
| guard code == SQLITE_OK else { | ||
| throw DatabaseError(resultCode: code, message: lastErrorMessage) | ||
| } | ||
| } | ||
|
|
||
| /// When using Commercial or Enterprise SQLCipher packages you must call | ||
| /// `PRAGMA cipher_license` with a valid license code prior to executing | ||
| /// cryptographic operations on an encrypted database. | ||
| /// Failure to provide a license code, or use of an expired trial code, | ||
| /// will result in an `SQLITE_AUTH (23)` error code reported from the SQLite API | ||
| /// License Codes will activate SQLCipher Commercial or Enterprise packages | ||
| /// from Zetetic: https://www.zetetic.net/sqlcipher/buy/ | ||
| /// 15-day free trials are available by request: https://www.zetetic.net/sqlcipher/trial/ | ||
| /// | ||
| /// Call this method from `Configuration.prepareDatabase`, | ||
| /// as in the example below: | ||
| /// | ||
| /// var config = Configuration() | ||
| /// config.prepareDatabase { db in | ||
| /// try db.applyLicense(license) | ||
| /// } | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_license | ||
| /// - Parameter license: base64 SQLCipher license code to activate SQLCipher commercial | ||
| public func applyLicense(_ license: String) throws { | ||
| try execute(sql: "PRAGMA cipher_license = '\(license)'") | ||
| } | ||
|
|
||
| /// Instructs SQLCipher to log internal debugging and operational information | ||
| /// to the sepecified log target (device) using `os_log` | ||
| /// The supplied logLevel will determine the granularity of the logs output | ||
| /// Available logLevel options are: NONE, ERROR, WARN, INFO, DEBUG, TRACE | ||
| /// Note that each level is more verbose than the last, | ||
| /// and particularly with DEBUG and TRACE the logging system will generate | ||
| /// a significant log volume | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log | ||
| /// - Parameter logLevel: CipherLogLevel The granularity to use for the logging system - defaults to `DEBUG` | ||
| public func enableCipherLogging(logLevel: CipherLogLevel = .debug) throws { | ||
| try execute(sql: "PRAGMA cipher_log = device") | ||
| try execute(sql: "PRAGMA cipher_log_level = \(logLevel.rawValue.uppercased())") | ||
| } | ||
|
|
||
| /// Instructs SQLCipher to disable logging internal debugging and operational information | ||
| /// | ||
| /// See https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log | ||
| public func disableCipherLogging() throws { | ||
| try execute(sql: "PRAGMA cipher_log_level = \(CipherLogLevel.none.rawValue.uppercased())") | ||
| } | ||
|
|
||
| internal func validateSQLCipher() throws { | ||
| // https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688 | ||
| // | ||
| // > In order to avoid situations where SQLite might be used | ||
| // > improperly at runtime, we strongly recommend that | ||
| // > applications institute a runtime test to ensure that the | ||
| // > application is actually using SQLCipher on the active | ||
| // > connection. | ||
| if try cipherVersion == nil { | ||
| throw DatabaseError(resultCode: .SQLITE_MISUSE, message: """ | ||
| GRDB is not linked against SQLCipher. \ | ||
| Check https://discuss.zetetic.net/t/important-advisory-sqlcipher-with-xcode-8-and-new-sdks/1688 | ||
| """) | ||
| } | ||
| } | ||
|
|
||
| internal func dropAllDatabaseObjects() throws { | ||
| // 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. | ||
|
|
||
| // Prevent foreign keys from messing with drop table statements | ||
| let foreignKeysEnabled = try Bool.fetchOne(self, sql: "PRAGMA foreign_keys")! | ||
| if foreignKeysEnabled { | ||
| try execute(sql: "PRAGMA foreign_keys = OFF") | ||
| } | ||
|
|
||
| try throwingFirstError( | ||
| execute: { | ||
| // Remove all database objects, one after the other | ||
| try inTransaction { | ||
| let sql = "SELECT type, name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%'" | ||
| while let row = try Row.fetchOne(self, sql: sql) { | ||
| let type: String = row["type"] | ||
| let name: String = row["name"] | ||
| try execute(sql: "DROP \(type) \(name.quotedDatabaseIdentifier)") | ||
| } | ||
| return .commit | ||
| } | ||
| }, | ||
| finally: { | ||
| // Restore foreign keys if needed | ||
| if foreignKeysEnabled { | ||
| try execute(sql: "PRAGMA foreign_keys = ON") | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.