metatext/Keychain/Sources/Keychain/Keychain.swift

141 lines
5.2 KiB
Swift
Raw Normal View History

// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
2020-09-04 00:54:05 +00:00
public protocol Keychain {
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?
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-09-04 00:54:05 +00:00
public struct LiveKeychain {}
2020-09-04 00:54:05 +00:00
extension LiveKeychain: Keychain {
2020-08-31 10:21:01 +00:00
public static func setGenericPassword(data: Data, forAccount account: String, service: String) throws {
2020-08-12 07:24:39 +00:00
var query = genericPasswordQueryDictionary(account: account, service: service)
query[kSecValueData as String] = data
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
2020-11-21 23:21:04 +00:00
var status = SecItemAdd(query as CFDictionary, nil)
if status == errSecDuplicateItem {
status = SecItemUpdate(
genericPasswordQueryDictionary(account: account, service: service) as CFDictionary,
[kSecValueData as String: data] as CFDictionary)
}
if status != errSecSuccess {
throw NSError(status: status)
}
}
2020-08-31 10:21:01 +00:00
public static func deleteGenericPassword(account: String, service: String) throws {
2020-08-12 07:24:39 +00:00
let status = SecItemDelete(genericPasswordQueryDictionary(account: account, service: service) as CFDictionary)
if status != errSecSuccess {
throw NSError(status: status)
}
}
2020-08-31 10:21:01 +00:00
public static func getGenericPassword(account: String, service: String) throws -> Data? {
var result: AnyObject?
2020-08-12 07:24:39 +00:00
var query = genericPasswordQueryDictionary(account: account, service: service)
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-31 10:21:01 +00:00
public static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data {
2020-11-21 23:21:04 +00:00
try? deleteKey(applicationTag: applicationTag)
var attributes = attributes
2020-08-12 07:24:39 +00:00
var error: Unmanaged<CFError>?
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,
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-31 10:21:01 +00:00
public static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? {
2020-08-12 07:24:39 +00:00
var result: AnyObject?
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:
// 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
2020-08-31 10:21:01 +00:00
public static func deleteKey(applicationTag: String) throws {
2020-08-14 01:59:17 +00:00
let status = SecItemDelete(keyQueryDictionary(applicationTag: applicationTag) as CFDictionary)
if status != errSecSuccess {
throw NSError(status: status)
}
}
}
2020-09-04 00:54:05 +00:00
private extension LiveKeychain {
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-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]
}
}