183 lines
5.7 KiB
Swift
183 lines
5.7 KiB
Swift
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
import Foundation
|
|
|
|
private struct Constants {
|
|
static let ActiveAccountDataService = "org.zoxcore.Antidote.KeychainManager.ActiveAccountDataService"
|
|
|
|
static let toxPasswordForActiveAccount = "toxPasswordForActiveAccount"
|
|
static let failedPinAttemptsNumber = "failedPinAttemptsNumber"
|
|
}
|
|
|
|
class KeychainManager {
|
|
/// Tox password used to encrypt/decrypt active account.
|
|
var toxPasswordForActiveAccount: String? {
|
|
get {
|
|
return getStringForKey(Constants.toxPasswordForActiveAccount)
|
|
}
|
|
set {
|
|
setString(newValue, forKey: Constants.toxPasswordForActiveAccount)
|
|
}
|
|
}
|
|
|
|
/// Number of failed enters of pin by user.
|
|
var failedPinAttemptsNumber: Int? {
|
|
get {
|
|
return getIntForKey(Constants.failedPinAttemptsNumber)
|
|
}
|
|
set {
|
|
setInt(newValue, forKey: Constants.failedPinAttemptsNumber)
|
|
}
|
|
}
|
|
|
|
/// Removes all data related to active account.
|
|
func deleteActiveAccountData() {
|
|
toxPasswordForActiveAccount = nil
|
|
failedPinAttemptsNumber = nil
|
|
}
|
|
}
|
|
|
|
private extension KeychainManager {
|
|
func getIntForKey(_ key: String) -> Int? {
|
|
guard let data = getDataForKey(key) else {
|
|
return nil
|
|
}
|
|
|
|
guard let number = NSKeyedUnarchiver.unarchiveObject(with: data) as? NSNumber else {
|
|
return nil
|
|
}
|
|
|
|
return number.intValue
|
|
}
|
|
|
|
func setInt(_ value: Int?, forKey key: String) {
|
|
guard let value = value else {
|
|
setData(nil, forKey: key)
|
|
return
|
|
}
|
|
|
|
let number = NSNumber(value: value)
|
|
|
|
let data = NSKeyedArchiver.archivedData(withRootObject: number)
|
|
setData(data, forKey: key)
|
|
}
|
|
|
|
func getStringForKey(_ key: String) -> String? {
|
|
guard let data = getDataForKey(key) else {
|
|
return nil
|
|
}
|
|
|
|
return NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String?
|
|
}
|
|
|
|
func setString(_ string: String?, forKey key: String) {
|
|
let data = string?.data(using: String.Encoding.utf8)
|
|
setData(data, forKey: key)
|
|
}
|
|
|
|
func getBoolForKey(_ key: String) -> Bool? {
|
|
guard let data = getDataForKey(key) else {
|
|
return nil
|
|
}
|
|
|
|
return (data as NSData).bytes.bindMemory(to: Int.self, capacity: data.count).pointee == 1
|
|
}
|
|
|
|
func setBool(_ value: Bool?, forKey key: String) {
|
|
var data: Data? = nil
|
|
|
|
if let value = value {
|
|
var bytes = value ? 1 : 0
|
|
withUnsafePointer(to: &bytes) {
|
|
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
|
|
data = Data(bytes: $0, count: MemoryLayout<Int>.size)
|
|
}
|
|
}
|
|
}
|
|
|
|
setData(data, forKey: key)
|
|
}
|
|
|
|
func getDataForKey(_ key: String) -> Data? {
|
|
var query = genericQueryWithKey(key)
|
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
query[kSecReturnData as String] = kCFBooleanTrue
|
|
|
|
var queryResult: AnyObject?
|
|
let status = withUnsafeMutablePointer(to: &queryResult) {
|
|
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
|
|
}
|
|
|
|
if status == errSecItemNotFound {
|
|
return nil
|
|
}
|
|
|
|
guard status == noErr else {
|
|
log("Error when getting keychain data for key \(key), status \(status)")
|
|
return nil
|
|
}
|
|
|
|
guard let data = queryResult as? Data else {
|
|
log("Unexpected data for key \(key)")
|
|
return nil
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func setData(_ newData: Data?, forKey key: String) {
|
|
let oldData = getDataForKey(key)
|
|
|
|
switch (oldData, newData) {
|
|
case (.some(_), .some(let data)):
|
|
// Update
|
|
let query = genericQueryWithKey(key)
|
|
|
|
var attributesToUpdate = [String : AnyObject]()
|
|
attributesToUpdate[kSecValueData as String] = data as AnyObject?
|
|
|
|
let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
|
|
guard status == noErr else {
|
|
log("Error when updating keychain data for key \(key), status \(status)")
|
|
return
|
|
}
|
|
|
|
case (.some(_), .none):
|
|
// Delete
|
|
let query = genericQueryWithKey(key)
|
|
let status = SecItemDelete(query as CFDictionary)
|
|
guard status == noErr else {
|
|
log("Error when updating keychain data for key \(key), status \(status)")
|
|
return
|
|
}
|
|
|
|
case (.none, .some(let data)):
|
|
// Add
|
|
var query = genericQueryWithKey(key)
|
|
query[kSecValueData as String] = data as AnyObject?
|
|
|
|
let status = SecItemAdd(query as CFDictionary, nil)
|
|
guard status == noErr else {
|
|
log("Error when setting keychain data for key \(key), status \(status)")
|
|
return
|
|
}
|
|
|
|
case (.none, .none):
|
|
// Nothing to do here, no changes
|
|
break
|
|
}
|
|
}
|
|
|
|
func genericQueryWithKey(_ key: String) -> [String : AnyObject] {
|
|
var query = [String : AnyObject]()
|
|
query[kSecClass as String] = kSecClassGenericPassword
|
|
query[kSecAttrService as String] = Constants.ActiveAccountDataService as AnyObject?
|
|
query[kSecAttrAccount as String] = key as AnyObject?
|
|
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
|
|
return query
|
|
}
|
|
}
|