Sesame-iOS/Sesame/Common/KeyManagement.swift

194 lines
4.7 KiB
Swift
Raw Normal View History

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
}
extension KeyManagement {
2022-04-09 17:43:33 +02:00
enum KeyType: String, Identifiable, CaseIterable {
2022-04-09 17:43:33 +02:00
case deviceKey = "sesame-device"
case remoteKey = "sesame-remote"
case authToken = "sesame-remote-auth"
2022-04-09 17:43:33 +02:00
var id: String {
rawValue
}
var displayName: String {
switch self {
case .deviceKey:
2023-08-07 15:47:40 +02:00
return "Unlock Key"
case .remoteKey:
2023-08-07 15:47:40 +02:00
return "Response Key"
case .authToken:
2023-08-07 15:47:40 +02:00
return "Server Token"
}
}
var keyLength: SymmetricKeySize {
.bits256
}
var usesHashing: Bool {
switch self {
case .authToken:
return true
default:
return false
}
}
}
}
extension KeyManagement.KeyType: CustomStringConvertible {
var description: String {
displayName
}
}
private struct KeyChain {
private let keyType = kSecAttrKeyTypeEC
2022-04-09 17:43:33 +02:00
private let keyClass = kSecAttrKeyClassSymmetric
private let domain: String
init(domain: String) {
self.domain = domain
}
2022-04-09 17:43:33 +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
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 {
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
return SymmetricKey(data: key as Data)
2022-01-29 18:59:42 +01:00
}
2022-04-09 17:43:33 +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 {
print("Failed to remove \(type): \(status)")
2022-04-09 17:43:33 +02:00
return
2022-01-29 18:59:42 +01:00
}
print("\(type) removed from keychain")
2022-01-29 18:59:42 +01:00
}
2022-04-09 17:43:33 +02:00
func has(_ type: KeyManagement.KeyType) -> Bool {
load(type) != nil
2022-01-29 18:59:42 +01:00
}
}
2022-04-09 17:43:33 +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
init() {
self.keyChain = KeyChain(domain: "christophhagen.de")
updateKeyStates()
2022-01-29 18:59:42 +01:00
}
2022-04-09 17:43:33 +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
}
}
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)
}
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) {
if keyChain.has(type) {
keyChain.delete(type)
2022-04-09 17:43:33 +02:00
}
keyChain.save(type, key)
updateKeyStates()
2022-04-09 17:43:33 +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
}
}