Skip to content

Commit 2ee9030

Browse files
authored
Merge pull request stephencelis#556 from stephencelis/sqlcipher
sqlcipher enhancements
2 parents f2f613c + 555a620 commit 2ee9030

File tree

7 files changed

+109
-30
lines changed

7 files changed

+109
-30
lines changed

CocoaPodsTests/integration_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def test_validate_project
1414
def validator
1515
@validator ||= TestRunningValidator.new(podspec, []).tap do |validator|
1616
validator.test_files = Dir["#{project_test_dir}/*.swift"]
17+
validator.test_resources = Dir["#{project_test_dir}/fixtures"]
1718
validator.config.verbose = true
1819
validator.no_clean = true
1920
validator.use_frameworks = true

CocoaPodsTests/test_running_validator.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ class TestRunningValidator < Pod::Validator
77
TEST_TARGET = 'Tests'
88

99
attr_accessor :test_files
10+
attr_accessor :test_resources
1011
attr_accessor :ios_simulator
1112
attr_accessor :tvos_simulator
1213
attr_accessor :watchos_simulator
1314

1415
def initialize(spec_or_path, source_urls)
1516
super(spec_or_path, source_urls)
17+
self.test_files = []
18+
self.test_resources = []
1619
self.ios_simulator = :oldest
1720
self.tvos_simulator = :oldest
1821
self.watchos_simulator = :oldest
@@ -30,6 +33,7 @@ def add_app_project_import
3033
project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj')
3134
group = project.new_group(TEST_TARGET)
3235
test_target = project.targets.last
36+
test_target.add_resources(test_resources.map { |resource| group.new_file(resource) })
3337
test_target.add_file_references(test_files.map { |file| group.new_file(file) })
3438
add_swift_version(test_target)
3539
project.save

