mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 13:26:16 +00:00
added keydetail view
added faceid authentication to view private keys added authwithbiometrics for secure displaying of sensitive data fix getkeys returns nothing added centredlabel (hstack with two spacers) made keypair identifieable fix alignment of text
This commit is contained in:
@@ -27,6 +27,8 @@
|
||||
A95FAA572DF4B62A00DE2F5A /* openssl.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A95FAA512DF4B62100DE2F5A /* openssl.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
A96C6A8A2E0C0B1100F377FE /* SSHState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6A892E0C0B1100F377FE /* SSHState.swift */; };
|
||||
A96C6AFE2E0C43B600F377FE /* Keypair.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFD2E0C43B600F377FE /* Keypair.swift */; };
|
||||
A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */; };
|
||||
A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96C6B012E0C49E800F377FE /* CenteredLabel.swift */; };
|
||||
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 */; };
|
||||
@@ -97,6 +99,8 @@
|
||||
A95FAA5C2DF4B7A300DE2F5A /* ci_prost_xcodebuild.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ci_prost_xcodebuild.sh; sourceTree = "<group>"; };
|
||||
A96C6A892E0C0B1100F377FE /* SSHState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHState.swift; sourceTree = "<group>"; };
|
||||
A96C6AFD2E0C43B600F377FE /* Keypair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keypair.swift; sourceTree = "<group>"; };
|
||||
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyDetailView.swift; sourceTree = "<group>"; };
|
||||
A96C6B012E0C49E800F377FE /* CenteredLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenteredLabel.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>"; };
|
||||
A985545C2E055D4D009051BD /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = "<group>"; };
|
||||
@@ -267,6 +271,8 @@
|
||||
children = (
|
||||
A98554582E0553AA009051BD /* KeyManager.swift */,
|
||||
A985545E2E056EDD009051BD /* KeychainLayer.swift */,
|
||||
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */,
|
||||
A96C6B012E0C49E800F377FE /* CenteredLabel.swift */,
|
||||
);
|
||||
path = Keys;
|
||||
sourceTree = "<group>";
|
||||
@@ -424,6 +430,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A96C6B022E0C49E800F377FE /* CenteredLabel.swift in Sources */,
|
||||
A923172F2E08851200ECE1E6 /* ShellView.swift in Sources */,
|
||||
A985545F2E056EDD009051BD /* KeychainLayer.swift in Sources */,
|
||||
A93143C62DF61FE300FCD5DB /* ViewModifiers.swift in Sources */,
|
||||
@@ -433,6 +440,7 @@
|
||||
A93143C02DF61B3200FCD5DB /* Host.swift in Sources */,
|
||||
A9B15A9A2E0ABA0400F66E02 /* DialogView.swift in Sources */,
|
||||
A92538C92DEE0742007E0A18 /* ShhShellApp.swift in Sources */,
|
||||
A96C6B002E0C45FE00F377FE /* KeyDetailView.swift in Sources */,
|
||||
A98554612E058433009051BD /* HostsManager.swift in Sources */,
|
||||
A985545D2E055D4D009051BD /* ConnectionView.swift in Sources */,
|
||||
A98554592E0553AA009051BD /* KeyManager.swift in Sources */,
|
||||
@@ -613,6 +621,7 @@
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "ShhShell uses Face ID to verify your identity";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = _;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
@@ -649,6 +658,7 @@
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
|
||||
INFOPLIST_KEY_NSFaceIDUsageDescription = "ShhShell uses Face ID to verify your identity";
|
||||
INFOPLIST_KEY_NSLocalNetworkUsageDescription = _;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LocalAuthentication
|
||||
|
||||
class HostsManager: ObservableObject {
|
||||
class HostsManager: ObservableObject, @unchecked Sendable {
|
||||
private let userDefaults = NSUbiquitousKeyValueStore.default
|
||||
|
||||
@Published var savedHosts: [Host] = []
|
||||
@@ -61,7 +62,7 @@ class HostsManager: ObservableObject {
|
||||
func getKeys() -> [Keypair] {
|
||||
var result: [Keypair] = []
|
||||
for host in savedHosts {
|
||||
if !result.contains(where: { $0 == Keypair(publicKey: host.publicKey, privateKey: host.privateKey)}) {
|
||||
if result.contains(where: { $0 == Keypair(publicKey: host.publicKey, privateKey: host.privateKey)}) {
|
||||
|
||||
} else {
|
||||
result.append(Keypair(publicKey: host.publicKey, privateKey: host.privateKey))
|
||||
@@ -69,4 +70,19 @@ class HostsManager: ObservableObject {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func authWithBiometrics() async -> Bool {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let reason = "Authenticate yourself with Face ID to view private keys"
|
||||
return await withCheckedContinuation { continuation in
|
||||
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, _ in
|
||||
continuation.resume(returning: success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
ShhShell/Keys/CenteredLabel.swift
Normal file
25
ShhShell/Keys/CenteredLabel.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// CenteredLabel.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 25/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct CenteredLabel: View {
|
||||
@State var title: String
|
||||
@State var systemName: String
|
||||
var body: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemName: systemName)
|
||||
Text(title)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CenteredLabel(title: "Button Text Labek", systemName: "pencil.tip.crop.circle")
|
||||
}
|
||||
77
ShhShell/Keys/KeyDetailView.swift
Normal file
77
ShhShell/Keys/KeyDetailView.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// KeyDetailView.swift
|
||||
// ShhShell
|
||||
//
|
||||
// Created by neon443 on 25/06/2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct KeyDetailView: View {
|
||||
@ObservedObject var hostsManager: HostsManager
|
||||
@State var keypair: Keypair
|
||||
@State private var reveal: Bool = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Public key")
|
||||
.bold()
|
||||
Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil")
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Private key")
|
||||
.bold()
|
||||
ZStack {
|
||||
Text(String(data: keypair.privateKey!, encoding: .utf8) ?? "nil")
|
||||
.blur(radius: reveal ? 0 : 5)
|
||||
VStack {
|
||||
Image(systemName: "eye.slash.fill")
|
||||
.resizable().scaledToFit()
|
||||
.frame(width: 50)
|
||||
Text("Tap to reveal")
|
||||
}
|
||||
.opacity(reveal ? 0 : 1)
|
||||
}
|
||||
.onTapGesture {
|
||||
Task {
|
||||
if !reveal {
|
||||
guard await hostsManager.authWithBiometrics() else { return }
|
||||
}
|
||||
withAnimation(.spring) { reveal.toggle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Task {
|
||||
guard await hostsManager.authWithBiometrics() else { return }
|
||||
if let privateKey = keypair.privateKey {
|
||||
UIPasteboard.general.string = String(data: privateKey, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
CenteredLabel(title: "Copy private key", systemName: "document.on.document")
|
||||
}
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
KeyDetailView(
|
||||
hostsManager: HostsManager(),
|
||||
keypair: Keypair(
|
||||
publicKey: "ssh-ed25519 dskjhfajkdhfjkdashfgjkhadsjkgfbhalkjhfjkhdask user@mac".data(using: .utf8),
|
||||
privateKey: """
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqu
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
"""
|
||||
.data(using: .utf8)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -15,11 +15,11 @@ struct KeyManagerView: View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
ForEach(hostsManager.savedHosts) { host in
|
||||
ForEach(hostsManager.getKeys()) { keypair in
|
||||
NavigationLink {
|
||||
|
||||
KeyDetailView(hostsManager: hostsManager, keypair: keypair)
|
||||
} label: {
|
||||
|
||||
Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,11 @@ struct KeyManagerView: View {
|
||||
NavigationLink {
|
||||
List {
|
||||
ForEach(hostsManager.savedHosts) { host in
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
VStack(alignment: .leading) {
|
||||
Text(host.address)
|
||||
.bold()
|
||||
Text(host.key ?? "nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol KeypairProtocol: Equatable, Codable, Hashable {
|
||||
protocol KeypairProtocol: Identifiable, Equatable, Codable, Hashable {
|
||||
var publicKey: Data? { get set }
|
||||
var privateKey: Data? { get set }
|
||||
}
|
||||
|
||||
struct Keypair: KeypairProtocol {
|
||||
var id = UUID()
|
||||
var publicKey: Data?
|
||||
var privateKey: Data?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user