Better SQLCipher key logic

This commit is contained in:
Justin Mazzocchi 2020-09-04 02:44:25 -07:00
parent f5cc39f256
commit 4747993046
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
3 changed files with 34 additions and 23 deletions

View file

@ -17,9 +17,8 @@ public struct ContentDatabase {
let path = try Self.fileURL(identityID: identityID).path let path = try Self.fileURL(identityID: identityID).path
var configuration = Configuration() var configuration = Configuration()
configuration.prepareDatabase = { db in configuration.prepareDatabase = {
let passphrase = try Secrets.databasePassphrase(identityID: identityID, keychain: keychain) try $0.usePassphrase(try Secrets.databaseKey(identityID: identityID, keychain: keychain))
try db.usePassphrase(passphrase)
} }
databaseQueue = try DatabaseQueue(path: path, configuration: configuration) databaseQueue = try DatabaseQueue(path: path, configuration: configuration)

View file

@ -21,9 +21,8 @@ public struct IdentityDatabase {
let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path let path = try FileManager.default.databaseDirectoryURL(name: Self.name).path
var configuration = Configuration() var configuration = Configuration()
configuration.prepareDatabase = { db in configuration.prepareDatabase = {
let passphrase = try Secrets.databasePassphrase(identityID: nil, keychain: keychain) try $0.usePassphrase(try Secrets.databaseKey(identityID: nil, keychain: keychain))
try db.usePassphrase(passphrase)
} }
databaseQueue = try DatabaseQueue(path: path, configuration: configuration) databaseQueue = try DatabaseQueue(path: path, configuration: configuration)

View file

@ -29,11 +29,11 @@ public extension Secrets {
case accessToken case accessToken
case pushKey case pushKey
case pushAuth case pushAuth
case databasePassphrase case databaseKey
} }
} }
public enum SecretsError: Error { enum SecretsError: Error {
case itemAbsent case itemAbsent
} }
@ -43,6 +43,7 @@ extension Secrets.Item {
case key case key
} }
// Note `databaseKey` is a generic password and not a key
var kind: Kind { var kind: Kind {
switch self { switch self {
case .pushKey: return .key case .pushKey: return .key
@ -52,7 +53,9 @@ extension Secrets.Item {
} }
public extension Secrets { public extension Secrets {
static func databasePassphrase(identityID: UUID?, keychain: Keychain.Type) throws -> String { // https://www.zetetic.net/sqlcipher/sqlcipher-api/#key
static func databaseKey(identityID: UUID?, keychain: Keychain.Type) throws -> String {
let passphraseData: Data
let scopedSecrets: Secrets? let scopedSecrets: Secrets?
if let identityID = identityID { if let identityID = identityID {
@ -62,25 +65,26 @@ public extension Secrets {
} }
do { do {
return try scopedSecrets?.item(.databasePassphrase) ?? unscopedItem(.databasePassphrase, keychain: keychain) passphraseData = try scopedSecrets?.item(.databaseKey)
?? unscopedItem(.databaseKey, keychain: keychain)
} catch SecretsError.itemAbsent { } catch SecretsError.itemAbsent {
var bytes = [Int8](repeating: 0, count: databasePassphraseByteCount) var bytes = [UInt8](repeating: 0, count: databaseKeyLength)
let status = SecRandomCopyBytes(kSecRandomDefault, databasePassphraseByteCount, &bytes) let status = SecRandomCopyBytes(kSecRandomDefault, databaseKeyLength, &bytes)
if status == errSecSuccess { if status == errSecSuccess {
let passphrase = Data(bytes: bytes, count: databasePassphraseByteCount).base64EncodedString() passphraseData = Data(bytes)
if let scopedSecrets = scopedSecrets { if let scopedSecrets = scopedSecrets {
try scopedSecrets.set(passphrase, forItem: .databasePassphrase) try scopedSecrets.set(passphraseData, forItem: .databaseKey)
} else { } else {
try setUnscoped(passphrase, forItem: .databasePassphrase, keychain: keychain) try setUnscoped(passphraseData, forItem: .databaseKey, keychain: keychain)
} }
return passphrase
} else { } else {
throw NSError(status: status) throw NSError(status: status)
} }
} }
return "x'\(passphraseData.uppercaseHexEncodedString())'"
} }
func deleteAllItems() throws { func deleteAllItems() throws {
@ -134,14 +138,17 @@ public extension Secrets {
func generatePushAuth() throws -> Data { func generatePushAuth() throws -> Data {
var bytes = [UInt8](repeating: 0, count: PushKey.authLength) var bytes = [UInt8](repeating: 0, count: PushKey.authLength)
let status = SecRandomCopyBytes(kSecRandomDefault, PushKey.authLength, &bytes)
_ = SecRandomCopyBytes(kSecRandomDefault, PushKey.authLength, &bytes) if status == errSecSuccess {
let pushAuth = Data(bytes)
let pushAuth = Data(bytes) try set(pushAuth, forItem: .pushAuth)
try set(pushAuth, forItem: .pushAuth) return pushAuth
} else {
return pushAuth throw NSError(status: status)
}
} }
func getPushAuth() throws -> Data? { func getPushAuth() throws -> Data? {
@ -151,7 +158,7 @@ public extension Secrets {
private extension Secrets { private extension Secrets {
static let keychainServiceName = "com.metabolist.metatext" static let keychainServiceName = "com.metabolist.metatext"
static let databasePassphraseByteCount = 64 static let databaseKeyLength = 32
private static func set(_ data: SecretsStorable, forAccount account: String, keychain: Keychain.Type) throws { private static func set(_ data: SecretsStorable, forAccount account: String, keychain: Keychain.Type) throws {
try keychain.setGenericPassword( try keychain.setGenericPassword(
@ -218,3 +225,9 @@ private struct PushKey {
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: sizeInBits] kSecAttrKeySizeInBits as String: sizeInBits]
} }
private extension Data {
func uppercaseHexEncodedString() -> String {
map { String(format: "%02hhX", $0) }.joined()
}
}