fix picker on connectionview

using trimmingchars whitespaces and newlines to remove any trailing "\n"s
added padding to the button
publickey data uses a switch now
reorganised keymanager and used //MARKs
added header into KeyType for easier expansion of key types in the future
improved default key comment to use "at" between chunks and exclude time
This commit is contained in:
neon443
2025-07-03 10:13:57 +01:00
parent 3d9530f01b
commit c2c74248e2
7 changed files with 88 additions and 69 deletions

View File

@@ -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()

View File

@@ -16,4 +16,11 @@ enum KeyType: Codable, Equatable, Hashable, CustomStringConvertible, CaseIterabl
return "Ed25519"
}
}
var header: String {
switch self {
case .ed25519:
"ssh-ed25519"
}
}
}

View File

@@ -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()
}
}

View File

@@ -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?)
}
}
}

View File

@@ -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")

View File

@@ -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()
}
}

View File

@@ -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)
}