SQLite.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@
4646
03A65E941C6BB3030062603F /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B331C3F142E00AE3E12 /* ValueTests.swift */; };
4747
03A65E951C6BB3030062603F /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247B161C3F127200AE3E12 /* TestHelpers.swift */; };
4848
03A65E971C6BB3210062603F /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A65E961C6BB3210062603F /* libsqlite3.tbd */; };
49+
19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
4950
19A1717B10CC941ACB5533D6 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
5051
19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
5152
19A171F12AB8B07F2FD7201A /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
5253
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
54+
19A17408007B182F884E3A53 /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
5355
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1721B8984686B9963B45D /* FTS5Tests.swift */; };
5456
19A1750CEE9B05267995CF3D /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
57+
19A175DFF47B84757E547C62 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
5558
19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
5659
19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
5760
19A17835FD5886FDC5A3228F /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A178A39ACA9667A62663CC /* Cipher.swift */; };
@@ -61,7 +64,10 @@
6164
19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17399EA9E61235D5D77BF /* CipherTests.swift */; };
6265
19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
6366
19A17EC0D68BA8C03288ADF7 /* FTS5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1730E4390C775C25677D1 /* FTS5.swift */; };
67+
19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
68+
19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A17B93B48B5560E6E51791 /* Fixtures.swift */; };
6469
19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19A1794CC4D7827E997E32A7 /* FoundationTests.swift */; };
70+
19A17FDA323BAFDEC627E76F /* fixtures in Resources */ = {isa = PBXBuildFile; fileRef = 19A17E2695737FAB5D6086E3 /* fixtures */; };
6571
3D67B3E61DB2469200A4F4C6 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D67B3E51DB2469200A4F4C6 /* libsqlite3.tbd */; };
6672
3D67B3E71DB246BA00A4F4C6 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEE1C3F06E900AE3E12 /* Blob.swift */; };
6773
3D67B3E81DB246BA00A4F4C6 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE247AEF1C3F06E900AE3E12 /* Connection.swift */; };
@@ -199,6 +205,8 @@
199205
19A17399EA9E61235D5D77BF /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = "<group>"; };
200206
19A178A39ACA9667A62663CC /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = "<group>"; };
201207
19A1794CC4D7827E997E32A7 /* FoundationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationTests.swift; sourceTree = "<group>"; };
208+
19A17B93B48B5560E6E51791 /* Fixtures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixtures.swift; sourceTree = "<group>"; };
209+
19A17E2695737FAB5D6086E3 /* fixtures */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = fixtures; sourceTree = "<group>"; };
202210
39548A631CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
203211
39548A651CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
204212
39548A671CA63C740003E3B5 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
@@ -465,6 +473,8 @@
465473
19A1721B8984686B9963B45D /* FTS5Tests.swift */,
466474
19A1794CC4D7827E997E32A7 /* FoundationTests.swift */,
467475
19A17399EA9E61235D5D77BF /* CipherTests.swift */,
476+
19A17E2695737FAB5D6086E3 /* fixtures */,
477+
19A17B93B48B5560E6E51791 /* Fixtures.swift */,
468478
);
469479
path = SQLiteTests;
470480
sourceTree = "<group>";
@@ -792,6 +802,7 @@
792802
isa = PBXResourcesBuildPhase;
793803
buildActionMask = 2147483647;
794804
files = (
805+
19A17F3E1F7ACA33BD43E138 /* fixtures in Resources */,
795806
);
796807
runOnlyForDeploymentPostprocessing = 0;
797808
};
@@ -813,6 +824,7 @@
813824
isa = PBXResourcesBuildPhase;
814825
buildActionMask = 2147483647;
815826
files = (
827+
19A17FDA323BAFDEC627E76F /* fixtures in Resources */,
816828
);
817829
runOnlyForDeploymentPostprocessing = 0;
818830
};
@@ -827,6 +839,7 @@
827839
isa = PBXResourcesBuildPhase;
828840
buildActionMask = 2147483647;
829841
files = (
842+
19A175DFF47B84757E547C62 /* fixtures in Resources */,
830843
);
831844
runOnlyForDeploymentPostprocessing = 0;
832845
};
@@ -882,6 +895,7 @@
882895
19A17254FBA7894891F7297B /* FTS5Tests.swift in Sources */,
883896
19A17E04C4C0956715C5676A /* FoundationTests.swift in Sources */,
884897
19A179A0C45377CB09BB358C /* CipherTests.swift in Sources */,
898+
19A17F60B685636D1F83C2DD /* Fixtures.swift in Sources */,
885899
);
886900
runOnlyForDeploymentPostprocessing = 0;
887901
};
@@ -961,6 +975,7 @@
961975
19A171E6FA242F72A308C594 /* FTS5Tests.swift in Sources */,
962976
19A17FB80B94E882050AA908 /* FoundationTests.swift in Sources */,
963977
19A177CC33F2E6A24AF90B02 /* CipherTests.swift in Sources */,
978+
19A17408007B182F884E3A53 /* Fixtures.swift in Sources */,
964979
);
965980
runOnlyForDeploymentPostprocessing = 0;
966981
};
@@ -1013,6 +1028,7 @@
10131028
19A174D78559CD30679BCCCB /* FTS5Tests.swift in Sources */,
10141029
19A178072B371489E6A1E839 /* FoundationTests.swift in Sources */,
10151030
19A17C4B951CB054EE48AB1C /* CipherTests.swift in Sources */,
1031+
19A1709C3E7A406E62293B2A /* Fixtures.swift in Sources */,
10161032
);
10171033
runOnlyForDeploymentPostprocessing = 0;
10181034
};

SQLite/Extensions/Cipher.swift

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,64 @@
11
#if SQLITE_SWIFT_SQLCIPHER
22
import SQLCipher
33

