From 3d9530f01b348df42661a07b7ccf0302956f9aa3 Mon Sep 17 00:00:00 2001 From: neon443 <69979447+neon443@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:20:00 +0100 Subject: [PATCH] simplified logic by saving Keypairs as an array to defaults, but without the privateKey and passphrase for obvious reasons added codingkeys, omitting the sensitive ones like privatekey and keypair added init from decoder to skip privatekey and passphrase removed keyTypes and keyNames, vastly simplifying logic --- ShhShell/Keys/KeyManager.swift | 76 +++++++++++----------------------- ShhShell/Keys/Keypair.swift | 23 +++++++++- 2 files changed, 46 insertions(+), 53 deletions(-) diff --git a/ShhShell/Keys/KeyManager.swift b/ShhShell/Keys/KeyManager.swift index a86cef6..d0d8093 100644 --- a/ShhShell/Keys/KeyManager.swift +++ b/ShhShell/Keys/KeyManager.swift @@ -16,9 +16,11 @@ class KeyManager: ObservableObject { private let passwordStore = GenericPasswordStore() @Published var keypairs: [Keypair] = [] - - @Published var keyTypes: [UUID: KeyType] = [:] - @Published var keyNames: [UUID: String] = [:] + var keyIDs: [UUID] { + keypairs.map { $0.id } + } +// @Published var keyTypes: [UUID: KeyType] = [:] +// @Published var keyNames: [UUID: String] = [:] private let baseTag = "com.neon443.ShhShell.keys".data(using: .utf8)! init() { @@ -26,50 +28,28 @@ class KeyManager: ObservableObject { } func loadKeypairs() { - loadKeyIDs() - keypairs = [] - for id in keyTypes.keys { - guard let keypair = getFromKeychain(keyID: id) else { continue } - keypairs.append(keypair) + userdefaults.synchronize() + guard let data = userdefaults.data(forKey: "keypairs") else { return } + guard let decoded = try? JSONDecoder().decode([Keypair].self, from: data) else { return } + withAnimation { self.keypairs = decoded } + for kp in keypairs { + guard let KeychainKeypair = getFromKeychain(kp) else { continue } + guard let index = keypairs.firstIndex(where: { $0.id == kp.id }) else { continue } + withAnimation { keypairs[index] = KeychainKeypair } } } func saveKeypairs() { + guard let coded = try? JSONEncoder().encode(keypairs) else { return } + userdefaults.set(coded, forKey: "keypairs") + userdefaults.synchronize() for keypair in keypairs { - keyTypes.updateValue(keypair.type, forKey: keypair.id) - keyNames.updateValue(keypair.name, forKey: keypair.id) saveToKeychain(keypair) } - saveKeyIDs() - loadKeypairs() - } - - func loadKeyIDs() { - userdefaults.synchronize() - let decoder = JSONDecoder() - guard let data = userdefaults.data(forKey: "keyIDs") else { return } - guard let decoded = try? decoder.decode([UUID:KeyType].self, from: data) else { return } - keyTypes = decoded - - guard let dataNames = userdefaults.data(forKey: "keyNames") else { return } - guard let decodedNames = try? decoder.decode([UUID:String].self, from: dataNames) else { return } - keyNames = decodedNames - } - - func saveKeyIDs() { - let encoder = JSONEncoder() - guard let encoded = try? encoder.encode(keyTypes) else { return } - userdefaults.set(encoded, forKey: "keyIDs") - - guard let encodedNames = try? encoder.encode(keyNames) else { return } - userdefaults.set(encodedNames, forKey: "keyNames") - userdefaults.synchronize() loadKeypairs() } func saveToKeychain(_ keypair: Keypair) { - keyTypes.updateValue(keypair.type, forKey: keypair.id) - keyNames.updateValue(keypair.name, forKey: keypair.id) if keypair.type == .ed25519 { let curve25519 = try! Curve25519.Signing.PrivateKey(rawRepresentation: keypair.privateKey) let readKey: Curve25519.Signing.PrivateKey? @@ -83,16 +63,14 @@ class KeyManager: ObservableObject { } } - func getFromKeychain(keyID: UUID) -> Keypair? { - guard let keyType = keyTypes[keyID] else { return nil } - guard let keyName = keyNames[keyID] else { return nil } - if keyType == .ed25519 { + func getFromKeychain(_ keypair: Keypair) -> Keypair? { + if keypair.type == .ed25519 { var key: Curve25519.Signing.PrivateKey? - key = try? passwordStore.readKey(account: keyID.uuidString) + key = try? passwordStore.readKey(account: keypair.id.uuidString) guard let key else { return nil } - return Keypair(id: keyID, type: keyType, name: keyName, privateKey: key.rawRepresentation) + return Keypair(id: keypair.id, type: keypair.type, name: keypair.name, privateKey: key.rawRepresentation) } else { - let tag = baseTag+keyID.uuidString.data(using: .utf8)! + let tag = baseTag+keypair.id.uuidString.data(using: .utf8)! let getQuery: [String: Any] = [kSecClass as String: kSecClassKey, kSecAttrApplicationTag as String: tag, kSecAttrKeyType as String: kSecAttrKeyTypeEC, @@ -101,8 +79,8 @@ class KeyManager: ObservableObject { let status = SecItemCopyMatching(getQuery as CFDictionary, &item) guard status == errSecSuccess else { fatalError() } return Keypair( - type: keyType, - name: keyName, + type: keypair.type, + name: keypair.name, privateKey: item as! Data ) } @@ -116,9 +94,7 @@ class KeyManager: ObservableObject { fatalError() } } - keyNames.removeValue(forKey: keypair.id) - keyTypes.removeValue(forKey: keypair.id) - saveKeyIDs() + saveKeypairs() } func renameKey(keypair: Keypair, newName: String) { @@ -278,10 +254,6 @@ class KeyManager: ObservableObject { return content } - func getPubkey(_ privateKey: SecKey) -> SecKey? { - return SecKeyCopyPublicKey(privateKey) - } - static func encode(str: String) -> Data { guard let utf8 = str.data(using: .utf8) else { return Data() diff --git a/ShhShell/Keys/Keypair.swift b/ShhShell/Keys/Keypair.swift index 07b1194..94a468e 100644 --- a/ShhShell/Keys/Keypair.swift +++ b/ShhShell/Keys/Keypair.swift @@ -26,7 +26,8 @@ struct Keypair: KeypairProtocol { var name: String = "" var publicKey: Data { if privateKey.isEmpty { - return Data() + print("not a valid ed25519 key") + fatalError() } else { return (try? Curve25519.Signing.PrivateKey(rawRepresentation: privateKey).publicKey.rawRepresentation) ?? Data() } @@ -34,6 +35,15 @@ struct Keypair: KeypairProtocol { var privateKey: Data var passphrase: String = "" + enum CodingKeys: String, CodingKey { + case id = "id" + case type = "type" + case name = "name" + +// case privateKey = "privateKey" +// case passphrase = "passphrase" + } + var label: String { if name.isEmpty { return openSshPubkey @@ -82,6 +92,17 @@ struct Keypair: KeypairProtocol { self.passphrase = passphrase } + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(UUID.self, forKey: .id) + self.type = try container.decode(KeyType.self, forKey: .type) + self.name = try container.decode(String.self, forKey: .name) +// self.privateKey = try container.decode(Data.self, forKey: .privateKey) +// self.passphrase = try container.decode(String.self, forKey: .passphrase) + self.privateKey = Data() + self.passphrase = "" + } + static func ==(lhs: Keypair, rhs: Keypair) -> Bool { if lhs.publicKey.base64EncodedString() == rhs.publicKey.base64EncodedString() && lhs.privateKey.base64EncodedString() == rhs.privateKey.base64EncodedString() {