Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ bin/
.build
Packages/
.swiftpm/
Package.resolved
106 changes: 73 additions & 33 deletions Documentation/Index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
- [SQLite.swift Documentation](#sqliteswift-documentation)
- [Installation](#installation)
- [Swift Package Manager](#swift-package-manager)
- [Using SQLite.swift with SQLCipher](#using-sqliteswift-with-sqlcipher)
- [Carthage](#carthage)
- [CocoaPods](#cocoapods)
- [Requiring a specific version of SQLite](#requiring-a-specific-version-of-sqlite)
- [Using SQLite.swift with SQLCipher](#using-sqliteswift-with-sqlcipher)
- [Manual](#manual)
- [Getting Started](#getting-started)
- [Connecting to a Database](#connecting-to-a-database)
Expand Down Expand Up @@ -119,7 +119,79 @@ process of downloading, compiling, and linking dependencies.
$ swift build
```

#### Using SQLite.swift with SQLCipher

If you want to use [SQLCipher][] with SQLite.swift you can specify the `SQLCipher` trait when consuming SQLite.swift.

```swift
depdencies: [
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.15.4", traits: ["SQLCipher"])
]
```

As of Xcode 16.4 (16F6), there's no direct way in the Xcode UI to select trait variations so you'll need to use a local wrapper package to pull in the SQLite.swift dependency with the `SQLCipher` trait enabled:

```swift
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "AppDependencies",
platforms: [
.macOS(.v10_14),
.iOS(.v13),
.macCatalyst(.v13),
.watchOS(.v8),
.tvOS(.v15),
.visionOS(.v1)
],
products: [
.library(
name: "AppDependencies",
targets: ["AppDependencies"]),
],
dependencies: [
.package(
url: "https://github.com/stephencelis/SQLite.swift.git",
from: "0.15.4",
traits: ["SQLCipher"])
],
targets: [
.target(
name: "AppDependencies",
dependencies: [
.product(
name: "SQLite",
package: "SQLite.swift")
]
)
]
)
```

Within Xcode add your local `AppDependencies` wrapper package as a package dependency and SQLite.swift with SQLCipher functionality will be accessible.

Using the `SQLCipher` trait will cause SQLite.swift to include a dependency on SQLCipher.swift and enable `Connection` methods to set and change the database key:

```swift
import SQLite

let db = try Connection("path/to/encrypted.sqlite3")
try db.key("secret")
try db.rekey("new secret") // changes encryption key on already encrypted db
```

To encrypt an existing database:

```swift
let db = try Connection("path/to/unencrypted.sqlite3")
try db.sqlcipher_export(.uri("encrypted.sqlite3"), key: "secret")
```

[Swift Package Manager]: https://swift.org/package-manager
[SQLCipher]: https://www.zetetic.net/sqlcipher/

### Carthage

Expand Down Expand Up @@ -191,41 +263,9 @@ end

See the [sqlite3 podspec][sqlite3pod] for more details.

#### Using SQLite.swift with SQLCipher

If you want to use [SQLCipher][] with SQLite.swift you can require the
`SQLCipher` subspec in your Podfile (SPM is not supported yet, see [#1084](https://github.com/stephencelis/SQLite.swift/issues/1084)):

```ruby
target 'YourAppTargetName' do
# Make sure you only require the subspec, otherwise you app might link against
# the system SQLite, which means the SQLCipher-specific methods won't work.
pod 'SQLite.swift/SQLCipher', '~> 0.15.4'
end
```

This will automatically add a dependency to the SQLCipher pod as well as
extend `Connection` with methods to change the database key:

```swift
import SQLite

let db = try Connection("path/to/encrypted.sqlite3")
try db.key("secret")
try db.rekey("new secret") // changes encryption key on already encrypted db
```

To encrypt an existing database:

```swift
let db = try Connection("path/to/unencrypted.sqlite3")
try db.sqlcipher_export(.uri("encrypted.sqlite3"), key: "secret")
```

[CocoaPods]: https://cocoapods.org
[CocoaPods Installation]: https://guides.cocoapods.org/using/getting-started.html#getting-started
[sqlite3pod]: https://github.com/clemensg/sqlite3pod
[SQLCipher]: https://www.zetetic.net/sqlcipher/

### Manual

Expand Down
34 changes: 30 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
// swift-tools-version:5.9
// swift-tools-version: 6.1
import PackageDescription

let deps: [Package.Dependency] = [
.github("swiftlang/swift-toolchain-sqlite", exact: "1.0.4")
.github("swiftlang/swift-toolchain-sqlite", exact: "1.0.4"),
.github("sqlcipher/SQLCipher.swift.git", from: "4.11.0")
]

let applePlatforms: [PackageDescription.Platform] = [.iOS, .macOS, .watchOS, .tvOS, .visionOS]

let sqlcipherTraitTargetCondition: TargetDependencyCondition? = .when(platforms: applePlatforms, traits: ["SQLCipher"])

let sqlcipherTraitBuildSettingCondition: BuildSettingCondition? = .when(platforms: applePlatforms, traits: ["SQLCipher"])

let targets: [Target] = [
.target(
name: "SQLite",
dependencies: [
.product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows, .android]))
.product(name: "SwiftToolchainCSQLite", package: "swift-toolchain-sqlite", condition: .when(platforms: [.linux, .windows, .android])),
.product(name: "SQLCipher", package: "SQLCipher.swift", condition: sqlcipherTraitTargetCondition)
],
exclude: [
"Info.plist"
],
cSettings: [
.define("SQLITE_HAS_CODEC", to: nil, sqlcipherTraitBuildSettingCondition)
],
swiftSettings: [
.define("SQLITE_HAS_CODEC", sqlcipherTraitBuildSettingCondition),
.define("SQLITE_SWIFT_SQLCIPHER", sqlcipherTraitBuildSettingCondition)
]
)
]
Expand All @@ -29,6 +44,9 @@ let testTargets: [Target] = [
],
resources: [
.copy("Resources")
],
swiftSettings: [
.define("SQLITE_SWIFT_SQLCIPHER", sqlcipherTraitBuildSettingCondition)
]
)
]
Expand All @@ -48,13 +66,21 @@ let package = Package(
targets: ["SQLite"]
)
],
traits: [
.trait(name: "SQLCipher", description: "Enables SQLCipher encryption when a key is supplied to Connection")
],
dependencies: deps,
targets: targets + testTargets
targets: targets + testTargets,
swiftLanguageModes: [.v5],
)

extension Package.Dependency {

static func github(_ repo: String, exact ver: Version) -> Package.Dependency {
.package(url: "https://github.com/\(repo)", exact: ver)
}

static func github(_ repo: String, from ver: Version) -> Package.Dependency {
.package(url: "https://github.com/\(repo)", from: ver)
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ syntax _and_ intent.
- [Full-text search][] support
- [Well-documented][See Documentation]
- Extensively tested
- [SQLCipher][] support via CocoaPods
- [SQLCipher][] support via Swift Package Manager
- [Schema query/migration][]
- Works on [Linux](Documentation/Linux.md) (with some limitations)
- Active support at
Expand Down
73 changes: 73 additions & 0 deletions Sources/SQLite/Extensions/Cipher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,49 @@ import SQLCipher
/// @see [sqlcipher api](https://www.zetetic.net/sqlcipher/sqlcipher-api/)
extension Connection {

/// 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? {
(try? scalar("PRAGMA cipher_version")) as? String
}

/// - 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? {
(try? scalar("PRAGMA cipher_fips_status")) as? String
}

/// - 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? {
(try? scalar("PRAGMA cipher_provider")) as? String
}

/// - 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? {
(try? scalar("PRAGMA cipher_provider_version")) as? String
}

/// Specify the key for an encrypted database. This routine should be
/// called right after sqlite3_open().
///
Expand Down Expand Up @@ -82,6 +118,43 @@ extension Connection {
try detach(schemaName)
}

/// 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/
///
/// 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 run("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 run("PRAGMA cipher_log = device")
try run("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 run("PRAGMA cipher_log_level = \(CipherLogLevel.none.rawValue.uppercased())")
}

// MARK: - private
private func _key_v2(db: String,
keyPointer: UnsafePointer<UInt8>,
Expand Down
16 changes: 15 additions & 1 deletion Tests/SQLiteTests/Extensions/CipherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,26 @@ class CipherTests: XCTestCase {
XCTAssertEqual(1, try conn.scalar("SELECT count(*) FROM foo") as? Int64)
}

func test_cipher_provider() throws {
XCTAssertEqual("commoncrypto", db1.cipherProvider)
}

func test_cipher_provider_version() throws {
XCTAssertNotNil(db1.cipherProviderVersion)
}

func test_cipher_fips_status() throws {
let fipsStatusString = db1.cipherFipsStatus
XCTAssertNotNil(fipsStatusString)
XCTAssertEqual(0, Int(fipsStatusString!))
}

private func keyData(length: Int = 64) -> NSData {
let keyData = NSMutableData(length: length)!
let result = SecRandomCopyBytes(kSecRandomDefault, length,
keyData.mutableBytes.assumingMemoryBound(to: UInt8.self))
XCTAssertEqual(0, result)
return NSData(data: keyData)
return NSData(data: keyData as Data)
}
}
#endif