SimpleX-Chat/apps/ios/SimpleXChat/KeyChain.swift
Evgeny Poberezkin 0b8d9d11e2
core, iOS: support for self-destruct password (#2412)
* core, iOS: support for self-destruct password

* disable test logging

* core: fix tests, iOS: remove notifications on removal

* change alerts
2023-05-09 09:33:30 +01:00

119 lines
3.7 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"
private let APP_PASSWORD_ITEM: String = "appPassword"
private let SELF_DESTRUCT_PASSWORD_ITEM: String = "selfDestructPassword"
public let kcDatabasePassword = KeyChainItem(forKey: DATABASE_PASSWORD_ITEM)
public let kcAppPassword = KeyChainItem(forKey: APP_PASSWORD_ITEM)
public let kcSelfDestructPassword = KeyChainItem(forKey: SELF_DESTRUCT_PASSWORD_ITEM)
public struct KeyChainItem {
var forKey: String
public func get() -> String? {
getItemString(forKey: forKey)
}
public func set(_ value: String) -> Bool {
setItemString(value, forKey: forKey)
}
public func remove() -> Bool {
deleteItem(forKey: forKey)
}
}
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
}