metatext/Shared/Services/KeychainService.swift

109 lines
3.9 KiB
Swift
Raw Normal View History

// 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?
static func generateKeyAndReturnPublicKey(applicationTag: String) throws -> Data
static func getPrivateKey(applicationTag: String) throws -> Data?
}
2020-08-12 09:01:21 +00:00
struct LiveKeychainService {}
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)
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)
if status != errSecSuccess {
throw NSError(status: status)
}
}
2020-08-12 07:24:39 +00:00
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
static func generateKeyAndReturnPublicKey(applicationTag: String) throws -> Data {
var attributes = keyAttributes
var error: Unmanaged<CFError>?
attributes[kSecPrivateKeyAttrs as String] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: Data(applicationTag.utf8)]
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
}
static func getPrivateKey(applicationTag: String) throws -> Data? {
var result: AnyObject?
let status = SecItemCopyMatching(keyQueryDictionary(applicationTag: applicationTag) as CFDictionary, &result)
switch status {
case errSecSuccess:
return result as? Data
case errSecItemNotFound:
return nil
default:
throw NSError(status: status)
}
}
}
2020-08-12 09:01:21 +00:00
private extension LiveKeychainService {
2020-08-12 07:24:39 +00:00
static let keySizeInBits = 256
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,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: keySizeInBits,
kSecAttrApplicationTag as String: applicationTag,
kSecReturnRef as String: true]
}
static let keyAttributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits as String: keySizeInBits]
}