From a8662c9a2fd42dc806c0fbcf7cbc4bcb9df2897b Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Mon, 25 Aug 2025 22:43:01 -0700 Subject: [PATCH] WIP --- .../Sources/SecretAgentKit/Agent.swift | 25 +++--- .../SecretAgentKit/SocketController.swift | 77 ++++++++++--------- Sources/SecretAgent/AppDelegate.swift | 11 +-- 3 files changed, 58 insertions(+), 55 deletions(-) diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index 7f5d6bf..9691bc2 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -11,7 +11,6 @@ public final class Agent: Sendable { private let witness: SigningWitness? private let publicKeyWriter = OpenSSHPublicKeyWriter() private let signatureWriter = OpenSSHSignatureWriter() - private let requestTracer = SigningRequestTracer() private let certificateHandler = OpenSSHCertificateHandler() private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "Agent") @@ -34,28 +33,24 @@ extension Agent { /// Handles an incoming request. /// - Parameters: - /// - reader: A ``FileHandleReader`` to read the content of the request. - /// - writer: A ``FileHandleWriter`` to write the response to. - /// - Return value: - /// - Boolean if data could be read - @discardableResult public func handle(reader: FileHandleReader, writer: FileHandleWriter) async -> Bool { + /// // FIXME: UPDATE DOCS + public func handle(data: Data, provenance: SigningRequestProvenance) async throws -> Data { logger.debug("Agent handling new data") - let data = Data(reader.availableData) - guard data.count > 4 else { return false} + guard data.count > 4 else { + throw AgentError.couldNotRead + } let requestTypeInt = data[4] guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { - writer.write(SSHAgent.ResponseType.agentFailure.data.lengthAndData) logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)") - return true + return SSHAgent.ResponseType.agentFailure.data.lengthAndData } logger.debug("Agent handling request of type \(requestType.debugDescription)") let subData = Data(data[5...]) - let response = await handle(requestType: requestType, data: subData, reader: reader) - writer.write(response) - return true + let response = await handle(requestType: requestType, data: subData, provenance: provenance) + return response } - func handle(requestType: SSHAgent.RequestType, data: Data, reader: FileHandleReader) async -> Data { + func handle(requestType: SSHAgent.RequestType, data: Data, provenance: SigningRequestProvenance) async -> Data { // Depending on the launch context (such as after macOS update), the agent may need to reload secrets before acting await reloadSecretsIfNeccessary() var response = Data() @@ -66,7 +61,6 @@ extension Agent { response.append(await identities()) logger.debug("Agent returned \(SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)") case .signRequest: - let provenance = requestTracer.provenance(from: reader) response.append(SSHAgent.ResponseType.agentSignResponse.data) response.append(try await sign(data: data, provenance: provenance)) logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)") @@ -184,6 +178,7 @@ extension Agent { /// An error involving agent operations.. enum AgentError: Error { + case couldNotRead case unhandledType case noMatchingKey case unsupportedKeyType diff --git a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift index acaf542..0c6cdbd 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift @@ -1,19 +1,21 @@ import Foundation import OSLog +import SecretKit /// A controller that manages socket configuration and request dispatching. public final class SocketController { - /// The active FileHandle. - private var fileHandle: FileHandle? /// The active SocketPort. private var port: SocketPort? /// A handler that will be notified when a new read/write handle is available. /// False if no data could be read - public var handler: (@Sendable (FileHandleReader, FileHandleWriter) async -> Bool)? + public var handler: OSAllocatedUnfairLock<(@Sendable (Data, SigningRequestProvenance) async throws -> Data)?> = .init(initialState: nil) /// Logger. private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "SocketController") + private let requestTracer = SigningRequestTracer() + + // Async sequence of message? /// Initializes a socket controller with a specified path. /// - Parameter path: The path to use as a socket. @@ -34,10 +36,43 @@ public final class SocketController { /// - Parameter path: The path to use as a socket. func configureSocket(at path: String) { guard let port = port else { return } - fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true) - NotificationCenter.default.addObserver(self, selector: #selector(handleConnectionAccept(notification:)), name: .NSFileHandleConnectionAccepted, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleConnectionDataAvailable(notification:)), name: .NSFileHandleDataAvailable, object: nil) - fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.Mode.common]) + let fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true) + fileHandle.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.Mode.common]) + Task { [handler, logger] in + for await notification in NotificationCenter.default.notifications(named: .NSFileHandleConnectionAccepted) { + logger.debug("Socket controller accepted connection") + guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { continue } + let provenance = SigningRequestTracer().provenance(from: new) + guard let handler = handler.withLock({ $0 }) else { + // FIXME: THIS + fatalError() + } + let response = try await handler(Data(fileHandle.availableData), provenance) + try fileHandle.write(contentsOf: response) + await new.waitForDataInBackgroundAndNotifyOnMainActor() + await fileHandle.acceptConnectionInBackgroundAndNotifyOnMainActor() + } + } + Task { [logger, handler] in + for await notification in NotificationCenter.default.notifications(named: .NSFileHandleDataAvailable) { + logger.debug("Socket controller has new data available") + guard let new = notification.object as? FileHandle else { return } + logger.debug("Socket controller received new file handle") + guard let handler = handler.withLock({ $0 }) else { + // FIXME: THIS + fatalError() + } + do { + let provenance = SigningRequestTracer().provenance(from: new) + let response = try await handler(Data(fileHandle.availableData), provenance) + try fileHandle.write(contentsOf: response) + logger.debug("Socket controller handled data, wait for more data") + await new.waitForDataInBackgroundAndNotifyOnMainActor() + } catch { + logger.debug("Socket controller called with empty data, socked closed") + } + } + } } /// Creates a SocketPort for a path. @@ -64,34 +99,6 @@ public final class SocketController { return SocketPort(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)! } - /// Handles a new connection being accepted, invokes the handler, and prepares to accept new connections. - /// - Parameter notification: A `Notification` that triggered the call. - @objc func handleConnectionAccept(notification: Notification) { - logger.debug("Socket controller accepted connection") - guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return } - Task { [handler, fileHandle] in - _ = await handler?(new, new) - await new.waitForDataInBackgroundAndNotifyOnMainActor() - await fileHandle?.acceptConnectionInBackgroundAndNotifyOnMainActor() - } - } - - /// Handles a new connection providing data and invokes the handler callback. - /// - Parameter notification: A `Notification` that triggered the call. - @objc func handleConnectionDataAvailable(notification: Notification) { - logger.debug("Socket controller has new data available") - guard let new = notification.object as? FileHandle else { return } - logger.debug("Socket controller received new file handle") - Task { [handler, logger = logger] in - if((await handler?(new, new)) == true) { - logger.debug("Socket controller handled data, wait for more data") - await new.waitForDataInBackgroundAndNotifyOnMainActor() - } else { - logger.debug("Socket controller called with empty data, socked closed") - } - } - } - } extension FileHandle { diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index e4f8749..4dfce2c 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -33,11 +33,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { logger.debug("SecretAgent finished launching") - Task { @MainActor in - socketController.handler = { [agent] reader, writer in - await agent.handle(reader: reader, writer: writer) - } - } + socketController.handler.withLock { [agent] in $0 = agent.handle } +// Task { +// for await (message, response) in socketController.messages { +// let handled = +// } +// } Task { for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) { try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)