2022-01-29 18:59:42 +01:00
|
|
|
import Foundation
|
|
|
|
import CryptoKit
|
|
|
|
import SwiftUI
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
struct KeySet {
|
|
|
|
|
|
|
|
let remote: SymmetricKey
|
|
|
|
|
|
|
|
let device: SymmetricKey
|
|
|
|
|
|
|
|
let server: Data
|
|
|
|
}
|
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
extension KeyManagement {
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
enum KeyType: String, Identifiable, CaseIterable {
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
case deviceKey = "sesame-device"
|
|
|
|
case remoteKey = "sesame-remote"
|
|
|
|
case authToken = "sesame-remote-auth"
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
var id: String {
|
|
|
|
rawValue
|
|
|
|
}
|
|
|
|
|
|
|
|
var displayName: String {
|
|
|
|
switch self {
|
|
|
|
case .deviceKey:
|
2023-08-07 15:47:40 +02:00
|
|
|
return "Unlock Key"
|
2022-05-01 14:07:43 +02:00
|
|
|
case .remoteKey:
|
2023-08-07 15:47:40 +02:00
|
|
|
return "Response Key"
|
2022-05-01 14:07:43 +02:00
|
|
|
case .authToken:
|
2023-08-07 15:47:40 +02:00
|
|
|
return "Server Token"
|
2022-05-01 14:07:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var keyLength: SymmetricKeySize {
|
|
|
|
.bits256
|
|
|
|
}
|
2022-05-01 18:30:30 +02:00
|
|
|
|
|
|
|
var usesHashing: Bool {
|
|
|
|
switch self {
|
|
|
|
case .authToken:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2022-05-01 14:07:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension KeyManagement.KeyType: CustomStringConvertible {
|
|
|
|
|
|
|
|
var description: String {
|
|
|
|
displayName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private struct KeyChain {
|
|
|
|
|
|
|
|
private let keyType = kSecAttrKeyTypeEC
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
private let keyClass = kSecAttrKeyClassSymmetric
|
|
|
|
|
|
|
|
private let domain: String
|
|
|
|
|
|
|
|
init(domain: String) {
|
|
|
|
self.domain = domain
|
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
private func baseQuery(for type: KeyManagement.KeyType) -> [String : Any] {
|
|
|
|
[kSecClass as String: kSecClassInternetPassword,
|
|
|
|
kSecAttrAccount as String: type.rawValue,
|
|
|
|
kSecAttrServer as String: domain]
|
|
|
|
}
|
|
|
|
|
|
|
|
func save(_ type: KeyManagement.KeyType, _ key: SymmetricKey) {
|
|
|
|
var query = baseQuery(for: type)
|
|
|
|
query[kSecValueData as String] = key.data
|
|
|
|
let status = SecItemAdd(query as CFDictionary, nil)
|
|
|
|
guard status == errSecSuccess else {
|
|
|
|
print("Failed to store \(type): \(status)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
print("\(type) saved to keychain")
|
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
func load(_ type: KeyManagement.KeyType) -> SymmetricKey? {
|
|
|
|
var query = baseQuery(for: type)
|
2022-04-09 17:43:33 +02:00
|
|
|
query[kSecReturnData as String] = kCFBooleanTrue
|
|
|
|
|
|
|
|
var item: CFTypeRef?
|
|
|
|
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
2023-08-14 10:39:29 +02:00
|
|
|
guard status != -25300 else {
|
|
|
|
return nil
|
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
guard status == errSecSuccess else {
|
2022-05-01 14:07:43 +02:00
|
|
|
print("Failed to get \(type): \(status)")
|
2022-04-09 17:43:33 +02:00
|
|
|
return nil
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
let key = item as! CFData
|
2022-05-01 14:07:43 +02:00
|
|
|
return SymmetricKey(data: key as Data)
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
func delete(_ type: KeyManagement.KeyType) {
|
|
|
|
let status = SecItemDelete(baseQuery(for: type) as CFDictionary)
|
2022-04-09 17:43:33 +02:00
|
|
|
guard status == errSecSuccess || status == errSecItemNotFound else {
|
2022-05-01 14:07:43 +02:00
|
|
|
print("Failed to remove \(type): \(status)")
|
2022-04-09 17:43:33 +02:00
|
|
|
return
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-05-01 14:07:43 +02:00
|
|
|
print("\(type) removed from keychain")
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
func has(_ type: KeyManagement.KeyType) -> Bool {
|
|
|
|
load(type) != nil
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-05-01 14:07:43 +02:00
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
final class KeyManagement: ObservableObject {
|
|
|
|
|
|
|
|
private let keyChain: KeyChain
|
|
|
|
|
|
|
|
@Published
|
|
|
|
private(set) var hasRemoteKey = false
|
|
|
|
|
|
|
|
@Published
|
|
|
|
private(set) var hasDeviceKey = false
|
|
|
|
|
|
|
|
@Published
|
|
|
|
private(set) var hasAuthToken = false
|
|
|
|
|
2023-12-12 17:33:42 +01:00
|
|
|
@Published
|
|
|
|
private(set) var hasAllKeys = false
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
init() {
|
|
|
|
self.keyChain = KeyChain(domain: "christophhagen.de")
|
|
|
|
updateKeyStates()
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-04-09 17:43:33 +02:00
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
func has(_ type: KeyType) -> Bool {
|
|
|
|
switch type {
|
|
|
|
case .deviceKey:
|
|
|
|
return hasDeviceKey
|
|
|
|
case .remoteKey:
|
|
|
|
return hasRemoteKey
|
|
|
|
case .authToken:
|
|
|
|
return hasAuthToken
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
2022-05-01 14:07:43 +02:00
|
|
|
}
|
|
|
|
|
2023-08-14 10:39:29 +02:00
|
|
|
func getAllKeys() -> KeySet? {
|
|
|
|
guard let remoteKey = get(.remoteKey),
|
|
|
|
let token = get(.authToken)?.data,
|
|
|
|
let deviceKey = get(.deviceKey) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return .init(remote: remoteKey, device: deviceKey, server: token)
|
|
|
|
}
|
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
func get(_ type: KeyType) -> SymmetricKey? {
|
|
|
|
keyChain.load(type)
|
|
|
|
}
|
|
|
|
|
|
|
|
func delete(_ type: KeyType) {
|
|
|
|
keyChain.delete(type)
|
|
|
|
updateKeyStates()
|
|
|
|
}
|
|
|
|
|
|
|
|
func generate(_ type: KeyType) {
|
|
|
|
let key = SymmetricKey(size: type.keyLength)
|
2023-08-07 15:47:40 +02:00
|
|
|
save(type, key: key)
|
|
|
|
}
|
|
|
|
|
|
|
|
func save(_ type: KeyType, data: Data) {
|
|
|
|
let key = SymmetricKey(data: data)
|
|
|
|
save(type, key: key)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func save(_ type: KeyType, key: SymmetricKey) {
|
2022-05-01 14:07:43 +02:00
|
|
|
if keyChain.has(type) {
|
|
|
|
keyChain.delete(type)
|
2022-04-09 17:43:33 +02:00
|
|
|
}
|
2022-05-01 14:07:43 +02:00
|
|
|
keyChain.save(type, key)
|
|
|
|
updateKeyStates()
|
2022-04-09 17:43:33 +02:00
|
|
|
}
|
|
|
|
|
2022-05-01 14:07:43 +02:00
|
|
|
private func updateKeyStates() {
|
|
|
|
self.hasRemoteKey = keyChain.has(.remoteKey)
|
|
|
|
self.hasDeviceKey = keyChain.has(.deviceKey)
|
|
|
|
self.hasAuthToken = keyChain.has(.authToken)
|
2023-12-12 17:33:42 +01:00
|
|
|
self.hasAllKeys = hasRemoteKey && hasDeviceKey && hasAuthToken
|
2022-01-29 18:59:42 +01:00
|
|
|
}
|
|
|
|
}
|