mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 05:19:13 +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, ); }; };
|
||||
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, ); }; };
|
||||
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 */; };
|
||||
/* 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -153,8 +159,10 @@
|
||||
children = (
|
||||
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
||||
A92538C62DEE0742007E0A18 /* ShhShellApp.swift */,
|
||||
A92538D32DEE0749007E0A18 /* Views */,
|
||||
A98554572E055398009051BD /* Keys */,
|
||||
A98554562E055394009051BD /* Host */,
|
||||
A93143C12DF61E8500FCD5DB /* SSH */,
|
||||
A92538D32DEE0749007E0A18 /* Views */,
|
||||
);
|
||||
path = ShhShell;
|
||||
sourceTree = "<group>";
|
||||
@@ -179,9 +187,10 @@
|
||||
A92538D32DEE0749007E0A18 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A98554532E05534F009051BD /* Keys */,
|
||||
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
||||
A91AE3B12DF73E0900FF3537 /* TerminalView.swift */,
|
||||
A91AE3BC2DF7402100FF3537 /* TextViewController.swift */,
|
||||
A985545C2E055D4D009051BD /* ConnectionView.swift */,
|
||||
A98554522E055347009051BD /* Terminal */,
|
||||
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
|
||||
);
|
||||
path = Views;
|
||||
@@ -198,7 +207,6 @@
|
||||
A93143C12DF61E8500FCD5DB /* SSH */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A93143BF2DF61B3200FCD5DB /* Host.swift */,
|
||||
A9C897EE2DF1A9A400EF9A5F /* SSHHandler.swift */,
|
||||
);
|
||||
path = SSH;
|
||||
@@ -214,6 +222,39 @@
|
||||
path = ci_scripts;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -376,9 +417,12 @@
|
||||
A92538C82DEE0742007E0A18 /* ContentView.swift in Sources */,
|
||||
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
||||
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
|
||||
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
||||
A91AE3B22DF73E0900FF3537 /* TerminalView.swift in Sources */,
|
||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||
A91AE3BD2DF7402100FF3537 /* TextViewController.swift in Sources */,
|
||||
A9C897EF2DF1A9A400EF9A5F /* SSHHandler.swift in Sources */,
|
||||
A98554552E05535F009051BD /* KeyManagerView.swift in Sources */,
|
||||
);
|
||||
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
|
||||
struct ShhShellApp: App {
|
||||
@StateObject var sshHandler: SSHHandler = SSHHandler(host: blankHost())
|
||||
@StateObject var keyManager: KeyManager = KeyManager()
|
||||
|
||||
var body: some Scene {
|
||||
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 {
|
||||
@ObservedObject var handler: SSHHandler
|
||||
|
||||
@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
|
||||
|
||||
@ObservedObject var keyManager: KeyManager
|
||||
|
||||
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))
|
||||
TabView {
|
||||
ConnectionView(
|
||||
handler: handler,
|
||||
keyManager: keyManager
|
||||
)
|
||||
.tabItem {
|
||||
Label("Connection", systemImage: "powerplug.portrait")
|
||||
}
|
||||
.transition(.opacity)
|
||||
KeyManagerView(keyManager: keyManager)
|
||||
.tabItem {
|
||||
Label("Keys", systemImage: "key.2.on.ring")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
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