2020-07-29 23:50:30 +00:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
2020-08-12 09:01:21 +00:00
|
|
|
protocol KeychainService {
|
2020-08-12 07:24:39 +00:00
|
|
|
static func setGenericPassword(data: Data, forAccount key: String, service: String) throws
|
|
|
|
static func deleteGenericPassword(account: String, service: String) throws
|
|
|
|
static func getGenericPassword(account: String, service: String) throws -> Data?
|
2020-08-13 10:18:21 +00:00
|
|
|
static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data
|
|
|
|
static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data?
|
2020-08-14 01:59:17 +00:00
|
|
|
static func deleteKey(applicationTag: String) throws
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
|
2020-08-12 09:01:21 +00:00
|
|
|
struct LiveKeychainService {}
|
2020-07-29 23:50:30 +00:00
|
|
|
|
2020-08-12 09:01:21 +00:00
|
|
|
extension LiveKeychainService: KeychainService {
|
2020-08-12 07:24:39 +00:00
|
|
|
static func setGenericPassword(data: Data, forAccount account: String, service: String) throws {
|
|
|
|
var query = genericPasswordQueryDictionary(account: account, service: service)
|
2020-07-29 23:50:30 +00:00
|
|
|
|
|
|
|
query[kSecValueData as String] = data
|
|
|
|
|
|
|
|
let status = SecItemAdd(query as CFDictionary, nil)
|
|
|
|
|
|
|
|
if status != errSecSuccess {
|
|
|
|
throw NSError(status: status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 07:24:39 +00:00
|
|
|
static func deleteGenericPassword(account: String, service: String) throws {
|
|
|
|
let status = SecItemDelete(genericPasswordQueryDictionary(account: account, service: service) as CFDictionary)
|
2020-07-29 23:50:30 +00:00
|
|
|
|
|
|
|
if status != errSecSuccess {
|
|
|
|
throw NSError(status: status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-12 07:24:39 +00:00
|
|
|
static func getGenericPassword(account: String, service: String) throws -> Data? {
|
2020-07-29 23:50:30 +00:00
|
|
|
var result: AnyObject?
|
2020-08-12 07:24:39 +00:00
|
|
|
var query = genericPasswordQueryDictionary(account: account, service: service)
|
2020-07-29 23:50:30 +00:00
|
|
|
|
|
|
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
|
|
query[kSecReturnData as String] = kCFBooleanTrue
|
|
|
|
|
|
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
|
|
|
|
|
|
switch status {
|
|
|
|
case errSecSuccess:
|
|
|
|
return result as? Data
|
|
|
|
case errSecItemNotFound:
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
throw NSError(status: status)
|
|
|
|
}
|
|
|
|
}
|
2020-08-12 07:24:39 +00:00
|
|
|
|
2020-08-13 10:18:21 +00:00
|
|
|
static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data {
|
|
|
|
var attributes = attributes
|
2020-08-12 07:24:39 +00:00
|
|
|
var error: Unmanaged<CFError>?
|
|
|
|
|
2020-08-13 10:18:21 +00:00
|
|
|
guard let accessControl = SecAccessControlCreateWithFlags(
|
|
|
|
kCFAllocatorDefault,
|
|
|
|
kSecAttrAccessibleAfterFirstUnlock,
|
|
|
|
[],
|
|
|
|
&error)
|
|
|
|
else { throw error?.takeRetainedValue() ?? NSError() }
|
|
|
|
|
2020-08-12 07:24:39 +00:00
|
|
|
attributes[kSecPrivateKeyAttrs as String] = [
|
|
|
|
kSecAttrIsPermanent as String: true,
|
2020-08-13 10:18:21 +00:00
|
|
|
kSecAttrApplicationTag as String: Data(applicationTag.utf8),
|
|
|
|
kSecAttrAccessControl as String: accessControl]
|
2020-08-12 07:24:39 +00:00
|
|
|
|
|
|
|
guard
|
|
|
|
let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error),
|
|
|
|
let publicKey = SecKeyCopyPublicKey(key),
|
|
|
|
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data?
|
|
|
|
else { throw error?.takeRetainedValue() ?? NSError() }
|
|
|
|
|
|
|
|
return publicKeyData
|
|
|
|
}
|
|
|
|
|
2020-08-13 10:18:21 +00:00
|
|
|
static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? {
|
2020-08-12 07:24:39 +00:00
|
|
|
var result: AnyObject?
|
2020-08-13 10:18:21 +00:00
|
|
|
var error: Unmanaged<CFError>?
|
|
|
|
var query = keyQueryDictionary(applicationTag: applicationTag)
|
|
|
|
|
|
|
|
query.merge(attributes, uniquingKeysWith: { $1 })
|
|
|
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
|
|
query[kSecReturnRef as String] = kCFBooleanTrue
|
|
|
|
|
|
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
2020-08-12 07:24:39 +00:00
|
|
|
|
|
|
|
switch status {
|
|
|
|
case errSecSuccess:
|
2020-08-13 10:18:21 +00:00
|
|
|
// swiftlint:disable force_cast
|
|
|
|
let secKey = result as! SecKey
|
|
|
|
// swiftlint:enable force_cast
|
|
|
|
guard let data = SecKeyCopyExternalRepresentation(secKey, &error) else {
|
|
|
|
throw error?.takeRetainedValue() ?? NSError()
|
|
|
|
}
|
|
|
|
|
|
|
|
return data as Data
|
2020-08-12 07:24:39 +00:00
|
|
|
case errSecItemNotFound:
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
throw NSError(status: status)
|
|
|
|
}
|
|
|
|
}
|
2020-08-14 01:59:17 +00:00
|
|
|
|
|
|
|
static func deleteKey(applicationTag: String) throws {
|
|
|
|
let status = SecItemDelete(keyQueryDictionary(applicationTag: applicationTag) as CFDictionary)
|
|
|
|
|
|
|
|
if status != errSecSuccess {
|
|
|
|
throw NSError(status: status)
|
|
|
|
}
|
|
|
|
}
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
|
|
|
|
2020-08-12 09:01:21 +00:00
|
|
|
private extension LiveKeychainService {
|
2020-08-12 07:24:39 +00:00
|
|
|
static func genericPasswordQueryDictionary(account: String, service: String) -> [String: Any] {
|
|
|
|
[kSecAttrService as String: service,
|
|
|
|
kSecAttrAccount as String: account,
|
|
|
|
kSecClass as String: kSecClassGenericPassword]
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|
2020-08-12 07:24:39 +00:00
|
|
|
|
|
|
|
static func keyQueryDictionary(applicationTag: String) -> [String: Any] {
|
|
|
|
[kSecClass as String: kSecClassKey,
|
|
|
|
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
|
|
|
|
kSecAttrApplicationTag as String: applicationTag,
|
|
|
|
kSecReturnRef as String: true]
|
|
|
|
}
|
2020-07-29 23:50:30 +00:00
|
|
|
}
|