mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
added support for saving keys in the keychain
load and save yk added savetokeychain to save a key to the keychain added getfromkeycahin to get a key from the keychain fix generatekey added apple's cryptokit in the keychain sample code (keychain layer dir)
This commit is contained in:
@@ -52,7 +52,6 @@
|
|||||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554542E05535F009051BD /* KeyManagerView.swift */; };
|
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554542E05535F009051BD /* KeyManagerView.swift */; };
|
||||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554582E0553AA009051BD /* KeyManager.swift */; };
|
A98554592E0553AA009051BD /* KeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554582E0553AA009051BD /* KeyManager.swift */; };
|
||||||
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545C2E055D4D009051BD /* ConnectionView.swift */; };
|
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545C2E055D4D009051BD /* ConnectionView.swift */; };
|
||||||
A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545E2E056EDD009051BD /* KeychainLayer.swift */; };
|
|
||||||
A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; };
|
A98554612E058433009051BD /* HostsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554602E058433009051BD /* HostsManager.swift */; };
|
||||||
A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; };
|
A98554632E0587DF009051BD /* HostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554622E0587DF009051BD /* HostsView.swift */; };
|
||||||
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A9A5871F2E0BF220006B31E6 /* SwiftTerm */; };
|
A9A587202E0BF220006B31E6 /* SwiftTerm in Frameworks */ = {isa = PBXBuildFile; productRef = A9A5871F2E0BF220006B31E6 /* SwiftTerm */; };
|
||||||
@@ -65,6 +64,11 @@
|
|||||||
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819302E102D8700442D38 /* HostkeysView.swift */; };
|
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D819302E102D8700442D38 /* HostkeysView.swift */; };
|
||||||
A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97702E0D30ED00142DDC /* HostSymbol.swift */; };
|
A9DA97712E0D30ED00142DDC /* HostSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97702E0D30ED00142DDC /* HostSymbol.swift */; };
|
||||||
A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* SymbolPreview.swift */; };
|
A9DA97732E0D40C100142DDC /* SymbolPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9DA97722E0D40C100142DDC /* SymbolPreview.swift */; };
|
||||||
|
A9FD37552E143D23005319A8 /* SecKeyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD37542E143D23005319A8 /* SecKeyConvertible.swift */; };
|
||||||
|
A9FD37572E143D5A005319A8 /* SecKeyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD37562E143D5A005319A8 /* SecKeyStore.swift */; };
|
||||||
|
A9FD37592E143D74005319A8 /* GenericPasswordConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD37582E143D74005319A8 /* GenericPasswordConvertible.swift */; };
|
||||||
|
A9FD375B2E143D77005319A8 /* GenericPasswordStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD375A2E143D77005319A8 /* GenericPasswordStore.swift */; };
|
||||||
|
A9FD375D2E143D7E005319A8 /* KeyStoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FD375C2E143D7E005319A8 /* KeyStoreError.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -150,7 +154,6 @@
|
|||||||
A98554542E05535F009051BD /* KeyManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagerView.swift; sourceTree = "<group>"; };
|
A98554542E05535F009051BD /* KeyManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManagerView.swift; sourceTree = "<group>"; };
|
||||||
A98554582E0553AA009051BD /* KeyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManager.swift; sourceTree = "<group>"; };
|
A98554582E0553AA009051BD /* KeyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyManager.swift; sourceTree = "<group>"; };
|
||||||
A985545C2E055D4D009051BD /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = "<group>"; };
|
A985545C2E055D4D009051BD /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = "<group>"; };
|
||||||
A985545E2E056EDD009051BD /* KeychainLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainLayer.swift; sourceTree = "<group>"; };
|
|
||||||
A98554602E058433009051BD /* HostsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsManager.swift; sourceTree = "<group>"; };
|
A98554602E058433009051BD /* HostsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsManager.swift; sourceTree = "<group>"; };
|
||||||
A98554622E0587DF009051BD /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = "<group>"; };
|
A98554622E0587DF009051BD /* HostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsView.swift; sourceTree = "<group>"; };
|
||||||
A9B15A992E0ABA0400F66E02 /* DialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = "<group>"; };
|
A9B15A992E0ABA0400F66E02 /* DialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = "<group>"; };
|
||||||
@@ -162,6 +165,11 @@
|
|||||||
A9D819302E102D8700442D38 /* HostkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostkeysView.swift; sourceTree = "<group>"; };
|
A9D819302E102D8700442D38 /* HostkeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostkeysView.swift; sourceTree = "<group>"; };
|
||||||
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = "<group>"; };
|
A9DA97702E0D30ED00142DDC /* HostSymbol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostSymbol.swift; sourceTree = "<group>"; };
|
||||||
A9DA97722E0D40C100142DDC /* SymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolPreview.swift; sourceTree = "<group>"; };
|
A9DA97722E0D40C100142DDC /* SymbolPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SymbolPreview.swift; sourceTree = "<group>"; };
|
||||||
|
A9FD37542E143D23005319A8 /* SecKeyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecKeyConvertible.swift; sourceTree = "<group>"; };
|
||||||
|
A9FD37562E143D5A005319A8 /* SecKeyStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecKeyStore.swift; sourceTree = "<group>"; };
|
||||||
|
A9FD37582E143D74005319A8 /* GenericPasswordConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPasswordConvertible.swift; sourceTree = "<group>"; };
|
||||||
|
A9FD375A2E143D77005319A8 /* GenericPasswordStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPasswordStore.swift; sourceTree = "<group>"; };
|
||||||
|
A9FD375C2E143D7E005319A8 /* KeyStoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyStoreError.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -376,9 +384,9 @@
|
|||||||
A98554572E055398009051BD /* Keys */ = {
|
A98554572E055398009051BD /* Keys */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A9FD37532E143D11005319A8 /* KeychainLayer */,
|
||||||
A96C90A22E12D53900724253 /* KeyType.swift */,
|
A96C90A22E12D53900724253 /* KeyType.swift */,
|
||||||
A98554582E0553AA009051BD /* KeyManager.swift */,
|
A98554582E0553AA009051BD /* KeyManager.swift */,
|
||||||
A985545E2E056EDD009051BD /* KeychainLayer.swift */,
|
|
||||||
A96C6AFD2E0C43B600F377FE /* Keypair.swift */,
|
A96C6AFD2E0C43B600F377FE /* Keypair.swift */,
|
||||||
);
|
);
|
||||||
path = Keys;
|
path = Keys;
|
||||||
@@ -414,6 +422,18 @@
|
|||||||
path = Themes;
|
path = Themes;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A9FD37532E143D11005319A8 /* KeychainLayer */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A9FD37542E143D23005319A8 /* SecKeyConvertible.swift */,
|
||||||
|
A9FD37562E143D5A005319A8 /* SecKeyStore.swift */,
|
||||||
|
A9FD37582E143D74005319A8 /* GenericPasswordConvertible.swift */,
|
||||||
|
A9FD375A2E143D77005319A8 /* GenericPasswordStore.swift */,
|
||||||
|
A9FD375C2E143D7E005319A8 /* KeyStoreError.swift */,
|
||||||
|
);
|
||||||
|
path = KeychainLayer;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
@@ -572,9 +592,9 @@
|
|||||||
A9D8192F2E0F1BEE00442D38 /* ThemePreview.swift in Sources */,
|
A9D8192F2E0F1BEE00442D38 /* ThemePreview.swift in Sources */,
|
||||||
A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */,
|
A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */,
|
||||||
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */,
|
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */,
|
||||||
A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */,
|
|
||||||
A9D819292E0E904200442D38 /* Theme.swift in Sources */,
|
A9D819292E0E904200442D38 /* Theme.swift in Sources */,
|
||||||
A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */,
|
A9D8192D2E0E9EB500442D38 /* ThemeManagerView.swift in Sources */,
|
||||||
|
A9FD375D2E143D7E005319A8 /* KeyStoreError.swift in Sources */,
|
||||||
A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */,
|
A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */,
|
||||||
A98554632E0587DF009051BD /* HostsView.swift in Sources */,
|
A98554632E0587DF009051BD /* HostsView.swift in Sources */,
|
||||||
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */,
|
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */,
|
||||||
@@ -593,14 +613,18 @@
|
|||||||
A96C90A32E12D53B00724253 /* KeyType.swift in Sources */,
|
A96C90A32E12D53B00724253 /* KeyType.swift in Sources */,
|
||||||
A98554612E058433009051BD /* HostsManager.swift in Sources */,
|
A98554612E058433009051BD /* HostsManager.swift in Sources */,
|
||||||
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
||||||
|
A9FD37592E143D74005319A8 /* GenericPasswordConvertible.swift in Sources */,
|
||||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||||
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
A9D819312E102D8700442D38 /* HostkeysView.swift in Sources */,
|
||||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||||
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */,
|
A923172D2E07138000ECE1E6 /* SSHTerminalDelegate.swift in Sources */,
|
||||||
|
A9FD37552E143D23005319A8 /* SecKeyConvertible.swift in Sources */,
|
||||||
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */,
|
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */,
|
||||||
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */,
|
A9C4140C2E096DB7005E3047 /* SSHError.swift in Sources */,
|
||||||
A96BE6AA2E116EC000C0FEE9 /* TerminalViewContainer.swift in Sources */,
|
A96BE6AA2E116EC000C0FEE9 /* TerminalViewContainer.swift in Sources */,
|
||||||
|
A9FD375B2E143D77005319A8 /* GenericPasswordStore.swift in Sources */,
|
||||||
|
A9FD37572E143D5A005319A8 /* SecKeyStore.swift in Sources */,
|
||||||
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */,
|
A923172A2E07113100ECE1E6 /* TerminalController.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class HostsManager: ObservableObject, @unchecked Sendable {
|
|||||||
string.contains("-----") {
|
string.contains("-----") {
|
||||||
keypair = KeyManager.importSSHPrivkey(priv: string)
|
keypair = KeyManager.importSSHPrivkey(priv: string)
|
||||||
} else {
|
} else {
|
||||||
keypair = Keypair(type: .ecdsa, name: UUID().uuidString, privateKey: privateKey)
|
keypair = Keypair(type: .ed25519, name: UUID().uuidString, privateKey: privateKey)
|
||||||
}
|
}
|
||||||
if !result.contains(keypair) {
|
if !result.contains(keypair) {
|
||||||
result.append(keypair)
|
result.append(keypair)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CryptoKit
|
import CryptoKit
|
||||||
import Security
|
import Security
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct Key: Identifiable, Hashable {
|
struct Key: Identifiable, Hashable {
|
||||||
var id = UUID()
|
var id = UUID()
|
||||||
@@ -20,28 +21,96 @@ struct Key: Identifiable, Hashable {
|
|||||||
class KeyManager: ObservableObject {
|
class KeyManager: ObservableObject {
|
||||||
private let userdefaults = NSUbiquitousKeyValueStore.default
|
private let userdefaults = NSUbiquitousKeyValueStore.default
|
||||||
|
|
||||||
var tags: [String] = []
|
@Published var keypairs: [Keypair] = []
|
||||||
|
var keyIDs: [UUID: KeyType] = [:]
|
||||||
|
var keyNames: [UUID: String] = [:]
|
||||||
|
private let baseTag = "com.neon443.ShhShell.keys".data(using: .utf8)!
|
||||||
|
|
||||||
func loadTags() {
|
init() {
|
||||||
userdefaults.synchronize()
|
loadKeyIDs()
|
||||||
let decoder = JSONDecoder()
|
for id in keyIDs.keys {
|
||||||
guard let data = userdefaults.data(forKey: "keyTags") else { return }
|
guard let keypair = getFromKeychain(keyID: id) else { continue }
|
||||||
guard let decoded = try? decoder.decode([String].self, from: data) else { return }
|
keypairs.append(keypair)
|
||||||
tags = decoded
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveTags() {
|
func loadKeyIDs() {
|
||||||
let encoder = JSONEncoder()
|
|
||||||
guard let encoded = try? encoder.encode(tags) else { return }
|
|
||||||
userdefaults.set(encoded, forKey: "keyTags")
|
|
||||||
userdefaults.synchronize()
|
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 }
|
||||||
|
keyIDs = 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(keyIDs) else { return }
|
||||||
|
userdefaults.set(encoded, forKey: "keyIDs")
|
||||||
|
|
||||||
|
guard let encodedNames = try? encoder.encode(keyNames) else { return }
|
||||||
|
userdefaults.set(encodedNames, forKey: "keyNames")
|
||||||
|
userdefaults.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveToKeychain(_ keypair: Keypair) {
|
||||||
|
withAnimation {
|
||||||
|
keyIDs.updateValue(keypair.type, forKey: keypair.id)
|
||||||
|
keyNames.updateValue(keypair.name, forKey: keypair.id)
|
||||||
|
}
|
||||||
|
saveKeyIDs()
|
||||||
|
if keypair.type == .ed25519 {
|
||||||
|
let curve25519 = try! Curve25519.Signing.PrivateKey(rawRepresentation: keypair.privateKey)
|
||||||
|
try! GenericPasswordStore().storeKey(curve25519.genericKeyRepresentation, account: keypair.id.uuidString)
|
||||||
|
} else {
|
||||||
|
let tag = baseTag+keypair.id.uuidString.data(using: .utf8)!
|
||||||
|
let addQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||||
|
kSecAttrApplicationTag as String: tag,
|
||||||
|
kSecValueRef as String: keypair.privateKey,
|
||||||
|
kSecAttrSynchronizable as String: kCFBooleanTrue!]
|
||||||
|
let status = SecItemAdd(addQuery as CFDictionary, nil)
|
||||||
|
guard status == errSecSuccess else { fatalError() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFromKeychain(keyID: UUID) -> Keypair? {
|
||||||
|
guard let keyType = keyIDs[keyID] else { return nil }
|
||||||
|
guard let keyName = keyNames[keyID] else { return nil }
|
||||||
|
if keyType == .ed25519 {
|
||||||
|
var key: Curve25519.Signing.PrivateKey?
|
||||||
|
key = try? GenericPasswordStore().readKey(account: keyID.uuidString)
|
||||||
|
guard let key else { return nil }
|
||||||
|
return Keypair(type: keyType, name: keyName, privateKey: key.rawRepresentation)
|
||||||
|
} else {
|
||||||
|
let tag = baseTag+keyID.uuidString.data(using: .utf8)!
|
||||||
|
let getQuery: [String: Any] = [kSecClass as String: kSecClassKey,
|
||||||
|
kSecAttrApplicationTag as String: tag,
|
||||||
|
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
|
||||||
|
kSecReturnRef as String: true]
|
||||||
|
var item: CFTypeRef?
|
||||||
|
let status = SecItemCopyMatching(getQuery as CFDictionary, &item)
|
||||||
|
guard status == errSecSuccess else { fatalError() }
|
||||||
|
return Keypair(
|
||||||
|
type: keyType,
|
||||||
|
name: keyName,
|
||||||
|
privateKey: item as! Data
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: generate keys
|
//MARK: generate keys
|
||||||
func generateKey(type: KeyType, SEPKeyTag: String, comment: String, passphrase: String) -> Keypair? {
|
func generateKey(type: KeyType, comment: String) {
|
||||||
switch type {
|
switch type {
|
||||||
case .ecdsa:
|
case .ed25519:
|
||||||
Keypair(type: .ecdsa, name: comment, privateKey: Curve25519.Signing.PrivateKey().rawRepresentation)
|
let keypair = Keypair(
|
||||||
|
type: .ed25519,
|
||||||
|
name: comment,
|
||||||
|
privateKey: Curve25519.Signing.PrivateKey().rawRepresentation
|
||||||
|
)
|
||||||
|
saveToKeychain(keypair)
|
||||||
case .rsa:
|
case .rsa:
|
||||||
fatalError("unimplemented")
|
fatalError("unimplemented")
|
||||||
}
|
}
|
||||||
@@ -109,7 +178,7 @@ class KeyManager: ObservableObject {
|
|||||||
|
|
||||||
let comment = String(data: extractField(&dataBlob), encoding: .utf8)!
|
let comment = String(data: extractField(&dataBlob), encoding: .utf8)!
|
||||||
|
|
||||||
return Keypair(type: .ecdsa, name: comment, privateKey: privatekeyData)
|
return Keypair(type: .ed25519, name: comment, privateKey: privatekeyData)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func makeSSHPrivkey(_ keypair: Keypair) -> Data {
|
static func makeSSHPrivkey(_ keypair: Keypair) -> Data {
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum KeyType: Codable, Equatable, Hashable, CustomStringConvertible {
|
enum KeyType: Codable, Equatable, Hashable, CustomStringConvertible {
|
||||||
|
case ed25519
|
||||||
|
case rsa
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .ecdsa:
|
case .ed25519:
|
||||||
return "ECDSA"
|
return "Ed25519"
|
||||||
case .rsa:
|
case .rsa:
|
||||||
return "RSA"
|
return "RSA"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case ecdsa
|
|
||||||
case rsa
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
//
|
|
||||||
// KeychainLayer.swift
|
|
||||||
// ShhShell
|
|
||||||
//
|
|
||||||
// Created by neon443 on 20/06/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
//https://developer.apple.com/documentation/cryptokit/storing-cryptokit-keys-in-the-keychain
|
|
||||||
protocol SecKeyConvertible: CustomStringConvertible {
|
|
||||||
// cretes a ket from an x9.63 represenation
|
|
||||||
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
|
|
||||||
|
|
||||||
//an x9.63 representation of the key
|
|
||||||
var x963Representation: Data { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
protocol GenericPasswordConvertible {
|
|
||||||
//creates key from generic rep
|
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes
|
|
||||||
|
|
||||||
//generic rep of key
|
|
||||||
var genericKeyRepresentation: SymmetricKey { get }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Curve25519.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
|
||||||
try self.init(rawRepresentation: data)
|
|
||||||
}
|
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
|
||||||
self.rawRepresentation.withUnsafeBytes {
|
|
||||||
SymmetricKey(data: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extension Curve25519.Signing.PrivateKey: GenericPasswordConvertible {
|
|
||||||
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
|
||||||
try self.init(rawRepresentation: data)
|
|
||||||
}
|
|
||||||
|
|
||||||
var genericKeyRepresentation: SymmetricKey {
|
|
||||||
self.rawRepresentation.withUnsafeBytes {
|
|
||||||
SymmetricKey(data: $0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KeyStoreError: Error {
|
|
||||||
case KeyStoreError(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
func storeKey<T: SecKeyConvertible>(_ key: T, label: String) throws {
|
|
||||||
let attributes = [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
|
||||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate] as [String: Any]
|
|
||||||
|
|
||||||
guard let secKey = SecKeyCreateWithData(
|
|
||||||
key.x963Representation as CFData,
|
|
||||||
attributes as CFDictionary,
|
|
||||||
nil
|
|
||||||
) else {
|
|
||||||
throw KeyStoreError.KeyStoreError("unable to create SecKey represntation")
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = [kSecClass: kSecClassKey,
|
|
||||||
kSecAttrApplicationLabel: label,
|
|
||||||
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
|
||||||
kSecUseDataProtectionKeychain: true,
|
|
||||||
kSecValueRef: secKey] as [String: Any]
|
|
||||||
let status = SecItemAdd(query as CFDictionary, nil)
|
|
||||||
guard status == errSecSuccess else {
|
|
||||||
throw KeyStoreError.KeyStoreError("unable to sstore item \(status)")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func retrieveKey(label: String) throws -> SecKey? {
|
|
||||||
let query = [kSecClass: kSecClassKey,
|
|
||||||
kSecAttrApplicationLabel: label,
|
|
||||||
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
|
||||||
kSecUseDataProtectionKeychain: true,
|
|
||||||
kSecReturnRef: true] as [String: Any]
|
|
||||||
|
|
||||||
var item: CFTypeRef?
|
|
||||||
var secKey: SecKey
|
|
||||||
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
|
||||||
case errSecSuccess: secKey = item as! SecKey
|
|
||||||
case errSecItemNotFound: return nil
|
|
||||||
case let status:
|
|
||||||
print(status)
|
|
||||||
throw KeyStoreError.KeyStoreError("keychain read failed")
|
|
||||||
}
|
|
||||||
// return secKey
|
|
||||||
|
|
||||||
var error: Unmanaged<CFError>?
|
|
||||||
guard (SecKeyCopyExternalRepresentation(secKey, &error) as Data?) != nil else {
|
|
||||||
throw KeyStoreError.KeyStoreError(error.debugDescription)
|
|
||||||
}
|
|
||||||
// let key = try T(x963Representation: data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
84
ShhShell/Keys/KeychainLayer/GenericPasswordConvertible.swift
Normal file
84
ShhShell/Keys/KeychainLayer/GenericPasswordConvertible.swift
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
The interface required for conversion to a generic password keychain item.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
/// The interface needed for SecKey conversion.
|
||||||
|
protocol GenericPasswordConvertible: CustomStringConvertible {
|
||||||
|
/// Creates a key from a generic key representation.
|
||||||
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes
|
||||||
|
|
||||||
|
/// A generic representation of the key.
|
||||||
|
var genericKeyRepresentation: SymmetricKey { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GenericPasswordConvertible {
|
||||||
|
/// A string version of the key for visual inspection.
|
||||||
|
/// IMPORTANT: Never log the actual key data.
|
||||||
|
public var description: String {
|
||||||
|
return self.genericKeyRepresentation.withUnsafeBytes { bytes in
|
||||||
|
return "Key representation contains \(bytes.count) bytes."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare that the Curve25519 keys are generic passord convertible.
|
||||||
|
extension Curve25519.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
||||||
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
|
try self.init(rawRepresentation: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
|
self.rawRepresentation.withUnsafeBytes {
|
||||||
|
SymmetricKey(data: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extension Curve25519.Signing.PrivateKey: GenericPasswordConvertible {
|
||||||
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
|
try self.init(rawRepresentation: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
|
self.rawRepresentation.withUnsafeBytes {
|
||||||
|
SymmetricKey(data: $0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that SymmetricKey is generic password convertible.
|
||||||
|
extension SymmetricKey: GenericPasswordConvertible {
|
||||||
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
|
self.init(data: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that Secure Enclave keys are generic password convertible.
|
||||||
|
extension SecureEnclave.P256.KeyAgreement.PrivateKey: GenericPasswordConvertible {
|
||||||
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
|
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
|
return SymmetricKey(data: dataRepresentation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SecureEnclave.P256.Signing.PrivateKey: GenericPasswordConvertible {
|
||||||
|
init<D>(genericKeyRepresentation data: D) throws where D: ContiguousBytes {
|
||||||
|
try self.init(dataRepresentation: data.withUnsafeBytes { Data($0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
var genericKeyRepresentation: SymmetricKey {
|
||||||
|
return SymmetricKey(data: dataRepresentation)
|
||||||
|
}
|
||||||
|
}
|
||||||
83
ShhShell/Keys/KeychainLayer/GenericPasswordStore.swift
Normal file
83
ShhShell/Keys/KeychainLayer/GenericPasswordStore.swift
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
Methods for storing generic password convertible items in the keychain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
import Security
|
||||||
|
|
||||||
|
struct GenericPasswordStore {
|
||||||
|
|
||||||
|
/// Stores a CryptoKit key in the keychain as a generic password.
|
||||||
|
func storeKey<T: GenericPasswordConvertible>(_ key: T, account: String) throws {
|
||||||
|
|
||||||
|
// Treat the key data as a generic password.
|
||||||
|
try key.genericKeyRepresentation.withUnsafeBytes { keyBytes in
|
||||||
|
let cfd = Data(bytesNoCopy: UnsafeMutableRawPointer(mutating: keyBytes.baseAddress!), count: keyBytes.count, deallocator: .none)
|
||||||
|
let query = [kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrAccount: account,
|
||||||
|
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
||||||
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecValueData: cfd] as [String: Any]
|
||||||
|
|
||||||
|
// Add the key data.
|
||||||
|
let status = SecItemAdd(query as CFDictionary, nil)
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeyStoreError("Unable to store item: \(status.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a CryptoKit key from the keychain as a generic password.
|
||||||
|
func readKey<T: GenericPasswordConvertible>(account: String) throws -> T? {
|
||||||
|
|
||||||
|
// Seek a generic password with the given account.
|
||||||
|
let query = [kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrAccount: account,
|
||||||
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecReturnData: true] as [String: Any]
|
||||||
|
|
||||||
|
// Find and cast the result as data.
|
||||||
|
var item: CFTypeRef?
|
||||||
|
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
||||||
|
case errSecSuccess:
|
||||||
|
guard let data = item as? Data else { return nil }
|
||||||
|
return try T(genericKeyRepresentation: data) // Convert back to a key.
|
||||||
|
case errSecItemNotFound: return nil
|
||||||
|
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a key in the keychain and then reads it back.
|
||||||
|
func roundTrip<T: GenericPasswordConvertible>(_ key: T) throws -> T {
|
||||||
|
|
||||||
|
// An account name for the key in the keychain.
|
||||||
|
let account = "com.example.genericpassword.key"
|
||||||
|
|
||||||
|
// Start fresh.
|
||||||
|
try deleteKey(account: account)
|
||||||
|
|
||||||
|
// Store and read it back.
|
||||||
|
try storeKey(key, account: account)
|
||||||
|
guard let key: T = try readKey(account: account) else {
|
||||||
|
throw KeyStoreError("Failed to locate stored key.")
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes any existing key with the given account.
|
||||||
|
func deleteKey(account: String) throws {
|
||||||
|
let query = [kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecAttrAccount: account] as [String: Any]
|
||||||
|
switch SecItemDelete(query as CFDictionary) {
|
||||||
|
case errSecItemNotFound, errSecSuccess: break // Okay to ignore
|
||||||
|
case let status:
|
||||||
|
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
ShhShell/Keys/KeychainLayer/KeyStoreError.swift
Normal file
29
ShhShell/Keys/KeychainLayer/KeyStoreError.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
Errors that can be generated as a result of attempting to store keys.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// An error we can throw when something goes wrong.
|
||||||
|
struct KeyStoreError: Error, CustomStringConvertible {
|
||||||
|
var message: String
|
||||||
|
|
||||||
|
init(_ message: String) {
|
||||||
|
self.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension OSStatus {
|
||||||
|
|
||||||
|
/// A human readable message for the status.
|
||||||
|
var message: String {
|
||||||
|
return (SecCopyErrorMessageString(self, nil) as String?) ?? String(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
ShhShell/Keys/KeychainLayer/SecKeyConvertible.swift
Normal file
36
ShhShell/Keys/KeychainLayer/SecKeyConvertible.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
The interface required for conversion to a SecKey instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
/// The interface needed for SecKey conversion.
|
||||||
|
protocol SecKeyConvertible: CustomStringConvertible {
|
||||||
|
/// Creates a key from an X9.63 representation.
|
||||||
|
init<Bytes>(x963Representation: Bytes) throws where Bytes: ContiguousBytes
|
||||||
|
|
||||||
|
/// An X9.63 representation of the key.
|
||||||
|
var x963Representation: Data { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SecKeyConvertible {
|
||||||
|
/// A string version of the key for visual inspection.
|
||||||
|
/// IMPORTANT: Never log the actual key data.
|
||||||
|
public var description: String {
|
||||||
|
return self.x963Representation.withUnsafeBytes { bytes in
|
||||||
|
return "Key representation contains \(bytes.count) bytes."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the NIST keys are convertible.
|
||||||
|
extension P256.Signing.PrivateKey: SecKeyConvertible {}
|
||||||
|
extension P256.KeyAgreement.PrivateKey: SecKeyConvertible {}
|
||||||
|
extension P384.Signing.PrivateKey: SecKeyConvertible {}
|
||||||
|
extension P384.KeyAgreement.PrivateKey: SecKeyConvertible {}
|
||||||
|
extension P521.Signing.PrivateKey: SecKeyConvertible {}
|
||||||
|
extension P521.KeyAgreement.PrivateKey: SecKeyConvertible {}
|
||||||
99
ShhShell/Keys/KeychainLayer/SecKeyStore.swift
Normal file
99
ShhShell/Keys/KeychainLayer/SecKeyStore.swift
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
See the LICENSE.txt file for this sample’s licensing information.
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
Methods for storing SecKey convertible items in the keychain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
import Security
|
||||||
|
|
||||||
|
struct SecKeyStore {
|
||||||
|
|
||||||
|
/// Stores a CryptoKit key in the keychain as a SecKey instance.
|
||||||
|
func storeKey<T: SecKeyConvertible>(_ key: T, label: String) throws {
|
||||||
|
|
||||||
|
// Describe the key.
|
||||||
|
let attributes = [kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
|
kSecAttrKeyClass: kSecAttrKeyClassPrivate] as [String: Any]
|
||||||
|
|
||||||
|
// Get a SecKey representation.
|
||||||
|
guard let secKey = SecKeyCreateWithData(key.x963Representation as CFData,
|
||||||
|
attributes as CFDictionary,
|
||||||
|
nil)
|
||||||
|
else {
|
||||||
|
throw KeyStoreError("Unable to create SecKey representation.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe the add operation.
|
||||||
|
let query = [kSecClass: kSecClassKey,
|
||||||
|
kSecAttrApplicationLabel: label,
|
||||||
|
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
||||||
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecValueRef: secKey] as [String: Any]
|
||||||
|
|
||||||
|
// Add the key to the keychain.
|
||||||
|
let status = SecItemAdd(query as CFDictionary, nil)
|
||||||
|
guard status == errSecSuccess else {
|
||||||
|
throw KeyStoreError("Unable to store item: \(status.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a CryptoKit key from the keychain as a SecKey instance.
|
||||||
|
func readKey<T: SecKeyConvertible>(label: String) throws -> T? {
|
||||||
|
|
||||||
|
// Seek an elliptic-curve key with a given label.
|
||||||
|
let query = [kSecClass: kSecClassKey,
|
||||||
|
kSecAttrApplicationLabel: label,
|
||||||
|
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||||
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecReturnRef: true] as [String: Any]
|
||||||
|
|
||||||
|
// Find and cast the result as a SecKey instance.
|
||||||
|
var item: CFTypeRef?
|
||||||
|
var secKey: SecKey
|
||||||
|
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
||||||
|
case errSecSuccess: secKey = item as! SecKey
|
||||||
|
case errSecItemNotFound: return nil
|
||||||
|
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the SecKey into a CryptoKit key.
|
||||||
|
var error: Unmanaged<CFError>?
|
||||||
|
guard let data = SecKeyCopyExternalRepresentation(secKey, &error) as Data? else {
|
||||||
|
throw KeyStoreError(error.debugDescription)
|
||||||
|
}
|
||||||
|
let key = try T(x963Representation: data)
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores a key in the keychain and then reads it back.
|
||||||
|
func roundTrip<T: SecKeyConvertible>(_ key: T) throws -> T {
|
||||||
|
// A label for the key in the keychain.
|
||||||
|
let label = "com.example.seckey.key"
|
||||||
|
|
||||||
|
// Start fresh.
|
||||||
|
try deleteKey(label: label)
|
||||||
|
|
||||||
|
// Store it and then get it back.
|
||||||
|
try storeKey(key, label: label)
|
||||||
|
guard let key: T = try readKey(label: label) else {
|
||||||
|
throw KeyStoreError("Failed to locate stored key.")
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes any existing key with the given label.
|
||||||
|
func deleteKey(label: String) throws {
|
||||||
|
let query = [kSecClass: kSecClassKey,
|
||||||
|
kSecUseDataProtectionKeychain: true,
|
||||||
|
kSecAttrApplicationLabel: label] as [String: Any]
|
||||||
|
switch SecItemDelete(query as CFDictionary) {
|
||||||
|
case errSecItemNotFound, errSecSuccess: break // Ignore these.
|
||||||
|
case let status:
|
||||||
|
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ protocol KeypairProtocol: Identifiable, Equatable, Codable, Hashable {
|
|||||||
|
|
||||||
struct Keypair: KeypairProtocol {
|
struct Keypair: KeypairProtocol {
|
||||||
var id = UUID()
|
var id = UUID()
|
||||||
var type: KeyType = .ecdsa
|
var type: KeyType = .ed25519
|
||||||
var name: String = ""
|
var name: String = ""
|
||||||
var publicKey: Data {
|
var publicKey: Data {
|
||||||
(try? Curve25519.Signing.PrivateKey(rawRepresentation: privateKey).publicKey.rawRepresentation) ?? Data()
|
(try? Curve25519.Signing.PrivateKey(rawRepresentation: privateKey).publicKey.rawRepresentation) ?? Data()
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ import CryptoKit
|
|||||||
KeyDetailView(
|
KeyDetailView(
|
||||||
hostsManager: HostsManager(),
|
hostsManager: HostsManager(),
|
||||||
keypair: Keypair(
|
keypair: Keypair(
|
||||||
type: .ecdsa,
|
type: .ed25519,
|
||||||
name: "previewKey",
|
name: "previewKey",
|
||||||
privateKey: Curve25519.Signing.PrivateKey().rawRepresentation
|
privateKey: Curve25519.Signing.PrivateKey().rawRepresentation
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,16 +22,18 @@ struct KeyManagerView: View {
|
|||||||
NavigationLink {
|
NavigationLink {
|
||||||
KeyDetailView(hostsManager: hostsManager, keypair: keypair)
|
KeyDetailView(hostsManager: hostsManager, keypair: keypair)
|
||||||
} label: {
|
} label: {
|
||||||
Text(String(data: keypair.publicKey, encoding: .utf8) ?? "nil")
|
Text(keypair.openSshPubkey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button("ed25519") {
|
Section() {
|
||||||
|
ForEach(keyManager.keypairs) { kp in
|
||||||
|
Text(kp.openSshPubkey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button("genereate rsa") {
|
Button("ed25519") {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user