diff --git a/Documentation/CustomSQLiteBuilds.md b/Documentation/CustomSQLiteBuilds.md index 933453afa4..33ff1361eb 100644 --- a/Documentation/CustomSQLiteBuilds.md +++ b/Documentation/CustomSQLiteBuilds.md @@ -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 diff --git a/GRDB.xcodeproj/project.pbxproj b/GRDB.xcodeproj/project.pbxproj index fa5ed150e2..7fdd6495ff 100755 --- a/GRDB.xcodeproj/project.pbxproj +++ b/GRDB.xcodeproj/project.pbxproj @@ -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 */; }; @@ -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 */; }; @@ -1072,7 +1072,6 @@ 56703290212B544F007D270F /* DatabaseUUIDEncodingStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUUIDEncodingStrategyTests.swift; sourceTree = ""; }; 567071FA208A509C006AD95A /* DateParsingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateParsingTests.swift; sourceTree = ""; }; 567156151CB142AA007DC145 /* DatabaseQueueReadOnlyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueReadOnlyTests.swift; sourceTree = ""; }; - 567156701CB18050007DC145 /* EncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = ""; }; 5671FC1F1DA3CAC9003BF4FF /* FTS3TokenizerDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS3TokenizerDescriptor.swift; sourceTree = ""; }; 5672DE581CDB72520022BA81 /* DatabaseQueueBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueBackupTests.swift; sourceTree = ""; }; 5672DE661CDB751D0022BA81 /* DatabasePoolBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolBackupTests.swift; sourceTree = ""; }; @@ -1268,6 +1267,7 @@ 56FF453F1D2C23BA00F21EF9 /* MutablePersistableRecordDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutablePersistableRecordDeleteTests.swift; sourceTree = ""; }; 56FF45551D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordUniqueIndexTests.swift; sourceTree = ""; }; 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordPersistenceConflictPolicy.swift; sourceTree = ""; }; + B501B3472254A5440071DCDC /* GRDBCipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRDBCipherTests.swift; sourceTree = ""; }; C96C0F242084A442006B2981 /* SQLiteDateParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteDateParser.swift; sourceTree = ""; }; DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GRDB-Bridging.h"; sourceTree = ""; }; DC3773F319C8CBB3004FCF85 /* GRDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GRDB.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1507,7 +1507,7 @@ 56176CA01EACEE2A000F3F2B /* GRDBCipher */ = { isa = PBXGroup; children = ( - 567156701CB18050007DC145 /* EncryptionTests.swift */, + B501B3472254A5440071DCDC /* GRDBCipherTests.swift */, ); name = GRDBCipher; sourceTree = ""; @@ -2318,6 +2318,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -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 */, @@ -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 */, @@ -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 */, @@ -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 */, diff --git a/GRDB/Core/Configuration.swift b/GRDB/Core/Configuration.swift index 5b547e1ddc..c552d77081 100644 --- a/GRDB/Core/Configuration.swift +++ b/GRDB/Core/Configuration.swift @@ -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. diff --git a/GRDB/Core/Database.swift b/GRDB/Core/Database.swift index feff648620..64d79535ce 100644 --- a/GRDB/Core/Database.swift +++ b/GRDB/Core/Database.swift @@ -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 @@ -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 // @@ -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). @@ -869,7 +888,7 @@ extension Database { } } -#if SQLITE_HAS_CODEC +#if SQLITE_HAS_CODEC && GRDBCIPHER extension Database { // MARK: - Encryption @@ -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)) + } + 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 @@ -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 diff --git a/GRDB/Core/DatabasePool.swift b/GRDB/Core/DatabasePool.swift index d2ef08cd02..dd74ba35a3 100644 --- a/GRDB/Core/DatabasePool.swift +++ b/GRDB/Core/DatabasePool.swift @@ -215,7 +215,7 @@ extension DatabasePool { #endif } -#if SQLITE_HAS_CODEC +#if SQLITE_HAS_CODEC && GRDBCIPHER extension DatabasePool { // MARK: - Encryption @@ -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 { diff --git a/GRDB/Core/DatabaseQueue.swift b/GRDB/Core/DatabaseQueue.swift index 7d87f698af..e3c8641223 100644 --- a/GRDB/Core/DatabaseQueue.swift +++ b/GRDB/Core/DatabaseQueue.swift @@ -132,7 +132,7 @@ extension DatabaseQueue { #endif } -#if SQLITE_HAS_CODEC +#if SQLITE_HAS_CODEC && GRDBCIPHER extension DatabaseQueue { // MARK: - Encryption @@ -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 { diff --git a/GRDB/Core/DatabaseWriter.swift b/GRDB/Core/DatabaseWriter.swift index c313ef2ba8..b915d5ad89 100644 --- a/GRDB/Core/DatabaseWriter.swift +++ b/GRDB/Core/DatabaseWriter.swift @@ -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 diff --git a/GRDBCipher.xcodeproj/project.pbxproj b/GRDBCipher.xcodeproj/project.pbxproj index cdc5ba54ac..6575b8dad0 100755 --- a/GRDBCipher.xcodeproj/project.pbxproj +++ b/GRDBCipher.xcodeproj/project.pbxproj @@ -591,10 +591,6 @@ 567DAF371EAB789800FC0928 /* DatabaseLogErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DAF341EAB789800FC0928 /* DatabaseLogErrorTests.swift */; }; 567DAF3A1EAB789800FC0928 /* DatabaseLogErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DAF341EAB789800FC0928 /* DatabaseLogErrorTests.swift */; }; 567DAF3B1EAB789800FC0928 /* DatabaseLogErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567DAF341EAB789800FC0928 /* DatabaseLogErrorTests.swift */; }; - 567E55ED1D2BDD3D00CC6F79 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; }; - 567E55EE1D2BDD3F00CC6F79 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; }; - 567E55F31D2BDDFE00CC6F79 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; }; - 567E55F41D2BDDFF00CC6F79 /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; }; 567F0B33220F196F00D111FB /* SQLInterpolationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567F0B32220F196F00D111FB /* SQLInterpolationTests.swift */; }; 567F0B34220F196F00D111FB /* SQLInterpolationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567F0B32220F196F00D111FB /* SQLInterpolationTests.swift */; }; 567F0B35220F196F00D111FB /* SQLInterpolationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567F0B32220F196F00D111FB /* SQLInterpolationTests.swift */; }; @@ -995,6 +991,8 @@ 6340BF821E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; }; 6340BF851E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; }; 6340BF861E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; }; + B501B34C2254A56B0071DCDC /* GRDBCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501B34B2254A56B0071DCDC /* GRDBCipherTests.swift */; }; + B501B34D2254A56B0071DCDC /* GRDBCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501B34B2254A56B0071DCDC /* GRDBCipherTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1208,7 +1206,6 @@ 567071F6208A00D4006AD95A /* SQLiteDateParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteDateParser.swift; sourceTree = ""; }; 567156151CB142AA007DC145 /* DatabaseQueueReadOnlyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueReadOnlyTests.swift; sourceTree = ""; }; 5671566C1CB16729007DC145 /* GRDBCipherOSXEncryptedTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GRDBCipherOSXEncryptedTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 567156701CB18050007DC145 /* EncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = ""; }; 5671FC1F1DA3CAC9003BF4FF /* FTS3TokenizerDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS3TokenizerDescriptor.swift; sourceTree = ""; }; 5672DE581CDB72520022BA81 /* DatabaseQueueBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueBackupTests.swift; sourceTree = ""; }; 5672DE661CDB751D0022BA81 /* DatabasePoolBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolBackupTests.swift; sourceTree = ""; }; @@ -1371,6 +1368,7 @@ 56FF453F1D2C23BA00F21EF9 /* MutablePersistableRecordDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutablePersistableRecordDeleteTests.swift; sourceTree = ""; }; 56FF45551D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordUniqueIndexTests.swift; sourceTree = ""; }; 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordPersistenceConflictPolicy.swift; sourceTree = ""; }; + B501B34B2254A56B0071DCDC /* GRDBCipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRDBCipherTests.swift; sourceTree = ""; }; DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GRDB-Bridging.h"; sourceTree = ""; }; DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC3773F819C8CBB3004FCF85 /* GRDB.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GRDB.h; sourceTree = ""; }; @@ -1621,7 +1619,7 @@ 56176CA01EACEE2A000F3F2B /* GRDBCipher */ = { isa = PBXGroup; children = ( - 567156701CB18050007DC145 /* EncryptionTests.swift */, + B501B34B2254A56B0071DCDC /* GRDBCipherTests.swift */, ); name = GRDBCipher; sourceTree = ""; @@ -2285,6 +2283,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -2559,6 +2558,7 @@ 560FC5791CB00B880014AA8E /* RecordPrimaryKeyMultipleTests.swift in Sources */, 56CC9257201E094F00CB597E /* PrefixCursorTests.swift in Sources */, 5698AC971DA4B0430056AF8C /* FTS4RecordTests.swift in Sources */, + B501B34C2254A56B0071DCDC /* GRDBCipherTests.swift in Sources */, 5657AB4F1D108BA9006283EF /* FoundationNSNumberTests.swift in Sources */, 56CC9236201E009700CB597E /* DropWhileCursorTests.swift in Sources */, 56D5075F1F6BAE8600AE1C5B /* PrimaryKeyInfoTests.swift in Sources */, @@ -2593,7 +2593,6 @@ 5665F86C203EF47D0084C6C0 /* ColumnInfoTests.swift in Sources */, 560FC5811CB00B880014AA8E /* RecordCopyTests.swift in Sources */, 560FC5821CB00B880014AA8E /* RawRepresentable+DatabaseValueConvertibleTests.swift in Sources */, - 567E55ED1D2BDD3D00CC6F79 /* EncryptionTests.swift in Sources */, 56741EA91E66A8B3003E422D /* FetchRequestTests.swift in Sources */, 56176C641EACCCC7000F3F2B /* FTS5WrapperTokenizerTests.swift in Sources */, 5653EBB820961FE800F46237 /* AssociationParallelSQLTests.swift in Sources */, @@ -2767,7 +2766,6 @@ 56B6EF65208CB762002F0ACB /* ColumnExpressionTests.swift in Sources */, 5671563C1CB16729007DC145 /* MutablePersistableRecordTests.swift in Sources */, 5671563D1CB16729007DC145 /* FetchableRecord+QueryInterfaceRequestTests.swift in Sources */, - 567E55F31D2BDDFE00CC6F79 /* EncryptionTests.swift in Sources */, 56B86E7E220FF4F500524C16 /* SQLLiteralTests.swift in Sources */, 5671563F1CB16729007DC145 /* RecordCopyTests.swift in Sources */, 5698ACB81DA6285E0056AF8C /* FTS3TokenizerTests.swift in Sources */, @@ -3024,7 +3022,6 @@ 562EA82B1F17B2AC00FA528C /* CompilationProtocolTests.swift in Sources */, 56DAA2D71DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 5698AC4E1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, - 567E55EE1D2BDD3F00CC6F79 /* EncryptionTests.swift in Sources */, 56AFCA361CB1AA9900F48B96 /* DatabaseCollationTests.swift in Sources */, 56AFCA371CB1AA9900F48B96 /* StatementColumnConvertibleFetchTests.swift in Sources */, 56AFCA381CB1AA9900F48B96 /* QueryInterfaceExpressionsTests.swift in Sources */, @@ -3065,6 +3062,7 @@ 566AD8CB1D531BED002EC1A8 /* TableDefinitionTests.swift in Sources */, 56AFCA4B1CB1AA9900F48B96 /* MutablePersistableRecordTests.swift in Sources */, 56CC9259201E094F00CB597E /* PrefixCursorTests.swift in Sources */, + B501B34D2254A56B0071DCDC /* GRDBCipherTests.swift in Sources */, 5657AB431D108BA9006283EF /* FoundationNSDataTests.swift in Sources */, 56AFCA4C1CB1AA9900F48B96 /* FetchableRecord+QueryInterfaceRequestTests.swift in Sources */, 56CC9238201E009900CB597E /* DropWhileCursorTests.swift in Sources */, @@ -3214,7 +3212,6 @@ 56DAA2D81DE99DAB006E10C8 /* DatabaseCursorTests.swift in Sources */, 5698AC4F1DA2D48A0056AF8C /* FTS3RecordTests.swift in Sources */, 56AFCA8F1CB1ABC800F48B96 /* DatabaseCollationTests.swift in Sources */, - 567E55F41D2BDDFF00CC6F79 /* EncryptionTests.swift in Sources */, 56AFCA901CB1ABC800F48B96 /* StatementColumnConvertibleFetchTests.swift in Sources */, 5690C33D1D23E7D200E59934 /* FoundationDateTests.swift in Sources */, 5672DE6C1CDB751D0022BA81 /* DatabasePoolBackupTests.swift in Sources */, diff --git a/GRDBCustom.xcodeproj/project.pbxproj b/GRDBCustom.xcodeproj/project.pbxproj index d6af4d6ec3..8fb83b2dfb 100755 --- a/GRDBCustom.xcodeproj/project.pbxproj +++ b/GRDBCustom.xcodeproj/project.pbxproj @@ -42,8 +42,8 @@ 5616AAF7207CD59400AC3664 /* RequestProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5616AAF4207CD59300AC3664 /* RequestProtocols.swift */; }; 561729552235340E0006E219 /* EncodableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561729532235340E0006E219 /* EncodableRecord.swift */; }; 561729562235340E0006E219 /* EncodableRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 561729532235340E0006E219 /* EncodableRecord.swift */; }; - 56176C7E1EACCD2F000F3F2B /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; }; - 56176C801EACCD31000F3F2B /* EncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* EncryptionTests.swift */; }; + 56176C7E1EACCD2F000F3F2B /* GRDBCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* GRDBCipherTests.swift */; }; + 56176C801EACCD31000F3F2B /* GRDBCipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 567156701CB18050007DC145 /* GRDBCipherTests.swift */; }; 562205FA1E420E49005860AC /* DatabasePoolReleaseMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363CF1C943D13000BE133 /* DatabasePoolReleaseMemoryTests.swift */; }; 562205FB1E420E49005860AC /* DatabasePoolSchemaCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 569531281C908A5B00CF1A2B /* DatabasePoolSchemaCacheTests.swift */; }; 562205FC1E420E49005860AC /* DatabaseQueueReleaseMemoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 563363D41C94484E000BE133 /* DatabaseQueueReleaseMemoryTests.swift */; }; @@ -428,6 +428,8 @@ 56FF455D1D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56FF45551D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift */; }; 6340BF831E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; }; 6340BF871E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */; }; + B5C0503F2252574500CA794A /* SQLiteEncryptionExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C0503E2252574500CA794A /* SQLiteEncryptionExtensionTests.swift */; }; + B5C050402252574500CA794A /* SQLiteEncryptionExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C0503E2252574500CA794A /* SQLiteEncryptionExtensionTests.swift */; }; EED476F21CFD17270026A4EC /* GRDBCustomSQLite-USER.h in Headers */ = {isa = PBXBuildFile; fileRef = EED476F11CFD16FF0026A4EC /* GRDBCustomSQLite-USER.h */; settings = {ATTRIBUTES = (Public, ); }; }; EED476F31CFD172C0026A4EC /* GRDBCustomSQLite-USER.h in Headers */ = {isa = PBXBuildFile; fileRef = EED476F11CFD16FF0026A4EC /* GRDBCustomSQLite-USER.h */; settings = {ATTRIBUTES = (Public, ); }; }; F3BA800A1CFB286A003DC1BA /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A238701B9C75030082EB20 /* Configuration.swift */; }; @@ -824,7 +826,7 @@ 56703299212B5461007D270F /* DatabaseUUIDEncodingStrategyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseUUIDEncodingStrategyTests.swift; sourceTree = ""; }; 567071F2208A00BE006AD95A /* SQLiteDateParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteDateParser.swift; sourceTree = ""; }; 567156151CB142AA007DC145 /* DatabaseQueueReadOnlyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueReadOnlyTests.swift; sourceTree = ""; }; - 567156701CB18050007DC145 /* EncryptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionTests.swift; sourceTree = ""; }; + 567156701CB18050007DC145 /* GRDBCipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GRDBCipherTests.swift; sourceTree = ""; }; 5671FC1F1DA3CAC9003BF4FF /* FTS3TokenizerDescriptor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS3TokenizerDescriptor.swift; sourceTree = ""; }; 5672DE581CDB72520022BA81 /* DatabaseQueueBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseQueueBackupTests.swift; sourceTree = ""; }; 5672DE661CDB751D0022BA81 /* DatabasePoolBackupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabasePoolBackupTests.swift; sourceTree = ""; }; @@ -979,6 +981,7 @@ 56FF453F1D2C23BA00F21EF9 /* MutablePersistableRecordDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutablePersistableRecordDeleteTests.swift; sourceTree = ""; }; 56FF45551D2CDA5200F21EF9 /* RecordUniqueIndexTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordUniqueIndexTests.swift; sourceTree = ""; }; 6340BF7F1E5E3F7900832805 /* RecordPersistenceConflictPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecordPersistenceConflictPolicy.swift; sourceTree = ""; }; + B5C0503E2252574500CA794A /* SQLiteEncryptionExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SQLiteEncryptionExtensionTests.swift; sourceTree = ""; }; DC2393C61ABE35F8003FF113 /* GRDB-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GRDB-Bridging.h"; sourceTree = ""; }; DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DC3773F819C8CBB3004FCF85 /* GRDB.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GRDB.h; sourceTree = ""; }; @@ -1211,7 +1214,8 @@ 56176CA01EACEE2A000F3F2B /* GRDBCipher */ = { isa = PBXGroup; children = ( - 567156701CB18050007DC145 /* EncryptionTests.swift */, + 567156701CB18050007DC145 /* GRDBCipherTests.swift */, + B5C0503E2252574500CA794A /* SQLiteEncryptionExtensionTests.swift */, ); name = GRDBCipher; sourceTree = ""; @@ -2150,6 +2154,7 @@ 5653EB7D20961FB200F46237 /* AssociationBelongsToSQLTests.swift in Sources */, 563B070021861D9D00B38F35 /* ValueObservationCountTests.swift in Sources */, 5698AC0A1D9B9FCF0056AF8C /* QueryInterfaceExtensibilityTests.swift in Sources */, + B5C050402252574500CA794A /* SQLiteEncryptionExtensionTests.swift in Sources */, 56EA63CA209C7F1E009715B8 /* DerivableRequestTests.swift in Sources */, F3BA80E91CFB3016003DC1BA /* RowAdapterTests.swift in Sources */, 562393371DEDFC5700A6B01F /* AnyCursorTests.swift in Sources */, @@ -2196,7 +2201,7 @@ 563B071321862C3E00B38F35 /* ValueObservationRecordTests.swift in Sources */, 56B6EF61208CB746002F0ACB /* ColumnExpressionTests.swift in Sources */, F3BA81121CFB3059003DC1BA /* DatabaseMigratorTests.swift in Sources */, - 56176C801EACCD31000F3F2B /* EncryptionTests.swift in Sources */, + 56176C801EACCD31000F3F2B /* GRDBCipherTests.swift in Sources */, F3BA80EB1CFB3016003DC1BA /* FetchableRecordTests.swift in Sources */, 56B86E71220FF4C900524C16 /* SQLLiteralTests.swift in Sources */, F3BA81151CFB305E003DC1BA /* Record+QueryInterfaceRequestTests.swift in Sources */, @@ -2467,6 +2472,7 @@ 5653EB7C20961FB200F46237 /* AssociationBelongsToSQLTests.swift in Sources */, 563B06FF21861D9D00B38F35 /* ValueObservationCountTests.swift in Sources */, 5698ACDA1DA925430056AF8C /* RowTestCase.swift in Sources */, + B5C0503F2252574500CA794A /* SQLiteEncryptionExtensionTests.swift in Sources */, 56EA63C9209C7F1E009715B8 /* DerivableRequestTests.swift in Sources */, F3BA811D1CFB305F003DC1BA /* TableRecord+QueryInterfaceRequestTests.swift in Sources */, F3BA80AD1CFB2FA6003DC1BA /* DataMemoryTests.swift in Sources */, @@ -2514,7 +2520,7 @@ 56B6EF60208CB746002F0ACB /* ColumnExpressionTests.swift in Sources */, 562205FD1E420EA2005860AC /* DatabasePoolBackupTests.swift in Sources */, 562206091E420EB2005860AC /* DatabaseQueueConcurrencyTests.swift in Sources */, - 56176C7E1EACCD2F000F3F2B /* EncryptionTests.swift in Sources */, + 56176C7E1EACCD2F000F3F2B /* GRDBCipherTests.swift in Sources */, 56B86E70220FF4C900524C16 /* SQLLiteralTests.swift in Sources */, F3BA811C1CFB305F003DC1BA /* QueryInterfaceExpressionsTests.swift in Sources */, 5698ACD11DA8C2620056AF8C /* RecordPrimaryKeyHiddenRowIDTests.swift in Sources */, diff --git a/Tests/GRDBTests/EncryptionTests.swift b/Tests/GRDBTests/GRDBCipherTests.swift similarity index 99% rename from Tests/GRDBTests/EncryptionTests.swift rename to Tests/GRDBTests/GRDBCipherTests.swift index f21f67597e..592d1bf7a6 100644 --- a/Tests/GRDBTests/EncryptionTests.swift +++ b/Tests/GRDBTests/GRDBCipherTests.swift @@ -1,8 +1,8 @@ -#if SQLITE_HAS_CODEC +#if SQLITE_HAS_CODEC && GRDBCIPHER import XCTest import GRDBCipher -class EncryptionTests: GRDBTestCase { +class GRDBCipherTests: GRDBTestCase { func testDatabaseQueueWithPassphraseToDatabaseQueueWithPassphrase() throws { do { diff --git a/Tests/GRDBTests/SQLiteEncryptionExtensionTests.swift b/Tests/GRDBTests/SQLiteEncryptionExtensionTests.swift new file mode 100644 index 0000000000..b8e58996cd --- /dev/null +++ b/Tests/GRDBTests/SQLiteEncryptionExtensionTests.swift @@ -0,0 +1,411 @@ +#if SQLITE_HAS_CODEC && GRDB_SQLITE_SEE +import XCTest +@testable import GRDBCustomSQLite + +class SQLiteEncryptionExtensionTests: GRDBTestCase { + + func testDatabaseQueueWithKeyToDatabaseQueueWithKey() throws { + do { + dbConfiguration.key = "secret" + dbConfiguration.encryptionAlgorithm = .AES128 + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + dbConfiguration.encryptionAlgorithm = .AES128 + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 1) + } + } + } + + func testDatabaseQueueWithKeyToDatabaseQueueWithoutKey() throws { + do { + dbConfiguration.key = "secret" + dbConfiguration.encryptionAlgorithm = .AES128 + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = nil + do { + _ = try makeDatabaseQueue(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabaseQueueWithKeyToDatabaseQueueWithWrongKey() throws { + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "wrong" + do { + _ = try makeDatabaseQueue(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabaseQueueWithKeyToDatabaseQueueWithWrongEncryptionAlgorithm() throws { + do { + dbConfiguration.key = "secret" + dbConfiguration.encryptionAlgorithm = .AES128 + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + dbConfiguration.encryptionAlgorithm = .AES256 + do { + _ = try makeDatabaseQueue(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabaseQueueWithKeyToDatabaseQueueWithNewKeyAndAlgorithm() throws { + do { + dbConfiguration.key = "secret" + dbConfiguration.encryptionAlgorithm = .AES128 + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.change(key: "newSecret", encryptionAlgorithm: .AES256) + try dbQueue.inDatabase { db in + try db.execute(sql: "INSERT INTO data (value) VALUES (2)") + } + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 2) + } + } + + do { + dbConfiguration.key = "newSecret" + dbConfiguration.encryptionAlgorithm = .AES256 + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 2) + } + } + } + + func testDatabaseQueueWithKeyToDatabasePoolWithKey() throws { + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 1) + } + } + } + + func testDatabaseQueueWithKeyToDatabasePoolWithoutKey() throws { + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = nil + do { + _ = try makeDatabasePool(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabaseQueueWithKeyToDatabasePoolWithWrongKey() throws { + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "wrong" + do { + _ = try makeDatabasePool(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabaseQueueWithKeyToDatabasePoolWithNewKey() throws { + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.change(key: "newSecret", encryptionAlgorithm: .AES128) + try dbPool.write { db in + try db.execute(sql: "INSERT INTO data (value) VALUES (2)") + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 2) + } + } + + do { + dbConfiguration.key = "newSecret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 2) + } + } + } + + func testDatabasePoolWithKeyToDatabasePoolWithKey() throws { + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.write { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 1) + } + } + } + + func testDatabasePoolWithKeyToDatabasePoolWithoutKey() throws { + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.write { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = nil + do { + _ = try makeDatabasePool(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabasePoolWithKeyToDatabasePoolWithWrongKey() throws { + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.write { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "wrong" + do { + _ = try makeDatabasePool(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabasePoolWithKeyToDatabasePoolWithWrongEncryptionAlgorithm() throws { + let key = "secret" + do { + dbConfiguration.key = key + dbConfiguration.encryptionAlgorithm = .AES128 + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.write { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = key + dbConfiguration.encryptionAlgorithm = .AES256 + do { + _ = try makeDatabasePool(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabasePoolWithKeyToDatabasePoolWithNewKey() throws { + + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.write { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.change(key: "newSecret", encryptionAlgorithm: .AES128) + try dbPool.write { db in + try db.execute(sql: "INSERT INTO data (value) VALUES (2)") + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 2) + } + } + + do { + dbConfiguration.key = "newSecret" + let dbPool = try makeDatabasePool(filename: "test.sqlite") + try dbPool.read { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 2) + } + } + } + + func testDatabaseQueueWithPragmaKeyToDatabaseQueueWithKey() throws { + do { + dbConfiguration.key = nil + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "PRAGMA key = 'secret'") + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 1) + } + } + } + + func testDatabaseQueueWithPragmaKeyToDatabaseQueueWithoutKey() throws { + do { + dbConfiguration.key = nil + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "PRAGMA key = 'secret'") + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = nil + do { + _ = try makeDatabaseQueue(filename: "test.sqlite") + XCTFail("Expected error") + } catch let error as DatabaseError { + XCTAssertEqual(error.resultCode, .SQLITE_NOTADB) + XCTAssertEqual(error.message!, "file is not a database") + } + } + } + + func testDatabaseQueueWithKeyToDatabaseQueueWithPragmaRemovedKeyAndType() throws { + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "CREATE TABLE data (value INTEGER)") + try db.execute(sql: "INSERT INTO data (value) VALUES (1)") + } + } + + do { + dbConfiguration.key = "secret" + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + try db.execute(sql: "PRAGMA rekey = ''") + } + } + + do { + dbConfiguration.key = nil + let dbQueue = try makeDatabaseQueue(filename: "test.sqlite") + try dbQueue.inDatabase { db in + XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM data")!, 1) + } + } + } + +} +#endif