Better SQLCipher key logic
This commit is contained in:
parent
f5cc39f256
commit
4747993046
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue