This commit is contained in:
Max Goedjen
2026-05-04 15:20:25 -07:00
parent bc19a37acf
commit 438b4c6658
10 changed files with 272 additions and 105 deletions

View File

@@ -0,0 +1,122 @@
import Cocoa
import OSLog
import SecretKit
import SecureEnclaveSecretKit
import SmartCardSecretKit
import SecretAgentKit
import Brief
import Observation
import Common
import SwiftUI
@main
struct SecretAgent: App {
@MainActor private let storeList: SecretStoreList = {
let list = SecretStoreList()
let cryptoKit = SecureEnclave.Store()
let migrator = SecureEnclave.CryptoKitMigrator()
try? migrator.migrate(to: cryptoKit)
list.add(store: cryptoKit)
list.add(store: SmartCard.Store())
return list
}()
private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier()
private let authenticationHandler = AuthenticationHandler()
private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
@State var pending: ([[SignatureRequest]], (Set<SignatureRequest>) async throws -> Void)?
@Environment(\.openWindow) var openWindow
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "App")
@SceneBuilder var body: some Scene {
MenuBarExtra(isInserted: .constant(false)) {
EmptyView()
} label: {
Image(systemName: "lock")
.task {
await notifier.registerPersistenceHandler {
try await authenticationHandler.persistAuthentication(secret: $0, forDuration: $1)
}
}
.task {
let socketController = SocketController(path: URL.socketPath)
let agent = Agent(storeList: storeList, authenticationHandler: authenticationHandler, witness: notifier)
for await session in socketController.sessions {
Task {
let inputParser = try await XPCAgentInputParser()
do {
for await message in session.messages {
let request = try await inputParser.parse(data: message)
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
try session.write(agentResponse)
}
} catch {
try session.close()
}
}
}
}
// .task {
// let socketController = SocketController(path: URL.agentHomeURL.appendingPathComponent("socket-two.ssh").path())
// let socketController = SocketController(path: "/Users/max/Downloads/test.ssh")
// let agent = Agent(storeList: storeList, authenticationHandler: authenticationHandler, witness: notifier)
// for await session in socketController.sessions {
// Task {
// let inputParser = try await XPCAgentInputParser()
// do {
// for await message in session.messages {
// let request = try await inputParser.parse(data: message)
// let agentResponse = await agent.handle(request: request, provenance: session.provenance)
// try session.write(agentResponse)
// }
// } catch {
// try session.close()
// }
// }
// }
// }
.task {
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
}
}
.task {
await authenticationHandler.setBatchAuthHandler { @MainActor pending, authorize in
self.pending = (pending, authorize)
openWindow(id: String(describing: BatchedRequestsView.self))
}
}
.task {
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
notifier.prompt()
_ = withObservationTracking {
updater.update
} onChange: { [updater, notifier] in
Task {
guard !updater.currentVersion.isTestBuild else { return }
await notifier.notify(update: updater.update!) { release in
await updater.ignore(release: release)
}
}
}
}
}
WindowGroup(id: String(describing: BatchedRequestsView.self)) {
pendingView
}
.windowStyle(.hiddenTitleBar)
.windowResizability(.contentSize)
}
@ViewBuilder
var pendingView: some View {
if let (requests, authorize) = pending {
BatchedRequestsView(pending: requests, review: authorize)
}
}
}

View File

@@ -1,86 +0,0 @@
import Cocoa
import OSLog
import SecretKit
import SecureEnclaveSecretKit
import SmartCardSecretKit
import SecretAgentKit
import Brief
import Observation
import Common
@main
class AppDelegate: NSObject, NSApplicationDelegate {
@MainActor private let storeList: SecretStoreList = {
let list = SecretStoreList()
let cryptoKit = SecureEnclave.Store()
let migrator = SecureEnclave.CryptoKitMigrator()
try? migrator.migrate(to: cryptoKit)
list.add(store: cryptoKit)
list.add(store: SmartCard.Store())
return list
}()
private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier()
private let authenticationHandler = AuthenticationHandler { pending, authorize in
print(pending)
print("Waiting")
// Task {
try await Task.sleep(for: .seconds(3))
try await authorize(pending)
// }
}
private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory)
private lazy var agent: Agent = {
Agent(storeList: storeList, authenticationHandler: authenticationHandler, witness: notifier)
}()
private lazy var socketController: SocketController = {
let path = URL.socketPath as String
return SocketController(path: path)
}()
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
func applicationDidFinishLaunching(_ aNotification: Notification) {
logger.debug("SecretAgent finished launching")
Task {
for await session in socketController.sessions {
Task {
let inputParser = try await XPCAgentInputParser()
do {
for await message in session.messages {
let request = try await inputParser.parse(data: message)
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
try session.write(agentResponse)
}
} catch {
try session.close()
}
}
}
}
Task { [notifier, authenticationHandler] in
await notifier.registerPersistenceHandler {
try await authenticationHandler.persistAuthentication(secret: $0, forDuration: $1)
}
}
Task {
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
}
}
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
notifier.prompt()
_ = withObservationTracking {
updater.update
} onChange: { [updater, notifier] in
Task {
guard !updater.currentVersion.isTestBuild else { return }
await notifier.notify(update: updater.update!) { release in
await updater.ignore(release: release)
}
}
}
}
}

View File

@@ -0,0 +1,58 @@
import SwiftUI
import SecretKit
import SecretAgentKit
import SmartCardSecretKit
struct BatchedRequestsView: View {
let pending: [[SignatureRequest]]
let review: (Set<SignatureRequest>) async throws -> Void
init(pending: [[SignatureRequest]], review: @escaping (Set<SignatureRequest>) async throws -> Void) {
self.pending = pending
self.review = review
}
var body: some View {
VStack(alignment: .leading) {
// .padding()
Form {
// Text("Multiple authenticated requests are pending. You can approve them batches, or request they all proceed individually.")
ForEach(Array(pending.enumerated()), id: \.offset) { group in
Section {
ForEach(Array(group.element.enumerated()), id: \.offset) { pending in
HStack {
VStack(alignment: .leading) {
Text(pending.element.provenance.origin.displayName)
.font(.headline)
Text(pending.element.provenance.date.formatted())
.font(.footnote)
}
Spacer()
Button("Review") {
Task {
try await review([pending.element])
}
}
}
}
} header: {
HStack {
Text("\(group.element.first!.provenance.origin.displayName) - \(group.element.first!.secret.name)")
Spacer()
Button("Review All") {
Task {
try await review(Set(group.element))
}
}
}
}
}
}
.formStyle(.grouped)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}

View File

@@ -16,10 +16,10 @@
<string>1</string>
<key>com.apple.security.hardened-process.hardened-heap</key>
<true/>
<key>com.apple.security.smartcard</key>
<true/>
<key>com.apple.security.hardened-process.platform-restrictions-string</key>
<string>2</string>
<key>com.apple.security.smartcard</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.maxgoedjen.Secretive</string>