4+
5+
/// Extension methods for [SQLCipher](https://www.zetetic.net/sqlcipher/).
6+
/// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/)
47
extension Connection {
5-
public func key(_ key: String) throws {
6-
try _key(keyPointer: key, keySize: key.utf8.count)
8+
9+
/// Specify the key for an encrypted database. This routine should be
10+
/// called right after sqlite3_open().
11+
///
12+
/// @param key The key to use.The key itself can be a passphrase, which is converted to a key
13+
/// using [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) key derivation. The result
14+
/// is used as the encryption key for the database.
15+
///
16+
/// Alternatively, it is possible to specify an exact byte sequence using a blob literal.
17+
/// With this method, it is the calling application's responsibility to ensure that the data
18+
/// provided is a 64 character hex string, which will be converted directly to 32 bytes (256 bits)
19+
/// of key data.
20+
/// e.g. x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'
21+
/// @param db name of the database, defaults to 'main'
22+
public func key(_ key: String, db: String = "main") throws {
23+
try _key_v2(db: db, keyPointer: key, keySize: key.utf8.count)
724
}
825

9-
public func key(_ key: Blob) throws {
10-
try _key(keyPointer: key.bytes, keySize: key.bytes.count)
26+
public func key(_ key: Blob, db: String = "main") throws {
27+
try _key_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
1128
}
1229

13-
public func rekey(_ key: String) throws {
14-
try _rekey(keyPointer: key, keySize: key.utf8.count)
30+
31+
/// Change the key on an open database. If the current database is not encrypted, this routine
32+
/// will encrypt it.
33+
/// To change the key on an existing encrypted database, it must first be unlocked with the
34+
/// current encryption key. Once the database is readable and writeable, rekey can be used
35+
/// to re-encrypt every page in the database with a new key.
36+
public func rekey(_ key: String, db: String = "main") throws {
37+
try _rekey_v2(db: db, keyPointer: key, keySize: key.utf8.count)
1538
}
1639

17-
public func rekey(_ key: Blob) throws {
18-
try _rekey(keyPointer: key.bytes, keySize: key.bytes.count)
40+
public func rekey(_ key: Blob, db: String = "main") throws {
41+
try _rekey_v2(db: db, keyPointer: key.bytes, keySize: key.bytes.count)
1942
}
2043

2144
// MARK: - private
22-
private func _key(keyPointer: UnsafePointer<UInt8>, keySize: Int) throws {
23-
try check(sqlite3_key(handle, keyPointer, Int32(keySize)))
45+
private func _key_v2(db: String, keyPointer: UnsafePointer<UInt8>, keySize: Int) throws {
46+
try check(sqlite3_key_v2(handle, db, keyPointer, Int32(keySize)))
47+
try cipher_key_check()
48+
}
49+
50+
private func _rekey_v2(db: String, keyPointer: UnsafePointer<UInt8>, keySize: Int) throws {
51+
try check(sqlite3_rekey_v2(handle, db, keyPointer, Int32(keySize)))
52+
}
53+
54+
// When opening an existing database, sqlite3_key_v2 will not immediately throw an error if
55+
// the key provided is incorrect. To test that the database can be successfully opened with the
56+
// provided key, it is necessary to perform some operation on the database (i.e. read from it).
57+
private func cipher_key_check() throws {
2458
try execute(
2559
"CREATE TABLE \"__SQLCipher.swift__\" (\"cipher key check\");\n" +
2660
"DROP TABLE \"__SQLCipher.swift__\";"
2761
)
2862
}
29-
30-
private func _rekey(keyPointer: UnsafePointer<UInt8>, keySize: Int) throws {
31-
try check(sqlite3_rekey(handle, keyPointer, Int32(keySize)))
32-
}
3363
}
3464
#endif

SQLiteTests/CipherTests.swift

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,20 @@ import SQLCipher
55

66
class CipherTests: XCTestCase {
77

8-
let db = try! Connection()
8+
let db1 = try! Connection()
99
let db2 = try! Connection()
1010

1111
override func setUp() {
1212
// db
13-
try! db.key("hello")
1413

15-
try! db.run("CREATE TABLE foo (bar TEXT)")
16-
try! db.run("INSERT INTO foo (bar) VALUES ('world')")
14+
try! db1.key("hello")
15+
16+
try! db1.run("CREATE TABLE foo (bar TEXT)")
17+
try! db1.run("INSERT INTO foo (bar) VALUES ('world')")
1718

1819
// db2
19-
let keyData = NSMutableData(length: 64)!
20-
let _ = SecRandomCopyBytes(kSecRandomDefault, 64,
21-
keyData.mutableBytes.assumingMemoryBound(to: UInt8.self))
22-
try! db2.key(Blob(bytes: keyData.bytes, length: keyData.length))
20+
let key2 = keyData()
21+
try! db2.key(Blob(bytes: key2.bytes, length: key2.length))
2322

2423
try! db2.run("CREATE TABLE foo (bar TEXT)")
2524
try! db2.run("INSERT INTO foo (bar) VALUES ('world')")
@@ -28,24 +27,26 @@ class CipherTests: XCTestCase {
2827
}
2928

3029
func test_key() {
31-
XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as? Int64)
30+
XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64)
31+
}
32+
33+
func test_key_blob_literal() {
34+
let db = try! Connection()
35+
try! db.key("x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'")
3236
}
3337

3438
func test_rekey() {
35-
try! db.rekey("goodbye")
36-
XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as? Int64)
39+
try! db1.rekey("goodbye")
40+
XCTAssertEqual(1, try! db1.scalar("SELECT count(*) FROM foo") as? Int64)
3741
}
3842

3943
func test_data_key() {
4044
XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64)
4145
}
4246

4347
func test_data_rekey() {
44-
let keyData = NSMutableData(length: 64)!
45-
let _ = SecRandomCopyBytes(kSecRandomDefault, 64,
46-
keyData.mutableBytes.assumingMemoryBound(to: UInt8.self))
47-
48-
try! db2.rekey(Blob(bytes: keyData.bytes, length: keyData.length))
48+
let newKey = keyData()
49+
try! db2.rekey(Blob(bytes: newKey.bytes, length: newKey.length))
4950
XCTAssertEqual(1, try! db2.scalar("SELECT count(*) FROM foo") as? Int64)
5051
}
5152

@@ -70,5 +71,24 @@ class CipherTests: XCTestCase {
7071
}
7172
XCTAssertEqual(SQLITE_NOTADB, rc)
7273
}
74+
75+
func test_open_db_encrypted_with_sqlcipher() {
76+
// $ sqlcipher SQLiteTests/fixtures/encrypted.sqlite
77+
// sqlite> pragma key = 'sqlcipher-test';
78+
// sqlite> CREATE TABLE foo (bar TEXT);
79+
// sqlite> INSERT INTO foo (bar) VALUES ('world');
80+
let encryptedFile = fixture("encrypted", withExtension: "sqlite")
81+
let conn = try! Connection(encryptedFile)
82+
try! conn.key("sqlcipher-test")
83+
XCTAssertEqual(1, try! conn.scalar("SELECT count(*) FROM foo") as? Int64)
84+
}
85+
86+
private func keyData(length: Int = 64) -> NSMutableData {
87+
let keyData = NSMutableData(length: length)!
88+
let result = SecRandomCopyBytes(kSecRandomDefault, length,
89+
keyData.mutableBytes.assumingMemoryBound(to: UInt8.self))
90+
XCTAssertEqual(0, result)
91+
return keyData
92+
}
7393
}
7494
#endif

SQLiteTests/Fixtures.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Foundation
2+
3+
func fixture(_ name: String, withExtension: String?) -> String {
4+
let testBundle = Bundle(for: SQLiteTestCase.self)
5+
return testBundle.url(
6+
forResource: URL(string: "fixtures")?.appendingPathComponent(name).path,
7+
withExtension: withExtension)!.path
8+
}
2 KB
Binary file not shown.

0 commit comments

Comments
 (0)