* use sqlcipher build, hardcoded encryption key * UI for db encryption * database passphrase UI * show orange icon when database is not encrypted * call encrypt * more ios ux * basic UX for passphrase complete * with animation Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> * passphrase complexity, fixes * fix moving entry field Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
108 lines
3.3 KiB
Swift
108 lines
3.3 KiB
Swift
//
|
|
// KeyChain.swift
|
|
// SimpleXChat
|
|
//
|
|
// Created by Evgeny on 04/09/2022.
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Security
|
|
|
|
private let ACCESS_POLICY: CFString = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
|
|
private let ACCESS_GROUP: String = "5NN7GUYB6T.chat.simplex.app"
|
|
private let DATABASE_PASSWORD_ITEM: String = "databasePassword"
|
|
|
|
public func getDatabaseKey() -> String? {
|
|
getItemString(forKey: DATABASE_PASSWORD_ITEM)
|
|
}
|
|
|
|
public func setDatabaseKey(_ key: String) -> Bool {
|
|
setItemString(key, forKey: DATABASE_PASSWORD_ITEM)
|
|
}
|
|
|
|
public func removeDatabaseKey() -> Bool {
|
|
deleteItem(forKey: DATABASE_PASSWORD_ITEM)
|
|
}
|
|
|
|
func randomDatabasePassword() -> String {
|
|
var keyData = Data(count: 32)
|
|
let status = keyData.withUnsafeMutableBytes {
|
|
SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!)
|
|
}
|
|
if status == errSecSuccess {
|
|
return keyData.base64EncodedString()
|
|
} else {
|
|
logger.error("randomDatabasePassword: error \(status)")
|
|
return ""
|
|
}
|
|
}
|
|
|
|
private func getItemData(forKey key: String) -> Data? {
|
|
var query = baseItemQuery(forKey: key)
|
|
query[kSecMatchLimit] = kSecMatchLimitOne
|
|
query[kSecReturnData] = true as AnyObject?
|
|
|
|
var dataRef: CFTypeRef?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &dataRef)
|
|
if status != errSecSuccess && status != errSecItemNotFound {
|
|
logger.error("getItemData: error getting data for key '\(key)', error: \(status)")
|
|
}
|
|
return dataRef as? Data
|
|
}
|
|
|
|
private func getItemString(forKey key: String) -> String? {
|
|
if let data = getItemData(forKey: key) {
|
|
return NSString(data: data, encoding: String.Encoding.utf8.rawValue) as? String
|
|
}
|
|
return nil
|
|
}
|
|
|
|
private func setItemData(_ data: Data, forKey key: String) -> Bool {
|
|
var query = baseItemQuery(forKey: key)
|
|
var update = [NSString : AnyObject]()
|
|
update[kSecValueData] = data as AnyObject?
|
|
update[kSecAttrAccessible] = ACCESS_POLICY
|
|
var status: OSStatus
|
|
if getItemData(forKey: key) == nil {
|
|
for (key, value) in update { query[key] = value }
|
|
status = SecItemAdd(query as CFDictionary, nil)
|
|
} else {
|
|
status = SecItemUpdate(query as CFDictionary, update as CFDictionary)
|
|
}
|
|
if status != errSecSuccess {
|
|
logger.error("setItemData: error setting data for key '\(key)', error: \(status)")
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
private func setItemString(_ s: String, forKey key: String) -> Bool {
|
|
if let data = s.data(using: .utf8) {
|
|
return setItemData(data, forKey: key)
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func deleteItem(forKey key: String) -> Bool {
|
|
let query = baseItemQuery(forKey: key)
|
|
if getItemData(forKey: key) != nil {
|
|
let status = SecItemDelete(query as CFDictionary)
|
|
if status != errSecSuccess {
|
|
logger.error("deleteItem: error deleting data for key '\(key)', error: \(status)")
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
private func baseItemQuery(forKey key: String) -> [NSString : AnyObject] {
|
|
var query = [NSString : AnyObject]()
|
|
query[kSecClass] = kSecClassGenericPassword
|
|
query[kSecAttrAccount] = key as AnyObject?
|
|
#if TARGET_OS_IOS && !TARGET_OS_SIMULATOR
|
|
query[kSecAttrAccessGroup] = ACCESS_GROUP
|
|
#endif
|
|
return query
|
|
}
|