diff --git a/ShhShell/Keys/KeyManager.swift b/ShhShell/Keys/KeyManager.swift index d0d8093..893afe5 100644 --- a/ShhShell/Keys/KeyManager.swift +++ b/ShhShell/Keys/KeyManager.swift @@ -46,11 +46,49 @@ class KeyManager: ObservableObject { for keypair in keypairs { saveToKeychain(keypair) } + } + + func renameKey(keypair: Keypair, newName: String) { + guard !newName.isEmpty else { return } + let keyID = keypair.id + guard let index = keypairs.firstIndex(where: { $0.id == keyID }) else { return } + var keypairWithNewName = keypair + keypairWithNewName.name = newName + withAnimation { keypairs[index] = keypairWithNewName } + saveKeypairs() + } + + func deleteKey(_ keypair: Keypair) { + removeFromKeycahin(keypair: keypair) + let keyID = keypair.id + withAnimation { keypairs.removeAll(where: { $0.id == keyID }) } + saveKeypairs() + } + + func importKey(type: KeyType, priv: String, name: String) { + if type == .ed25519 { + guard let importedKeypair = KeyManager.importSSHPrivkey(priv: priv) else { return } + saveToKeychain(importedKeypair) + } else { fatalError() } + } + + func generateKey(type: KeyType, comment: String) { + switch type { + case .ed25519: + let keypair = Keypair( + type: .ed25519, + name: comment, + privateKey: Curve25519.Signing.PrivateKey().rawRepresentation + ) + saveToKeychain(keypair) + } loadKeypairs() } + //MARK: keychain func saveToKeychain(_ keypair: Keypair) { - if keypair.type == .ed25519 { + switch keypair.type { + case .ed25519: let curve25519 = try! Curve25519.Signing.PrivateKey(rawRepresentation: keypair.privateKey) let readKey: Curve25519.Signing.PrivateKey? readKey = try! passwordStore.readKey(account: keypair.id.uuidString) @@ -58,8 +96,10 @@ class KeyManager: ObservableObject { try! passwordStore.deleteKey(account: keypair.id.uuidString) } try! passwordStore.storeKey(curve25519.genericKeyRepresentation, account: keypair.id.uuidString) - } else { - + } + if !keypairs.contains(keypair) { + keypairs.append(keypair) + saveKeypairs() } } @@ -97,46 +137,7 @@ class KeyManager: ObservableObject { saveKeypairs() } - func renameKey(keypair: Keypair, newName: String) { - guard !newName.isEmpty else { return } - let keyID = keypair.id - guard let index = keypairs.firstIndex(where: { $0.id == keyID }) else { return } - var keypairWithNewName = keypair - keypairWithNewName.name = newName - withAnimation { keypairs[index] = keypairWithNewName } - saveKeypairs() - } - - func deleteKey(_ keypair: Keypair) { - removeFromKeycahin(keypair: keypair) - let keyID = keypair.id - withAnimation { keypairs.removeAll(where: { $0.id == keyID }) } - saveKeypairs() - } - - func importKey(type: KeyType, priv: String, name: String) { - if type == .ed25519 { - guard let importedKeypair = KeyManager.importSSHPrivkey(priv: priv) else { return } - saveToKeychain(importedKeypair) - saveKeypairs() - } else { fatalError() } - } - - //MARK: generate keys - func generateKey(type: KeyType, comment: String) { - switch type { - case .ed25519: - let keypair = Keypair( - type: .ed25519, - name: comment, - privateKey: Curve25519.Signing.PrivateKey().rawRepresentation - ) - saveToKeychain(keypair) - saveKeypairs() - } - loadKeypairs() - } - + //MARK: openssh converters/importers static func importSSHPubkey(pub: String) -> Data { let split = pub.split(separator: " ") guard split.count == 3 else { return Data() } @@ -150,15 +151,14 @@ class KeyManager: ObservableObject { } static func makeSSHPubkey(_ keypair: Keypair) -> Data { - let header = "ssh-ed25519" var keyBlob: Data = Data() //key type bit - keyBlob += encode(str: header) + keyBlob += encode(str: keypair.type.header) //base64 blob bit keyBlob += encode(data: keypair.publicKey) let b64key = keyBlob.base64EncodedString() - let pubkeyline = "\(header) \(b64key) \(keypair.name)\n" + let pubkeyline = "\(keypair.type.header) \(b64key) \(keypair.name)\n" return Data(pubkeyline.utf8) } @@ -254,6 +254,7 @@ class KeyManager: ObservableObject { return content } + //MARK: openssh conversion helpers static func encode(str: String) -> Data { guard let utf8 = str.data(using: .utf8) else { return Data() diff --git a/ShhShell/Keys/KeyType.swift b/ShhShell/Keys/KeyType.swift index bd36615..658c3ab 100644 --- a/ShhShell/Keys/KeyType.swift +++ b/ShhShell/Keys/KeyType.swift @@ -16,4 +16,11 @@ enum KeyType: Codable, Equatable, Hashable, CustomStringConvertible, CaseIterabl return "Ed25519" } } + + var header: String { + switch self { + case .ed25519: + "ssh-ed25519" + } + } } diff --git a/ShhShell/Keys/Keypair.swift b/ShhShell/Keys/Keypair.swift index 94a468e..d175f60 100644 --- a/ShhShell/Keys/Keypair.swift +++ b/ShhShell/Keys/Keypair.swift @@ -25,10 +25,9 @@ struct Keypair: KeypairProtocol { var type: KeyType = .ed25519 var name: String = "" var publicKey: Data { - if privateKey.isEmpty { - print("not a valid ed25519 key") - fatalError() - } else { + guard !privateKey.isEmpty else { return Data() } + switch type { + case .ed25519: return (try? Curve25519.Signing.PrivateKey(rawRepresentation: privateKey).publicKey.rawRepresentation) ?? Data() } } diff --git a/ShhShell/Views/Hosts/ConnectionView.swift b/ShhShell/Views/Hosts/ConnectionView.swift index b0be1e6..a3e1051 100644 --- a/ShhShell/Views/Hosts/ConnectionView.swift +++ b/ShhShell/Views/Hosts/ConnectionView.swift @@ -82,9 +82,10 @@ struct ConnectionView: View { Picker("Private key", selection: $handler.host.privateKeyID) { Text("None") .tag(nil as UUID?) + Divider() ForEach(keyManager.keypairs) { keypair in Text(keypair.label) - .tag(keypair.id) + .tag(keypair.id as UUID?) } } } diff --git a/ShhShell/Views/Keys/KeyDetailView.swift b/ShhShell/Views/Keys/KeyDetailView.swift index 3be167f..813dc9e 100644 --- a/ShhShell/Views/Keys/KeyDetailView.swift +++ b/ShhShell/Views/Keys/KeyDetailView.swift @@ -90,14 +90,14 @@ struct KeyDetailView: View { VStack(alignment: .leading) { Text("Public key") .bold() - Text(keypair.openSshPubkey.dropLast(2)) + Text(keypair.openSshPubkey.trimmingCharacters(in: .whitespacesAndNewlines)) } VStack(alignment: .leading) { Text("Private key") .bold() .frame(maxWidth: .infinity) ZStack(alignment: .center) { - Text(keypair.openSshPrivkey.dropLast(2)) + Text(keypair.openSshPrivkey.trimmingCharacters(in: .whitespacesAndNewlines)) .blur(radius: reveal ? 0 : 5) VStack { Image(systemName: "eye.slash.fill") diff --git a/ShhShell/Views/Keys/KeyImporterView.swift b/ShhShell/Views/Keys/KeyImporterView.swift index 523dfde..45ae5fb 100644 --- a/ShhShell/Views/Keys/KeyImporterView.swift +++ b/ShhShell/Views/Keys/KeyImporterView.swift @@ -12,7 +12,7 @@ struct KeyImporterView: View { @Environment(\.dismiss) var dismiss - @State var keyName: String = UIDevice().model + " " + Date().formatted() + @State var keyName: String = UIDevice().model + " at " + Date().formatted(date: .numeric, time: .omitted) @State var privkeyStr: String = "" @State var keyType: KeyType = .ed25519 @@ -24,25 +24,33 @@ struct KeyImporterView: View { List { TextBox(label: "Name", text: $keyName, prompt: "A name for your key") - Picker("Key type", selection: $keyType) { - ForEach(KeyType.allCases, id: \.self) { type in - Text(type.description) - .tag(type) - } - } - .pickerStyle(SegmentedPickerStyle()) - HStack { - Text("Private Key") - Spacer() - Text("Required") - .foregroundStyle(.red) + Text("Key Type") + Picker("Key type", selection: $keyType) { + ForEach(KeyType.allCases, id: \.self) { type in + Text(type.description) + .tag(type) + } + } + .pickerStyle(SegmentedPickerStyle()) } - TextEditor(text: $privkeyStr) + Section { + HStack { + Text("Private Key") + Spacer() + Text("Required") + .foregroundStyle(.red) + } + .listRowSeparator(.hidden) + + TextEditor(text: $privkeyStr) + .background(.black) + .clipShape(RoundedRectangle(cornerRadius: 10)) + } if !keypair.openSshPubkey.isEmpty { - TextEditor(text: .constant(keypair.openSshPubkey)) + Text(keypair.openSshPubkey.trimmingCharacters(in: .whitespacesAndNewlines)) .foregroundStyle(.gray) } @@ -53,11 +61,14 @@ struct KeyImporterView: View { dismiss() } label: { Text("Import") + .font(.title) + .bold() } .onTapGesture { UINotificationFeedbackGenerator().notificationOccurred(.success) } .buttonStyle(.borderedProminent) + .padding() } } diff --git a/ShhShell/Views/Keys/KeyManagerView.swift b/ShhShell/Views/Keys/KeyManagerView.swift index 994e3e4..353fbd6 100644 --- a/ShhShell/Views/Keys/KeyManagerView.swift +++ b/ShhShell/Views/Keys/KeyManagerView.swift @@ -52,7 +52,7 @@ struct KeyManagerView: View { } Button("Generate a new Ed25519 Key") { - let comment = UIDevice().model + " " + Date().formatted() + let comment = UIDevice().model + " at " + Date().formatted(date: .numeric, time: .omitted) keyManager.generateKey(type: .ed25519, comment: comment) }