mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
added a keymanager and keymanagerview, can ontly generate 4096 rsa rn
uses seckey
This commit is contained in:
@@ -29,6 +29,9 @@
|
|||||||
A95FAA552DF4B62900DE2F5A /* LibSSH.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA502DF4B62100DE2F5A /* LibSSH.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
A95FAA552DF4B62900DE2F5A /* LibSSH.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA502DF4B62100DE2F5A /* LibSSH.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; };
|
A95FAA562DF4B62A00DE2F5A /* openssl.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; };
|
||||||
A95FAA572DF4B62A00DE2F5A /* openssl.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
A95FAA572DF4B62A00DE2F5A /* openssl.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554542E05535F009051BD /* KeyManagerView.swift */; };
|
||||||
|
A98554592E0553AA009051BD /* KeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98554582E0553AA009051BD /* KeyManager.swift */; };
|
||||||
|
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A985545C2E055D4D009051BD /* ConnectionView.swift */; };
|
||||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -87,6 +90,9 @@
|
|||||||
A95FAA5A2DF4B79900DE2F5A /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
|
A95FAA5A2DF4B79900DE2F5A /* ci_post_clone.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = "<group>"; };
|
||||||
A95FAA5B2DF4B7A000DE2F5A /* ci_pre_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_pre_xcodebuild.sh; sourceTree = "<group>"; };
|
A95FAA5B2DF4B7A000DE2F5A /* ci_pre_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_pre_xcodebuild.sh; sourceTree = "<group>"; };
|
||||||
A95FAA5C2DF4B7A300DE2F5A /* ci_prost_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_prost_xcodebuild.sh; sourceTree = "<group>"; };
|
A95FAA5C2DF4B7A300DE2F5A /* ci_prost_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_prost_xcodebuild.sh; 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>"; };
|
||||||
|
A985545C2E055D4D009051BD /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = "<group>"; };
|
||||||
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
|
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHHandler.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -153,8 +159,10 @@
|
|||||||
children = (
|
children = (
|
||||||
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
||||||
A92538C62DEE0742007E0A18 /* ShhShellApp.swift */,
|
A92538C62DEE0742007E0A18 /* ShhShellApp.swift */,
|
||||||
A92538D32DEE0749007E0A18 /* Views */,
|
A98554572E055398009051BD /* Keys */,
|
||||||
|
A98554562E055394009051BD /* Host */,
|
||||||
A93143C12DF61E8500FCD5DB /* SSH */,
|
A93143C12DF61E8500FCD5DB /* SSH */,
|
||||||
|
A92538D32DEE0749007E0A18 /* Views */,
|
||||||
);
|
);
|
||||||
path = ShhShell;
|
path = ShhShell;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -179,9 +187,10 @@
|
|||||||
A92538D32DEE0749007E0A18 /* Views */ = {
|
A92538D32DEE0749007E0A18 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A98554532E05534F009051BD /* Keys */,
|
||||||
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
||||||
A91AE3B12DF73E0900FF3537 /* TerminalView.swift */,
|
A985545C2E055D4D009051BD /* ConnectionView.swift */,
|
||||||
A91AE3BC2DF7402100FF3537 /* TextViewController.swift */,
|
A98554522E055347009051BD /* Terminal */,
|
||||||
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
|
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@@ -198,7 +207,6 @@
|
|||||||
A93143C12DF61E8500FCD5DB /* SSH */ = {
|
A93143C12DF61E8500FCD5DB /* SSH */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
A93143BF2DF61B3200FCD5DB /* Host.swift */,
|
|
||||||
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */,
|
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */,
|
||||||
);
|
);
|
||||||
path = SSH;
|
path = SSH;
|
||||||
@@ -214,6 +222,39 @@
|
|||||||
path = ci_scripts;
|
path = ci_scripts;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A98554522E055347009051BD /* Terminal */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A91AE3B12DF73E0900FF3537 /* TerminalView.swift */,
|
||||||
|
A91AE3BC2DF7402100FF3537 /* TextViewController.swift */,
|
||||||
|
);
|
||||||
|
path = Terminal;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A98554532E05534F009051BD /* Keys */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A98554542E05535F009051BD /* KeyManagerView.swift */,
|
||||||
|
);
|
||||||
|
path = Keys;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A98554562E055394009051BD /* Host */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A93143BF2DF61B3200FCD5DB /* Host.swift */,
|
||||||
|
);
|
||||||
|
path = Host;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A98554572E055398009051BD /* Keys */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A98554582E0553AA009051BD /* KeyManager.swift */,
|
||||||
|
);
|
||||||
|
path = Keys;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A9C8976F2DF1980900EF9A5F /* Frameworks */ = {
|
A9C8976F2DF1980900EF9A5F /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -376,9 +417,12 @@
|
|||||||
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
|
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
|
||||||
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
||||||
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
|
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
|
||||||
|
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
||||||
A91AE3B22DF73E0900FF3537 /* TerminalView.swift in Sources */,
|
A91AE3B22DF73E0900FF3537 /* TerminalView.swift in Sources */,
|
||||||
|
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||||
A91AE3BD2DF7402100FF3537 /* TextViewController.swift in Sources */,
|
A91AE3BD2DF7402100FF3537 /* TextViewController.swift in Sources */,
|
||||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||||
|
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
38
ShhShell/Keys/KeyManager.swift
Normal file
38
ShhShell/Keys/KeyManager.swift
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// KeyManager.swift
|
||||||
|
// ShhShell
|
||||||
|
//
|
||||||
|
// Created by neon443 on 20/06/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Key: Identifiable, Hashable {
|
||||||
|
var id = UUID()
|
||||||
|
var privateKey: SecKey
|
||||||
|
var publicKey: SecKey {
|
||||||
|
SecKeyCopyPublicKey(privateKey)!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyManager: ObservableObject {
|
||||||
|
func generateRSA() throws {
|
||||||
|
let type = kSecAttrKeyTypeRSA
|
||||||
|
let tag = "com.neon443.ShhSell.keys.\(Date().timeIntervalSince1970)".data(using: .utf8)!
|
||||||
|
let attributes: [String: Any] =
|
||||||
|
[kSecAttrKeyType as String: type,
|
||||||
|
kSecAttrKeySizeInBits as String: 4096,
|
||||||
|
kSecPrivateKeyAttrs as String:
|
||||||
|
[kSecAttrIsPermanent as String: true,
|
||||||
|
kSecAttrApplicationTag as String: tag]
|
||||||
|
]
|
||||||
|
|
||||||
|
var error: Unmanaged<CFError>?
|
||||||
|
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
|
||||||
|
throw error!.takeRetainedValue() as Error
|
||||||
|
}
|
||||||
|
print(privateKey)
|
||||||
|
|
||||||
|
print(SecKeyCopyPublicKey(privateKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,14 @@ import SwiftUI
|
|||||||
@main
|
@main
|
||||||
struct ShhShellApp: App {
|
struct ShhShellApp: App {
|
||||||
@StateObject var sshHandler: SSHHandler = SSHHandler(host: blankHost())
|
@StateObject var sshHandler: SSHHandler = SSHHandler(host: blankHost())
|
||||||
|
@StateObject var keyManager: KeyManager = KeyManager()
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView(handler: sshHandler)
|
ContentView(
|
||||||
|
handler: sshHandler,
|
||||||
|
keyManager: keyManager
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
162
ShhShell/Views/ConnectionView.swift
Normal file
162
ShhShell/Views/ConnectionView.swift
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
//
|
||||||
|
// ConnectionView.swift
|
||||||
|
// ShhShell
|
||||||
|
//
|
||||||
|
// Created by neon443 on 20/06/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ConnectionView: View {
|
||||||
|
@StateObject var handler: SSHHandler
|
||||||
|
@StateObject var keyManager: KeyManager
|
||||||
|
|
||||||
|
@State var passphrase: String = ""
|
||||||
|
|
||||||
|
@State var pubkeyStr: String = ""
|
||||||
|
@State var privkeyStr: String = ""
|
||||||
|
|
||||||
|
@State var pubkey: Data?
|
||||||
|
@State var privkey: Data?
|
||||||
|
|
||||||
|
@State var privPickerPresented: Bool = false
|
||||||
|
@State var pubPickerPresented: Bool = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
List {
|
||||||
|
HStack {
|
||||||
|
TextField("", text: $pubkeyStr, prompt: Text("Public Key"))
|
||||||
|
.onSubmit {
|
||||||
|
pubkey = Data(pubkeyStr.utf8)
|
||||||
|
}
|
||||||
|
Button() {
|
||||||
|
pubPickerPresented.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "folder")
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.fileImporter(isPresented: $pubPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
|
||||||
|
do {
|
||||||
|
let fileURL = try Result.get()
|
||||||
|
pubkey = try! Data(contentsOf: fileURL)
|
||||||
|
print(fileURL)
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
TextField("", text: $privkeyStr, prompt: Text("Private Key"))
|
||||||
|
.onSubmit {
|
||||||
|
privkey = Data(privkeyStr.utf8)
|
||||||
|
}
|
||||||
|
Button() {
|
||||||
|
privPickerPresented.toggle()
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "folder")
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.fileImporter(isPresented: $privPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
|
||||||
|
do {
|
||||||
|
let fileURL = try Result.get()
|
||||||
|
privkey = try! Data(contentsOf: fileURL)
|
||||||
|
print(fileURL)
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField("", text: $passphrase)
|
||||||
|
HStack {
|
||||||
|
Text(handler.connected ? "connected" : "not connected")
|
||||||
|
.modifier(foregroundColorStyle(handler.connected ? .green : .red))
|
||||||
|
|
||||||
|
Text(handler.authorized ? "authorized" : "unauthorized")
|
||||||
|
.modifier(foregroundColorStyle(handler.authorized ? .green : .red))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if let testSucceded = testSucceded {
|
||||||
|
// Image(systemName: testSucceded ? "checkmark.circle" : "xmark.circle")
|
||||||
|
// .modifier(foregroundColorStyle(testSucceded ? .green : .red))
|
||||||
|
// }
|
||||||
|
|
||||||
|
if handler.host.key != nil {
|
||||||
|
Text("Hostkey: \(handler.host.key!.base64EncodedString())")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField("address", text: $handler.host.address)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
"port",
|
||||||
|
text: Binding(
|
||||||
|
get: { String(handler.host.port) },
|
||||||
|
set: { handler.host.port = Int($0) ?? 22} )
|
||||||
|
)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
|
||||||
|
TextField("username", text: $handler.host.username)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
|
||||||
|
SecureField("password", text: $handler.host.password)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
|
||||||
|
if handler.connected {
|
||||||
|
Button() {
|
||||||
|
handler.disconnect()
|
||||||
|
} label: {
|
||||||
|
Label("Disconnect", systemImage: "xmark.app.fill")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button() {
|
||||||
|
handler.connect()
|
||||||
|
if pubkey != nil && privkey != nil {
|
||||||
|
handler.authWithPubkey(pub: pubkey!, priv: privkey!, pass: passphrase)
|
||||||
|
} else {
|
||||||
|
let _ = handler.authWithPw()
|
||||||
|
}
|
||||||
|
handler.openShell()
|
||||||
|
} label: {
|
||||||
|
Label("Connect", systemImage: "powerplug.portrait")
|
||||||
|
}
|
||||||
|
.disabled(
|
||||||
|
pubkey == nil && privkey == nil ||
|
||||||
|
handler.host.username.isEmpty && handler.host.password.isEmpty
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink() {
|
||||||
|
TerminalView(handler: handler)
|
||||||
|
} label: {
|
||||||
|
Label("Open Terminal", systemImage: "apple.terminal")
|
||||||
|
}
|
||||||
|
.disabled(!(handler.connected && handler.authorized))
|
||||||
|
|
||||||
|
Button() {
|
||||||
|
withAnimation { handler.testExec() }
|
||||||
|
} label: {
|
||||||
|
if handler.testSuceeded {
|
||||||
|
Image(systemName: handler.testSuceeded ? "checkmark.circle" : "xmark.circle")
|
||||||
|
.modifier(foregroundColorStyle(handler.testSuceeded ? .green : .red))
|
||||||
|
} else {
|
||||||
|
Label("Test Connection", systemImage: "checkmark")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(!(handler.connected && handler.authorized))
|
||||||
|
}
|
||||||
|
.transition(.opacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ConnectionView(
|
||||||
|
handler: SSHHandler(host: debugHost()),
|
||||||
|
keyManager: KeyManager()
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,151 +9,28 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@ObservedObject var handler: SSHHandler
|
@ObservedObject var handler: SSHHandler
|
||||||
|
@ObservedObject var keyManager: KeyManager
|
||||||
@State var passphrase: String = ""
|
|
||||||
|
|
||||||
@State var pubkeyStr: String = ""
|
|
||||||
@State var privkeyStr: String = ""
|
|
||||||
|
|
||||||
@State var pubkey: Data?
|
|
||||||
@State var privkey: Data?
|
|
||||||
|
|
||||||
@State var privPickerPresented: Bool = false
|
|
||||||
@State var pubPickerPresented: Bool = false
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
TabView {
|
||||||
List {
|
ConnectionView(
|
||||||
HStack {
|
handler: handler,
|
||||||
TextField("", text: $pubkeyStr, prompt: Text("Public Key"))
|
keyManager: keyManager
|
||||||
.onSubmit {
|
)
|
||||||
pubkey = Data(pubkeyStr.utf8)
|
.tabItem {
|
||||||
}
|
Label("Connection", systemImage: "powerplug.portrait")
|
||||||
Button() {
|
|
||||||
pubPickerPresented.toggle()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "folder")
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.fileImporter(isPresented: $pubPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
|
|
||||||
do {
|
|
||||||
let fileURL = try Result.get()
|
|
||||||
pubkey = try! Data(contentsOf: fileURL)
|
|
||||||
print(fileURL)
|
|
||||||
} catch {
|
|
||||||
print(error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
TextField("", text: $privkeyStr, prompt: Text("Private Key"))
|
|
||||||
.onSubmit {
|
|
||||||
privkey = Data(privkeyStr.utf8)
|
|
||||||
}
|
|
||||||
Button() {
|
|
||||||
privPickerPresented.toggle()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "folder")
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.fileImporter(isPresented: $privPickerPresented, allowedContentTypes: [.item, .content, .data]) { (Result) in
|
|
||||||
do {
|
|
||||||
let fileURL = try Result.get()
|
|
||||||
privkey = try! Data(contentsOf: fileURL)
|
|
||||||
print(fileURL)
|
|
||||||
} catch {
|
|
||||||
print(error.localizedDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField("", text: $passphrase)
|
|
||||||
HStack {
|
|
||||||
Text(handler.connected ? "connected" : "not connected")
|
|
||||||
.modifier(foregroundColorStyle(handler.connected ? .green : .red))
|
|
||||||
|
|
||||||
Text(handler.authorized ? "authorized" : "unauthorized")
|
|
||||||
.modifier(foregroundColorStyle(handler.authorized ? .green : .red))
|
|
||||||
}
|
|
||||||
|
|
||||||
// if let testSucceded = testSucceded {
|
|
||||||
// Image(systemName: testSucceded ? "checkmark.circle" : "xmark.circle")
|
|
||||||
// .modifier(foregroundColorStyle(testSucceded ? .green : .red))
|
|
||||||
// }
|
|
||||||
|
|
||||||
if handler.host.key != nil {
|
|
||||||
Text("Hostkey: \(handler.host.key!.base64EncodedString())")
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField("address", text: $handler.host.address)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
|
|
||||||
TextField(
|
|
||||||
"port",
|
|
||||||
text: Binding(
|
|
||||||
get: { String(handler.host.port) },
|
|
||||||
set: { handler.host.port = Int($0) ?? 22} )
|
|
||||||
)
|
|
||||||
.keyboardType(.numberPad)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
|
|
||||||
TextField("username", text: $handler.host.username)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
|
|
||||||
SecureField("password", text: $handler.host.password)
|
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
|
|
||||||
if handler.connected {
|
|
||||||
Button() {
|
|
||||||
handler.disconnect()
|
|
||||||
} label: {
|
|
||||||
Label("Disconnect", systemImage: "xmark.app.fill")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button() {
|
|
||||||
handler.connect()
|
|
||||||
if pubkey != nil && privkey != nil {
|
|
||||||
handler.authWithPubkey(pub: pubkey!, priv: privkey!, pass: passphrase)
|
|
||||||
} else {
|
|
||||||
let _ = handler.authWithPw()
|
|
||||||
}
|
|
||||||
handler.openShell()
|
|
||||||
} label: {
|
|
||||||
Label("Connect", systemImage: "powerplug.portrait")
|
|
||||||
}
|
|
||||||
.disabled(
|
|
||||||
pubkey == nil && privkey == nil ||
|
|
||||||
handler.host.username.isEmpty && handler.host.password.isEmpty
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NavigationLink() {
|
|
||||||
TerminalView(handler: handler)
|
|
||||||
} label: {
|
|
||||||
Label("Open Terminal", systemImage: "apple.terminal")
|
|
||||||
}
|
|
||||||
.disabled(!(handler.connected && handler.authorized))
|
|
||||||
|
|
||||||
Button() {
|
|
||||||
withAnimation { handler.testExec() }
|
|
||||||
} label: {
|
|
||||||
if handler.testSuceeded {
|
|
||||||
Image(systemName: handler.testSuceeded ? "checkmark.circle" : "xmark.circle")
|
|
||||||
.modifier(foregroundColorStyle(handler.testSuceeded ? .green : .red))
|
|
||||||
} else {
|
|
||||||
Label("Test Connection", systemImage: "checkmark")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disabled(!(handler.connected && handler.authorized))
|
|
||||||
}
|
}
|
||||||
.transition(.opacity)
|
KeyManagerView(keyManager: keyManager)
|
||||||
|
.tabItem {
|
||||||
|
Label("Keys", systemImage: "key.2.on.ring")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ContentView(
|
ContentView(
|
||||||
handler: SSHHandler(host: debugHost())
|
handler: SSHHandler(host: debugHost()),
|
||||||
|
keyManager: KeyManager()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
33
ShhShell/Views/Keys/KeyManagerView.swift
Normal file
33
ShhShell/Views/Keys/KeyManagerView.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// KeyManagerView.swift
|
||||||
|
// ShhShell
|
||||||
|
//
|
||||||
|
// Created by neon443 on 20/06/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct KeyManagerView: View {
|
||||||
|
@ObservedObject var keyManager: KeyManager
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button("ed25519") {
|
||||||
|
do {
|
||||||
|
try keyManager.generateEd25519()
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Button("rsa") {
|
||||||
|
// do {
|
||||||
|
// try keyManager.generateRSA()
|
||||||
|
// } catch {
|
||||||
|
// print(error.localizedDescription)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
KeyManagerView(keyManager: KeyManager())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user