From cf5ae49ebc6958756462b6d81e2bab83f178d374 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Sep 2025 12:53:10 -0700 Subject: [PATCH 1/2] Remove unneeded protocosl (#674) --- .../SecretAgentKit/FileHandleProtocols.swift | 22 +------------------ .../SecretAgentKit/SigningRequestTracer.swift | 6 ++--- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift b/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift index 40a2840..83ade57 100644 --- a/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift +++ b/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift @@ -1,26 +1,6 @@ import Foundation -/// Protocol abstraction of the reading aspects of FileHandle. -public protocol FileHandleReader: Sendable { - - /// Gets data that is available for reading. - var availableData: Data { get } - /// A file descriptor of the handle. - var fileDescriptor: Int32 { get } - /// The process ID of the process coonnected to the other end of the FileHandle. - var pidOfConnectedProcess: Int32 { get } - -} - -/// Protocol abstraction of the writing aspects of FileHandle. -public protocol FileHandleWriter: Sendable { - - /// Writes data to the handle. - func write(_ data: Data) - -} - -extension FileHandle: FileHandleReader, FileHandleWriter { +extension FileHandle { public var pidOfConnectedProcess: Int32 { let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1) diff --git a/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift b/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift index aa8e295..a4f683a 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift @@ -10,10 +10,10 @@ struct SigningRequestTracer { extension SigningRequestTracer { - /// Generates a ``SecretKit.SigningRequestProvenance`` from a ``FileHandleReader``. - /// - Parameter fileHandleReader: The reader involved in processing the request. + /// Generates a ``SecretKit.SigningRequestProvenance`` from a ``FileHandle``. + /// - Parameter fileHandle: The reader involved in processing the request. /// - Returns: A ``SecretKit.SigningRequestProvenance`` describing the origin of the request. - func provenance(from fileHandleReader: FileHandleReader) -> SigningRequestProvenance { + func provenance(from fileHandleReader: FileHandle) -> SigningRequestProvenance { let firstInfo = process(from: fileHandleReader.pidOfConnectedProcess) var provenance = SigningRequestProvenance(root: firstInfo) From 63b42bd9dfcedfef6df5cbb2c626227b641fd8d5 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Sep 2025 23:16:23 -0700 Subject: [PATCH 2/2] Move downloader and socket input parsing to xpc services (#675) * XPC updater POC * WIP * obo * WIP * Working * . * . * . * Cleanup * Cleanup * Throw restrict * Remove dead protocol. * . * Fix ECDSA test. * Scripts --- .github/workflows/release.yml | 2 + .github/workflows/test.yml | 4 +- Sources/AgentRequestParser/Info.plist | 11 + Sources/AgentRequestParser/main.swift | 55 ++ Sources/Config/Secretive.xctestplan | 22 +- Sources/Packages/Package.swift | 11 +- Sources/Packages/Sources/Brief/Updater.swift | 29 +- .../Sources/SecretAgentKit/Agent.swift | 75 +-- .../SecretAgentKit/FileHandleProtocols.swift | 2 +- .../OpenSSHCertificateHandler.swift | 28 +- .../OpenSSHReader.swift | 25 +- .../SecretAgentKit/SSHAgentInputParser.swift | 109 ++++ .../SecretAgentKit/SSHAgentProtocol.swift | 62 ++- .../SecretAgentKit/SigningRequestTracer.swift | 5 +- .../Sources/XPCWrappers/XPCWrappers.swift | 49 ++ .../SecretAgentKitTests/AgentTests.swift | 93 ++-- .../OpenSSHReaderTests.swift | 2 +- .../Tests/SecretAgentKitTests/StubStore.swift | 2 +- Sources/ReleasesDownloader/Info.plist | 11 + Sources/ReleasesDownloader/main.swift | 44 ++ Sources/SecretAgent/AppDelegate.swift | 4 +- Sources/SecretAgent/XPCInputParser.swift | 23 + Sources/Secretive.xcodeproj/project.pbxproj | 469 +++++++++++++++++- .../xcschemes/PackageTests.xcscheme | 46 ++ 24 files changed, 995 insertions(+), 188 deletions(-) create mode 100644 Sources/AgentRequestParser/Info.plist create mode 100644 Sources/AgentRequestParser/main.swift rename Sources/Packages/Sources/{SecretKit/OpenSSH => SecretAgentKit}/OpenSSHCertificateHandler.swift (73%) rename Sources/Packages/Sources/{SecretKit/OpenSSH => SecretAgentKit}/OpenSSHReader.swift (51%) create mode 100644 Sources/Packages/Sources/SecretAgentKit/SSHAgentInputParser.swift create mode 100644 Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift rename Sources/Packages/Tests/{SecretKitTests => SecretAgentKitTests}/OpenSSHReaderTests.swift (98%) create mode 100644 Sources/ReleasesDownloader/Info.plist create mode 100644 Sources/ReleasesDownloader/main.swift create mode 100644 Sources/SecretAgent/XPCInputParser.swift create mode 100644 Sources/Secretive.xcodeproj/xcshareddata/xcschemes/PackageTests.xcscheme diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d69dfa..7f2d43d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,8 @@ jobs: - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app - name: Test + run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test + # SPM doesn't seem to pick up on the tests currently? run: swift test --build-system swiftbuild --package-path Sources/Packages build: permissions: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2fb5150..0e2f077 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,8 @@ jobs: - name: Set Environment run: sudo xcrun xcode-select -s /Applications/Xcode_26.0.app - name: Test Main Packages - run: swift test --build-system swiftbuild --package-path Sources/Packages + run: xcrun xcodebuild -project Sources/Secretive.xcodeproj -scheme PackageTests test + # SPM doesn't seem to pick up on the tests currently? + # run: swift test --build-system swiftbuild --package-path Sources/Packages - name: Test SecretKit Packages run: swift test --build-system swiftbuild diff --git a/Sources/AgentRequestParser/Info.plist b/Sources/AgentRequestParser/Info.plist new file mode 100644 index 0000000..c123a5d --- /dev/null +++ b/Sources/AgentRequestParser/Info.plist @@ -0,0 +1,11 @@ + + + + + XPCService + + ServiceType + Application + + + diff --git a/Sources/AgentRequestParser/main.swift b/Sources/AgentRequestParser/main.swift new file mode 100644 index 0000000..0105e8f --- /dev/null +++ b/Sources/AgentRequestParser/main.swift @@ -0,0 +1,55 @@ +import XPC +import SecretAgentKit +import OSLog + +private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent.AgentRequestParser", category: "Parser") + +func handleRequest(_ request: XPCListener.IncomingSessionRequest) -> XPCListener.IncomingSessionRequest.Decision { + logger.log("Parser received inbound request") + return request.accept { xpcMessage in + xpcMessage.handoffReply(to: .global(qos: .userInteractive)) { + logger.log("Parser accepted inbound request") + handle(with: xpcMessage) + } + } +} + +func handle(with xpcMessage: XPCReceivedMessage) { + do { + let parser = SSHAgentInputParser() + let result = try parser.parse(data: xpcMessage.wrappedDecode()) + logger.log("Parser parsed message as type \(result.debugDescription)") + xpcMessage.reply(result) + } catch { + logger.error("Parser failed with error \(error)") + xpcMessage.reply(error) + } +} + +extension XPCReceivedMessage { + + func wrappedDecode() throws(SSHAgentInputParser.AgentParsingError) -> Data { + do { + return try decode(as: Data.self) + } catch { + throw SSHAgentInputParser.AgentParsingError.invalidData + } + } + +} + +do { + if #available(macOS 26.0, *) { + _ = try XPCListener( + service: "com.maxgoedjen.Secretive.AgentRequestParser", + requirement: .isFromSameTeam(), + incomingSessionHandler: handleRequest(_:) + ) + } else { + _ = try XPCListener(service: "com.maxgoedjen.Secretive.AgentRequestParser", incomingSessionHandler: handleRequest(_:)) + } + logger.log("Parser initialized") + dispatchMain() +} catch { + logger.error("Failed to create parser, error: \(error)") +} diff --git a/Sources/Config/Secretive.xctestplan b/Sources/Config/Secretive.xctestplan index fb2bac9..61b77d0 100644 --- a/Sources/Config/Secretive.xctestplan +++ b/Sources/Config/Secretive.xctestplan @@ -13,12 +13,24 @@ }, "testTargets" : [ { - "enabled" : false, - "parallelizable" : true, "target" : { - "containerPath" : "container:Secretive.xcodeproj", - "identifier" : "50617D9323FCE48E0099B055", - "name" : "SecretiveTests" + "containerPath" : "container:Packages", + "identifier" : "BriefTests", + "name" : "BriefTests" + } + }, + { + "target" : { + "containerPath" : "container:Packages", + "identifier" : "SecretKitTests", + "name" : "SecretKitTests" + } + }, + { + "target" : { + "containerPath" : "container:Packages", + "identifier" : "SecretAgentKitTests", + "name" : "SecretAgentKitTests" } } ], diff --git a/Sources/Packages/Package.swift b/Sources/Packages/Package.swift index 8acfb24..dec30d6 100644 --- a/Sources/Packages/Package.swift +++ b/Sources/Packages/Package.swift @@ -21,13 +21,16 @@ let package = Package( targets: ["SmartCardSecretKit"]), .library( name: "SecretAgentKit", - targets: ["SecretAgentKit"]), + targets: ["SecretAgentKit", "XPCWrappers"]), .library( name: "SecretAgentKitHeaders", targets: ["SecretAgentKitHeaders"]), .library( name: "Brief", targets: ["Brief"]), + .library( + name: "XPCWrappers", + targets: ["XPCWrappers"]), ], dependencies: [ ], @@ -70,7 +73,7 @@ let package = Package( ), .target( name: "Brief", - dependencies: [], + dependencies: ["XPCWrappers"], resources: [localization], swiftSettings: swiftSettings, ), @@ -78,6 +81,10 @@ let package = Package( name: "BriefTests", dependencies: ["Brief"], ), + .target( + name: "XPCWrappers", + swiftSettings: swiftSettings, + ), ] ) diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index 600ddc5..7491378 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -1,5 +1,6 @@ import Foundation import Observation +import XPCWrappers /// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version. @Observable public final class Updater: UpdaterProtocol, Sendable { @@ -33,27 +34,25 @@ import Observation ) { self.osVersion = osVersion self.currentVersion = currentVersion - if checkOnLaunch { - // Don't do a launch check if the user hasn't seen the setup prompt explaining updater yet. - Task { - await checkForUpdates() - } - } Task { + if checkOnLaunch { + try await checkForUpdates() + } while !Task.isCancelled { try? await Task.sleep(for: .seconds(Int(checkFrequency))) - await checkForUpdates() + try await checkForUpdates() } } } /// Manually trigger an update check. - public func checkForUpdates() async { - guard let (data, _) = try? await URLSession.shared.data(from: Constants.updateURL) else { return } - guard let releases = try? JSONDecoder().decode([Release].self, from: data) else { return } - await evaluate(releases: releases) + public func checkForUpdates() async throws { + let session = try XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.ReleasesDownloader") + await evaluate(releases: try await session.send()) + session.complete() } + /// Ignores a specified release. `update` will be nil if the user has ignored the latest available release. /// - Parameter release: The release to ignore. public func ignore(release: Release) async { @@ -100,11 +99,3 @@ extension Updater { } } - -extension Updater { - - enum Constants { - static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")! - } - -} diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index 302fa3d..7fe268d 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -31,49 +31,29 @@ public final class Agent: Sendable { extension Agent { - /// Handles an incoming request. - /// - Parameters: - /// - data: The data to handle. - /// - provenance: The origin of the request. - /// - Returns: A response data payload. - public func handle(data: Data, provenance: SigningRequestProvenance) async throws -> Data { - logger.debug("Agent handling new data") - guard data.count > 4 else { - throw InvalidDataProvidedError() - } - let requestTypeInt = data[4] - guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { - logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription) for unknown request type \(requestTypeInt)") - 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, provenance: provenance) - return response - } - - private func handle(requestType: SSHAgent.RequestType, data: Data, provenance: SigningRequestProvenance) async -> Data { + public func handle(request: SSHAgent.Request, 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() do { - switch requestType { + switch request { case .requestIdentities: - response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data) + response.append(SSHAgent.Response.agentIdentitiesAnswer.data) response.append(await identities()) - logger.debug("Agent returned \(SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)") - case .signRequest: - response.append(SSHAgent.ResponseType.agentSignResponse.data) - response.append(try await sign(data: data, provenance: provenance)) - logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)") + logger.debug("Agent returned \(SSHAgent.Response.agentIdentitiesAnswer.debugDescription)") + case .signRequest(let context): + response.append(SSHAgent.Response.agentSignResponse.data) + response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance)) + logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)") + case .unknown(let value): + logger.error("Agent received unknown request of type \(value).") default: - logger.debug("Agent received valid request of type \(requestType.debugDescription), but not currently supported.") - response.append(SSHAgent.ResponseType.agentFailure.data) - + logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.") + throw UnhandledRequestError() } } catch { - response = SSHAgent.ResponseType.agentFailure.data - logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)") + response = SSHAgent.Response.agentFailure.data + logger.debug("Agent returned \(SSHAgent.Response.agentFailure.debugDescription)") } return response.lengthAndData } @@ -113,27 +93,16 @@ extension Agent { /// - data: The data to sign. /// - provenance: A ``SecretKit.SigningRequestProvenance`` object describing the origin of the request. /// - Returns: An OpenSSH formatted Data payload containing the signed data response. - func sign(data: Data, provenance: SigningRequestProvenance) async throws -> Data { - let reader = OpenSSHReader(data: data) - let payloadHash = try reader.readNextChunk() - let hash: Data - - // Check if hash is actually an openssh certificate and reconstruct the public key if it is - if let certificatePublicKey = await certificateHandler.publicKeyHash(from: payloadHash) { - hash = certificatePublicKey - } else { - hash = payloadHash - } - - guard let (secret, store) = await secret(matching: hash) else { - logger.debug("Agent did not have a key matching \(hash as NSData)") + func sign(data: Data, keyBlob: Data, provenance: SigningRequestProvenance) async throws -> Data { + guard let (secret, store) = await secret(matching: keyBlob) else { + let keyBlobHex = keyBlob.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined() + logger.debug("Agent did not have a key matching \(keyBlobHex)") throw NoMatchingKeyError() } try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance) - let dataToSign = try reader.readNextChunk() - let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance) + let rawRepresentation = try await store.sign(data: data, with: secret, for: provenance) let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation) try await witness?.witness(accessTo: secret, from: store, by: provenance) @@ -172,16 +141,16 @@ extension Agent { extension Agent { - struct InvalidDataProvidedError: Error {} struct NoMatchingKeyError: Error {} + struct UnhandledRequestError: Error {} } -extension SSHAgent.ResponseType { +extension SSHAgent.Response { var data: Data { var raw = self.rawValue - return Data(bytes: &raw, count: UInt8.bitWidth/8) + return Data(bytes: &raw, count: MemoryLayout.size) } } diff --git a/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift b/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift index 83ade57..f95865e 100644 --- a/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift +++ b/Sources/Packages/Sources/SecretAgentKit/FileHandleProtocols.swift @@ -3,7 +3,7 @@ import Foundation extension FileHandle { public var pidOfConnectedProcess: Int32 { - let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1) + let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout.size, alignment: 1) var len = socklen_t(MemoryLayout.size) getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len) return pidPointer.load(as: Int32.self) diff --git a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift b/Sources/Packages/Sources/SecretAgentKit/OpenSSHCertificateHandler.swift similarity index 73% rename from Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift rename to Sources/Packages/Sources/SecretAgentKit/OpenSSHCertificateHandler.swift index a5af72c..5451e49 100644 --- a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift +++ b/Sources/Packages/Sources/SecretAgentKit/OpenSSHCertificateHandler.swift @@ -1,5 +1,6 @@ import Foundation import OSLog +import SecretKit /// Manages storage and lookup for OpenSSH certificates. public actor OpenSSHCertificateHandler: Sendable { @@ -25,33 +26,6 @@ public actor OpenSSHCertificateHandler: Sendable { } } - /// Reconstructs a public key from a ``Data``, if that ``Data`` contains an OpenSSH certificate hash. Currently only ecdsa certificates are supported - /// - Parameter certBlock: The openssh certificate to extract the public key from - /// - Returns: A ``Data`` object containing the public key in OpenSSH wire format if the ``Data`` is an OpenSSH certificate hash, otherwise nil. - public func publicKeyHash(from hash: Data) -> Data? { - let reader = OpenSSHReader(data: hash) - do { - let certType = String(decoding: try reader.readNextChunk(), as: UTF8.self) - switch certType { - case "ecdsa-sha2-nistp256-cert-v01@openssh.com", - "ecdsa-sha2-nistp384-cert-v01@openssh.com", - "ecdsa-sha2-nistp521-cert-v01@openssh.com": - _ = try reader.readNextChunk() // nonce - let curveIdentifier = try reader.readNextChunk() - let publicKey = try reader.readNextChunk() - - let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "") - return openSSHIdentifier.lengthAndData + - curveIdentifier.lengthAndData + - publicKey.lengthAndData - default: - return nil - } - } catch { - return nil - } - } - /// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret`` /// - Parameter secret: The secret to search for a certificate with /// - Returns: A (``Data``, ``Data``) tuple containing the certificate and certificate name, respectively. diff --git a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift b/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift similarity index 51% rename from Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift rename to Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift index 22417cb..d4d5103 100644 --- a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift +++ b/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift @@ -1,42 +1,47 @@ import Foundation /// Reads OpenSSH protocol data. -public final class OpenSSHReader { +final class OpenSSHReader { var remaining: Data /// Initialize the reader with an OpenSSH data payload. /// - Parameter data: The data to read. - public init(data: Data) { + init(data: Data) { remaining = Data(data) } /// Reads the next chunk of data from the playload. /// - Returns: The next chunk of data. - public func readNextChunk(convertEndianness: Bool = true) throws -> Data { + func readNextChunk(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> Data { let littleEndianLength = try readNextBytes(as: UInt32.self) let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength) - guard remaining.count >= length else { throw EndOfData() } + guard remaining.count >= length else { throw .beyondBounds } let dataRange = 0..(as: T.Type) throws -> T { + func readNextBytes(as: T.Type) throws(OpenSSHReaderError) -> T { let size = MemoryLayout.size - guard remaining.count >= size else { throw EndOfData() } + guard remaining.count >= size else { throw .beyondBounds } let lengthRange = 0.. String { - try String(decoding: readNextChunk(), as: UTF8.self) + func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String { + try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self) } - public struct EndOfData: Error {} + func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader { + OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness)) + } } + +public enum OpenSSHReaderError: Error, Codable { + case beyondBounds +} diff --git a/Sources/Packages/Sources/SecretAgentKit/SSHAgentInputParser.swift b/Sources/Packages/Sources/SecretAgentKit/SSHAgentInputParser.swift new file mode 100644 index 0000000..c6a44f6 --- /dev/null +++ b/Sources/Packages/Sources/SecretAgentKit/SSHAgentInputParser.swift @@ -0,0 +1,109 @@ +import Foundation +import OSLog +import SecretKit + +public protocol SSHAgentInputParserProtocol: Sendable { + + func parse(data: Data) async throws -> SSHAgent.Request + +} + +public struct SSHAgentInputParser: SSHAgentInputParserProtocol { + + private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "InputParser") + + public init() { + + } + + public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request { + logger.debug("Parsing new data") + guard data.count > 4 else { + throw .invalidData + } + let specifiedLength = (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4 + let rawRequestInt = data[4] + let remainingDataRange = 5.. SSHAgent.Request.SignatureRequestContext { + let reader = OpenSSHReader(data: data) + let rawKeyBlob = try reader.readNextChunk() + let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob + let dataToSign = try reader.readNextChunk() + return SSHAgent.Request.SignatureRequestContext(keyBlob: keyBlob, dataToSign: dataToSign) + } + + func certificatePublicKeyBlob(from hash: Data) -> Data? { + let reader = OpenSSHReader(data: hash) + do { + let certType = String(decoding: try reader.readNextChunk(), as: UTF8.self) + switch certType { + case "ecdsa-sha2-nistp256-cert-v01@openssh.com", + "ecdsa-sha2-nistp384-cert-v01@openssh.com", + "ecdsa-sha2-nistp521-cert-v01@openssh.com": + _ = try reader.readNextChunk() // nonce + let curveIdentifier = try reader.readNextChunk() + let publicKey = try reader.readNextChunk() + let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "") + return openSSHIdentifier.lengthAndData + + curveIdentifier.lengthAndData + + publicKey.lengthAndData + default: + return nil + } + } catch { + return nil + } + } + +} + + +extension SSHAgentInputParser { + + public enum AgentParsingError: Error, Codable { + case unknownRequest + case unhandledRequest + case invalidData + case openSSHReader(OpenSSHReaderError) + } + +} diff --git a/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift b/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift index 30b4747..0007989 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift @@ -6,21 +6,39 @@ public enum SSHAgent {} extension SSHAgent { /// The type of the SSH Agent Request, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1 - public enum RequestType: UInt8, CustomDebugStringConvertible { + public enum Request: CustomDebugStringConvertible, Codable, Sendable { - case requestIdentities = 11 - case signRequest = 13 - case addIdentity = 17 - case removeIdentity = 18 - case removeAllIdentities = 19 - case addIDConstrained = 25 - case addSmartcardKey = 20 - case removeSmartcardKey = 21 - case lock = 22 - case unlock = 23 - case addSmartcardKeyConstrained = 26 - case protocolExtension = 27 + case requestIdentities + case signRequest(SignatureRequestContext) + case addIdentity + case removeIdentity + case removeAllIdentities + case addIDConstrained + case addSmartcardKey + case removeSmartcardKey + case lock + case unlock + case addSmartcardKeyConstrained + case protocolExtension + case unknown(UInt8) + public var protocolID: UInt8 { + switch self { + case .requestIdentities: 11 + case .signRequest: 13 + case .addIdentity: 17 + case .removeIdentity: 18 + case .removeAllIdentities: 19 + case .addIDConstrained: 25 + case .addSmartcardKey: 20 + case .removeSmartcardKey: 21 + case .lock: 22 + case .unlock: 23 + case .addSmartcardKeyConstrained: 26 + case .protocolExtension: 27 + case .unknown(let value): value + } + } public var debugDescription: String { switch self { @@ -36,12 +54,28 @@ extension SSHAgent { case .unlock: "SSH_AGENTC_UNLOCK" case .addSmartcardKeyConstrained: "SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED" case .protocolExtension: "SSH_AGENTC_EXTENSION" + case .unknown: "UNKNOWN_MESSAGE" } } + + public struct SignatureRequestContext: Sendable, Codable { + public let keyBlob: Data + public let dataToSign: Data + + public init(keyBlob: Data, dataToSign: Data) { + self.keyBlob = keyBlob + self.dataToSign = dataToSign + } + + public static var empty: SignatureRequestContext { + SignatureRequestContext(keyBlob: Data(), dataToSign: Data()) + } + } + } /// The type of the SSH Agent Response, as described in https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent#section-5.1 - public enum ResponseType: UInt8, CustomDebugStringConvertible { + public enum Response: UInt8, CustomDebugStringConvertible { case agentFailure = 5 case agentSuccess = 6 diff --git a/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift b/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift index a4f683a..2872825 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SigningRequestTracer.swift @@ -13,9 +13,8 @@ extension SigningRequestTracer { /// Generates a ``SecretKit.SigningRequestProvenance`` from a ``FileHandle``. /// - Parameter fileHandle: The reader involved in processing the request. /// - Returns: A ``SecretKit.SigningRequestProvenance`` describing the origin of the request. - func provenance(from fileHandleReader: FileHandle) -> SigningRequestProvenance { - let firstInfo = process(from: fileHandleReader.pidOfConnectedProcess) - + func provenance(from fileHandle: FileHandle) -> SigningRequestProvenance { + let firstInfo = process(from: fileHandle.pidOfConnectedProcess) var provenance = SigningRequestProvenance(root: firstInfo) while NSRunningApplication(processIdentifier: provenance.origin.pid) == nil && provenance.origin.parentPID != nil { provenance.chain.append(process(from: provenance.origin.parentPID!)) diff --git a/Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift b/Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift new file mode 100644 index 0000000..21515eb --- /dev/null +++ b/Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift @@ -0,0 +1,49 @@ +import Foundation + +public struct XPCTypedSession: Sendable { + + private let session: XPCSession + + public init(serviceName: String, warmup: Bool = false) throws { + if #available(macOS 26.0, *) { + session = try XPCSession(xpcService: serviceName, requirement: .isFromSameTeam()) + } else { + session = try XPCSession(xpcService: serviceName) + } + if warmup { + Task { [self] in + _ = try? await send() + } + } + } + + public func send(_ message: some Encodable = Data()) async throws -> ResponseType { + try await withCheckedThrowingContinuation { continuation in + do { + try session.send(message) { result in + switch result { + case .success(let message): + if let result = try? message.decode(as: ResponseType.self) { + continuation.resume(returning: result) + } else if let error = try? message.decode(as: ErrorType.self) { + continuation.resume(throwing: error) + } else { + continuation.resume(throwing: UnknownMessageError()) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } catch { + continuation.resume(throwing: error) + } + } + } + + public func complete() { + session.cancel(reason: "Done") + } + +} + +public struct UnknownMessageError: Error, Codable {} diff --git a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift index c542559..bbef669 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift @@ -8,19 +8,22 @@ import CryptoKit // MARK: Identity Listing - -// let testProvenance = SigningRequestProvenance(root: .init(pid: 0, processName: "Test", appName: "Test", iconURL: nil, path: /, validSignature: true, parentPID: nil)) - @Test func emptyStores() async throws { let agent = Agent(storeList: SecretStoreList()) - let response = try await agent.handle(data: Constants.Requests.requestIdentities, provenance: .test) + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities) + let response = await agent.handle(request: request, provenance: .test) #expect(response == Constants.Responses.requestIdentitiesEmpty) } @Test func identitiesList() async throws { let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) let agent = Agent(storeList: list) - let response = try await agent.handle(data: Constants.Requests.requestIdentities, provenance: .test) + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities) + let response = await agent.handle(request: request, provenance: .test) + + let actual = OpenSSHReader(data: response) + let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple) + print(actual, expected) #expect(response == Constants.Responses.requestIdentitiesMultiple) } @@ -29,40 +32,42 @@ import CryptoKit @Test func noMatchingIdentities() async throws { let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) let agent = Agent(storeList: list) - let response = try await agent.handle(data: Constants.Requests.requestSignatureWithNoneMatching, provenance: .test) + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching) + let response = await agent.handle(request: request, provenance: .test) #expect(response == Constants.Responses.requestFailure) } -// @Test func ecdsaSignature() async throws { -// let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) -// let requestReader = OpenSSHReader(data: Constants.Requests.requestSignature[5...]) -// _ = requestReader.readNextChunk() -// let dataToSign = requestReader.readNextChunk() -// let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) -// let agent = Agent(storeList: list) -// await agent.handle(reader: stubReader, writer: stubWriter) -// let outer = OpenSSHReader(data: stubWriter.data[5...]) -// let payload = outer.readNextChunk() -// let inner = OpenSSHReader(data: payload) -// _ = inner.readNextChunk() -// let signedData = inner.readNextChunk() -// let rsData = OpenSSHReader(data: signedData) -// var r = rsData.readNextChunk() -// var s = rsData.readNextChunk() -// // This is fine IRL, but it freaks out CryptoKit -// if r[0] == 0 { -// r.removeFirst() -// } -// if s[0] == 0 { -// s.removeFirst() -// } -// var rs = r -// rs.append(s) -// let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs) -// // Correct signature -// #expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey) -// .isValidSignature(signature, for: dataToSign)) -// } + @Test func ecdsaSignature() async throws { + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) + guard case SSHAgent.Request.signRequest(let context) = request else { return } + let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) + let agent = Agent(storeList: list) + let response = await agent.handle(request: request, provenance: .test) + let responseReader = OpenSSHReader(data: response) + let length = try responseReader.readNextBytes(as: UInt32.self).bigEndian + let type = try responseReader.readNextBytes(as: UInt8.self).bigEndian + #expect(length == response.count - MemoryLayout.size) + #expect(type == SSHAgent.Response.agentSignResponse.rawValue) + let outer = OpenSSHReader(data: responseReader.remaining) + let inner = try outer.readNextChunkAsSubReader() + _ = try inner.readNextChunk() + let rsData = try inner.readNextChunkAsSubReader() + var r = try rsData.readNextChunk() + var s = try rsData.readNextChunk() + // This is fine IRL, but it freaks out CryptoKit + if r[0] == 0 { + r.removeFirst() + } + if s[0] == 0 { + s.removeFirst() + } + var rs = r + rs.append(s) + let signature = try P256.Signing.ECDSASignature(rawRepresentation: rs) + // Correct signature + #expect(try P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey) + .isValidSignature(signature, for: context.dataToSign)) + } // MARK: Witness protocol @@ -72,7 +77,7 @@ import CryptoKit return true }, witness: { _, _ in }) let agent = Agent(storeList: list, witness: witness) - let response = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test) + let response = await agent.handle(request: .signRequest(.empty), provenance: .test) #expect(response == Constants.Responses.requestFailure) } @@ -85,7 +90,8 @@ import CryptoKit witnessed = true }) let agent = Agent(storeList: list, witness: witness) - _ = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test) + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) + _ = await agent.handle(request: request, provenance: .test) #expect(witnessed) } @@ -100,7 +106,8 @@ import CryptoKit witnessTrace = trace }) let agent = Agent(storeList: list, witness: witness) - _ = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test) + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) + _ = await agent.handle(request: request, provenance: .test) #expect(witnessTrace == speakNowTrace) #expect(witnessTrace == .test) } @@ -112,7 +119,8 @@ import CryptoKit let store = await list.stores.first?.base as! Stub.Store store.shouldThrow = true let agent = Agent(storeList: list) - let response = try await agent.handle(data: Constants.Requests.requestSignature, provenance: .test) + let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature) + let response = await agent.handle(request: request, provenance: .test) #expect(response == Constants.Responses.requestFailure) } @@ -120,7 +128,7 @@ import CryptoKit @Test func unhandledAdd() async throws { let agent = Agent(storeList: SecretStoreList()) - let response = try await agent.handle(data: Constants.Requests.addIdentity, provenance: .test) + let response = await agent.handle(request: .addIdentity, provenance: .test) #expect(response == Constants.Responses.requestFailure) } @@ -146,14 +154,13 @@ extension AgentTests { enum Requests { static let requestIdentities = Data(base64Encoded: "AAAAAQs=")! - static let addIdentity = Data(base64Encoded: "AAAAARE=")! static let requestSignatureWithNoneMatching = Data(base64Encoded: "AAABhA0AAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAO8AAAAgbqmrqPUtJ8mmrtaSVexjMYyXWNqjHSnoto7zgv86xvcyAAAAA2dpdAAAAA5zc2gtY29ubmVjdGlvbgAAAAlwdWJsaWNrZXkBAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAAA=")! static let requestSignature = Data(base64Encoded: "AAABRA0AAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08AAADPAAAAIBIFsbCZ4/dhBmLNGHm0GKj7EJ4N8k/jXRxlyg+LFIYzMgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAAA==")! } enum Responses { static let requestIdentitiesEmpty = Data(base64Encoded: "AAAABQwAAAAA")! - static let requestIdentitiesMultiple = Data(base64Encoded: "AAABKwwAAAACAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0")! + static let requestIdentitiesMultiple = Data(base64Encoded: "AAABLwwAAAACAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAFWVjZHNhLTI1NkBleGFtcGxlLmNvbQAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEspLMDmreMJverQkqKC9zF9ZUasn5uSWkbRlz1jNTCtuyH1KKm+VImL6wdAj47SbzwM6lEEC24AdfrR64P9i/bnS2i83v/4wQVtcZn+Et13QGgWlZst8lxCPzTookaVwMAAAAFWVjZHNhLTM4NEBleGFtcGxlLmNvbQ==")! static let requestFailure = Data(base64Encoded: "AAAAAQU=")! } diff --git a/Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift b/Sources/Packages/Tests/SecretAgentKitTests/OpenSSHReaderTests.swift similarity index 98% rename from Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift rename to Sources/Packages/Tests/SecretAgentKitTests/OpenSSHReaderTests.swift index 65d93cf..34201c6 100644 --- a/Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/OpenSSHReaderTests.swift @@ -1,6 +1,6 @@ import Foundation import Testing -@testable import SecretKit +@testable import SecretAgentKit @testable import SecureEnclaveSecretKit @testable import SmartCardSecretKit diff --git a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift index 222588a..c3a01d7 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/StubStore.swift @@ -82,7 +82,7 @@ extension Stub { let privateKey: Data init(keySize: Int, publicKey: Data, privateKey: Data) { - self.attributes = Attributes(keyType: .init(algorithm: .ecdsa, size: keySize), authentication: .notRequired) + self.attributes = Attributes(keyType: .init(algorithm: .ecdsa, size: keySize), authentication: .notRequired, publicKeyAttribution: "ecdsa-\(keySize)@example.com") self.publicKey = publicKey self.privateKey = privateKey } diff --git a/Sources/ReleasesDownloader/Info.plist b/Sources/ReleasesDownloader/Info.plist new file mode 100644 index 0000000..c123a5d --- /dev/null +++ b/Sources/ReleasesDownloader/Info.plist @@ -0,0 +1,11 @@ + + + + + XPCService + + ServiceType + Application + + + diff --git a/Sources/ReleasesDownloader/main.swift b/Sources/ReleasesDownloader/main.swift new file mode 100644 index 0000000..622e0bf --- /dev/null +++ b/Sources/ReleasesDownloader/main.swift @@ -0,0 +1,44 @@ +import XPC +import OSLog +import Brief + +private let logger = Logger(subsystem: "com.maxgoedjen.secretive.ReleasesDownloader", category: "ReleasesDownloader") + +enum Constants { + static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")! +} + +func handleRequest(_ request: XPCListener.IncomingSessionRequest) -> XPCListener.IncomingSessionRequest.Decision { + logger.log("ReleasesDownloader received inbound request") + return request.accept { xpcDictionary in + xpcDictionary.handoffReply(to: .global(qos: .userInteractive)) { + logger.log("ReleasesDownloader accepted inbound request") + Task { + do { + let (data, _) = try await URLSession.shared.data(from: Constants.updateURL) + let releases = try JSONDecoder().decode([Release].self, from: data) + xpcDictionary.reply(releases) + } catch { + logger.error("ReleasesDownloader failed with unknown error \(error)") + xpcDictionary.reply([] as [Release]) + } + } + } + } +} + +do { + if #available(macOS 26.0, *) { + _ = try XPCListener( + service: "com.maxgoedjen.Secretive.ReleasesDownloader", + requirement: .isFromSameTeam(), + incomingSessionHandler: handleRequest(_:) + ) + } else { + _ = try XPCListener(service: "com.maxgoedjen.Secretive.ReleasesDownloader", incomingSessionHandler: handleRequest(_:)) + } + logger.log("ReleasesDownloader initialized") + dispatchMain() +} catch { + logger.error("Failed to create ReleasesDownloader, error: \(error)") +} diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index ee4b799..237f595 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -34,11 +34,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { logger.debug("SecretAgent finished launching") Task { + let inputParser = try XPCAgentInputParser() for await session in socketController.sessions { Task { do { for await message in session.messages { - let agentResponse = try await agent.handle(data: message, provenance: session.provenance) + let request = try await inputParser.parse(data: message) + let agentResponse = await agent.handle(request: request, provenance: session.provenance) try await session.write(agentResponse) } } catch { diff --git a/Sources/SecretAgent/XPCInputParser.swift b/Sources/SecretAgent/XPCInputParser.swift new file mode 100644 index 0000000..40ff327 --- /dev/null +++ b/Sources/SecretAgent/XPCInputParser.swift @@ -0,0 +1,23 @@ +import Foundation +import SecretAgentKit +import Brief +import XPCWrappers + +/// Delegates all agent input parsing to an XPC service which wraps OpenSSH +public final class XPCAgentInputParser: SSHAgentInputParserProtocol { + + private let session: XPCTypedSession + + public init() throws { + session = try XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.AgentRequestParser", warmup: true) + } + + public func parse(data: Data) async throws -> SSHAgent.Request { + try await session.send(data) + } + + deinit { + session.complete() + } + +} diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index 96c6479..c9fce11 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -21,10 +21,18 @@ 5008C23E2E525D8900507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; 5008C2402E52792400507AC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50617D8623FCE48E0099B055 /* Assets.xcassets */; }; 5008C2412E52D18700507AC2 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */; }; + 500F04EB2E6CDEB0001CF06E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 500F04E82E6CDEB0001CF06E /* main.swift */; }; + 500F04EF2E6CDEF4001CF06E /* SecretAgentKit in Frameworks */ = {isa = PBXBuildFile; productRef = 500F04EE2E6CDEF4001CF06E /* SecretAgentKit */; }; + 500F04F02E6CDF20001CF06E /* AgentRequestParser.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 500F04D72E6CDEAB001CF06E /* AgentRequestParser.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 501421622781262300BBAA70 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501421612781262300BBAA70 /* Brief */; }; 501421652781268000BBAA70 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; }; 50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; }; + 501577C82E6BC5B4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 501577CF2E6BC5D4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 501577DA2E6BC5F3004A37D0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501577D62E6BC5F3004A37D0 /* main.swift */; }; + 501577DF2E6BC647004A37D0 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501577DE2E6BC647004A37D0 /* Brief */; }; + 501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; }; 5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; }; 504788EC2E680DC800B4556F /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788EB2E680DC400B4556F /* URLs.swift */; }; 504788F22E681F3A00B4556F /* Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504788F12E681F3A00B4556F /* Instructions.swift */; }; @@ -64,6 +72,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 500F04F12E6CDF20001CF06E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 500F04D62E6CDEAB001CF06E; + remoteInfo = AgentRequestParser; + }; 50142166278126B500BBAA70 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 50617D7723FCE48D0099B055 /* Project object */; @@ -71,9 +86,53 @@ remoteGlobalIDString = 50A3B78924026B7500D209EA; remoteInfo = SecretAgent; }; + 501577C62E6BC5B4004A37D0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 501577BC2E6BC5B4004A37D0; + remoteInfo = ReleasesDownloader; + }; + 501577D02E6BC5D4004A37D0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 501577BC2E6BC5B4004A37D0; + remoteInfo = ReleasesDownloader; + }; + 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 50617D7723FCE48D0099B055 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 501577BC2E6BC5B4004A37D0; + remoteInfo = ReleasesDownloader; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 501577C92E6BC5B4004A37D0 /* Embed XPC Services */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; + dstSubfolderSpec = 16; + files = ( + 501577C82E6BC5B4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */, + ); + name = "Embed XPC Services"; + runOnlyForDeploymentPostprocessing = 0; + }; + 501577D22E6BC5D4004A37D0 /* Embed XPC Services */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices"; + dstSubfolderSpec = 16; + files = ( + 500F04F02E6CDF20001CF06E /* AgentRequestParser.xpc in Embed XPC Services */, + 501577CF2E6BC5D4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */, + ); + name = "Embed XPC Services"; + runOnlyForDeploymentPostprocessing = 0; + }; 50617DBF23FCE4AB0099B055 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -112,8 +171,15 @@ 50033AC227813F1700253856 /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = ""; }; 5003EF39278005C800DF2006 /* Packages */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Packages; sourceTree = ""; }; 5008C23D2E525D8200507AC2 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = Packages/Resources/Localizable.xcstrings; sourceTree = SOURCE_ROOT; }; + 500F04D72E6CDEAB001CF06E /* AgentRequestParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = AgentRequestParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; + 500F04E72E6CDEB0001CF06E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 500F04E82E6CDEB0001CF06E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = ""; }; 50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = ""; }; + 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = ReleasesDownloader.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; + 501577D52E6BC5F3004A37D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 501577D62E6BC5F3004A37D0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + 501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = ""; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; 504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; 504788F12E681F3A00B4556F /* Instructions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instructions.swift; sourceTree = ""; }; @@ -161,6 +227,22 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 500F04D42E6CDEAB001CF06E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 500F04EF2E6CDEF4001CF06E /* SecretAgentKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 501577BA2E6BC5B4004A37D0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 501577DF2E6BC647004A37D0 /* Brief in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50617D7C23FCE48D0099B055 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -195,6 +277,24 @@ path = Helpers; sourceTree = ""; }; + 500F04E92E6CDEB0001CF06E /* AgentRequestParser */ = { + isa = PBXGroup; + children = ( + 500F04E72E6CDEB0001CF06E /* Info.plist */, + 500F04E82E6CDEB0001CF06E /* main.swift */, + ); + path = AgentRequestParser; + sourceTree = ""; + }; + 501577D92E6BC5F3004A37D0 /* ReleasesDownloader */ = { + isa = PBXGroup; + children = ( + 501577D52E6BC5F3004A37D0 /* Info.plist */, + 501577D62E6BC5F3004A37D0 /* main.swift */, + ); + path = ReleasesDownloader; + sourceTree = ""; + }; 504788ED2E681EB200B4556F /* Styles */ = { isa = PBXGroup; children = ( @@ -251,6 +351,8 @@ 5003EF39278005C800DF2006 /* Packages */, 50617D8123FCE48E0099B055 /* Secretive */, 50A3B78B24026B7500D209EA /* SecretAgent */, + 501577D92E6BC5F3004A37D0 /* ReleasesDownloader */, + 500F04E92E6CDEB0001CF06E /* AgentRequestParser */, 508A58AF241E144C0069DC07 /* Config */, 50617D8023FCE48E0099B055 /* Products */, 5099A08B240243730062B6F2 /* Frameworks */, @@ -262,6 +364,8 @@ children = ( 50617D7F23FCE48E0099B055 /* Secretive.app */, 50A3B78A24026B7500D209EA /* SecretAgent.app */, + 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */, + 500F04D72E6CDEAB001CF06E /* AgentRequestParser.xpc */, ); name = Products; sourceTree = ""; @@ -339,6 +443,7 @@ children = ( 50020BAF24064869003D4025 /* AppDelegate.swift */, 5018F54E24064786002EB505 /* Notifier.swift */, + 501578122E6C0479004A37D0 /* XPCInputParser.swift */, 50A3B79524026B7600D209EA /* Main.storyboard */, 50A3B79824026B7600D209EA /* Info.plist */, 508BF29425B4F140009EFB7E /* InternetAccessPolicy.plist */, @@ -359,6 +464,46 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 500F04D62E6CDEAB001CF06E /* AgentRequestParser */ = { + isa = PBXNativeTarget; + buildConfigurationList = 500F04E22E6CDEAB001CF06E /* Build configuration list for PBXNativeTarget "AgentRequestParser" */; + buildPhases = ( + 500F04D32E6CDEAB001CF06E /* Sources */, + 500F04D42E6CDEAB001CF06E /* Frameworks */, + 500F04D52E6CDEAB001CF06E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AgentRequestParser; + packageProductDependencies = ( + 500F04EE2E6CDEF4001CF06E /* SecretAgentKit */, + ); + productName = AgentRequestParser; + productReference = 500F04D72E6CDEAB001CF06E /* AgentRequestParser.xpc */; + productType = "com.apple.product-type.xpc-service"; + }; + 501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */ = { + isa = PBXNativeTarget; + buildConfigurationList = 501577CE2E6BC5B4004A37D0 /* Build configuration list for PBXNativeTarget "ReleasesDownloader" */; + buildPhases = ( + 501577B92E6BC5B4004A37D0 /* Sources */, + 501577BA2E6BC5B4004A37D0 /* Frameworks */, + 501577BB2E6BC5B4004A37D0 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ReleasesDownloader; + packageProductDependencies = ( + 501577DE2E6BC647004A37D0 /* Brief */, + ); + productName = ReleasesDownloader; + productReference = 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */; + productType = "com.apple.product-type.xpc-service"; + }; 50617D7E23FCE48D0099B055 /* Secretive */ = { isa = PBXNativeTarget; buildConfigurationList = 50617D9D23FCE48E0099B055 /* Build configuration list for PBXNativeTarget "Secretive" */; @@ -368,11 +513,13 @@ 50617D7D23FCE48D0099B055 /* Resources */, 50617DBF23FCE4AB0099B055 /* Embed Frameworks */, 50C385AF240E438B00AF2719 /* CopyFiles */, + 501577C92E6BC5B4004A37D0 /* Embed XPC Services */, ); buildRules = ( ); dependencies = ( 50142167278126B500BBAA70 /* PBXTargetDependency */, + 501577C72E6BC5B4004A37D0 /* PBXTargetDependency */, ); name = Secretive; packageProductDependencies = ( @@ -393,10 +540,14 @@ 50A3B78724026B7500D209EA /* Frameworks */, 50A3B78824026B7500D209EA /* Resources */, 50A5C18E240E4B4B00E2996C /* Embed Frameworks */, + 501577D22E6BC5D4004A37D0 /* Embed XPC Services */, ); buildRules = ( ); dependencies = ( + 501577D12E6BC5D4004A37D0 /* PBXTargetDependency */, + 501577D42E6BC5DD004A37D0 /* PBXTargetDependency */, + 500F04F22E6CDF20001CF06E /* PBXTargetDependency */, ); name = SecretAgent; packageProductDependencies = ( @@ -417,10 +568,16 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1220; + LastSwiftUpdateCheck = 2600; LastUpgradeCheck = 2600; ORGANIZATIONNAME = "Max Goedjen"; TargetAttributes = { + 500F04D62E6CDEAB001CF06E = { + CreatedOnToolsVersion = 26.0; + }; + 501577BC2E6BC5B4004A37D0 = { + CreatedOnToolsVersion = 26.0; + }; 50617D7E23FCE48D0099B055 = { CreatedOnToolsVersion = 11.3; }; @@ -453,11 +610,27 @@ targets = ( 50617D7E23FCE48D0099B055 /* Secretive */, 50A3B78924026B7500D209EA /* SecretAgent */, + 501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */, + 500F04D62E6CDEAB001CF06E /* AgentRequestParser */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 500F04D52E6CDEAB001CF06E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 501577BB2E6BC5B4004A37D0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50617D7D23FCE48D0099B055 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -485,6 +658,22 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 500F04D32E6CDEAB001CF06E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 500F04EB2E6CDEB0001CF06E /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 501577B92E6BC5B4004A37D0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 501577DA2E6BC5F3004A37D0 /* main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50617D7B23FCE48D0099B055 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -530,17 +719,38 @@ files = ( 50020BB024064869003D4025 /* AppDelegate.swift in Sources */, 5018F54F24064786002EB505 /* Notifier.swift in Sources */, + 501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 500F04F22E6CDF20001CF06E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 500F04D62E6CDEAB001CF06E /* AgentRequestParser */; + targetProxy = 500F04F12E6CDF20001CF06E /* PBXContainerItemProxy */; + }; 50142167278126B500BBAA70 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 50A3B78924026B7500D209EA /* SecretAgent */; targetProxy = 50142166278126B500BBAA70 /* PBXContainerItemProxy */; }; + 501577C72E6BC5B4004A37D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */; + targetProxy = 501577C62E6BC5B4004A37D0 /* PBXContainerItemProxy */; + }; + 501577D12E6BC5D4004A37D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */; + targetProxy = 501577D02E6BC5D4004A37D0 /* PBXContainerItemProxy */; + }; + 501577D42E6BC5DD004A37D0 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 501577BC2E6BC5B4004A37D0 /* ReleasesDownloader */; + targetProxy = 501577D32E6BC5DD004A37D0 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -555,6 +765,223 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 500F04E32E6CDEAB001CF06E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AgentRequestParser/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AgentRequestParser; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.AgentRequestParser; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 500F04E42E6CDEAB001CF06E /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AgentRequestParser/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AgentRequestParser; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.AgentRequestParser; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Test; + }; + 500F04E52E6CDEAB001CF06E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = AgentRequestParser/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = AgentRequestParser; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.AgentRequestParser; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 501577CA2E6BC5B4004A37D0 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ReleasesDownloader/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReleasesDownloader; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.ReleasesDownloader; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 501577CB2E6BC5B4004A37D0 /* Test */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ReleasesDownloader/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReleasesDownloader; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.ReleasesDownloader; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Test; + }; + 501577CC2E6BC5B4004A37D0 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_IDENTITY = "Developer ID Application"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = Z72PRUAWF6; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; + ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; + ENABLE_RESOURCE_ACCESS_CALENDARS = NO; + ENABLE_RESOURCE_ACCESS_CAMERA = NO; + ENABLE_RESOURCE_ACCESS_CONTACTS = NO; + ENABLE_RESOURCE_ACCESS_LOCATION = NO; + ENABLE_RESOURCE_ACCESS_PRINTING = NO; + ENABLE_RESOURCE_ACCESS_USB = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ReleasesDownloader/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = ReleasesDownloader; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Max Goedjen. All rights reserved."; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.Secretive.ReleasesDownloader; + PRODUCT_NAME = "$(TARGET_NAME)"; + REGISTER_APP_GROUPS = YES; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 50617D9B23FCE48E0099B055 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 508A58AB241E121B0069DC07 /* Config.xcconfig */; @@ -712,7 +1139,7 @@ ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; @@ -752,7 +1179,7 @@ ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; @@ -864,7 +1291,7 @@ ENABLE_ENHANCED_SECURITY = YES; ENABLE_HARDENED_RUNTIME = NO; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_POINTER_AUTHENTICATION = YES; ENABLE_PREVIEWS = YES; ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; @@ -898,7 +1325,7 @@ ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_PREVIEWS = YES; ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; @@ -933,7 +1360,7 @@ ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_PREVIEWS = YES; ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; @@ -969,7 +1396,7 @@ ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; - ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = NO; ENABLE_PREVIEWS = YES; ENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO; ENABLE_RESOURCE_ACCESS_BLUETOOTH = NO; @@ -995,6 +1422,26 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 500F04E22E6CDEAB001CF06E /* Build configuration list for PBXNativeTarget "AgentRequestParser" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 500F04E32E6CDEAB001CF06E /* Debug */, + 500F04E42E6CDEAB001CF06E /* Test */, + 500F04E52E6CDEAB001CF06E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 501577CE2E6BC5B4004A37D0 /* Build configuration list for PBXNativeTarget "ReleasesDownloader" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 501577CA2E6BC5B4004A37D0 /* Debug */, + 501577CB2E6BC5B4004A37D0 /* Test */, + 501577CC2E6BC5B4004A37D0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 50617D7A23FCE48D0099B055 /* Build configuration list for PBXProject "Secretive" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1060,10 +1507,18 @@ isa = XCSwiftPackageProductDependency; productName = SmartCardSecretKit; }; + 500F04EE2E6CDEF4001CF06E /* SecretAgentKit */ = { + isa = XCSwiftPackageProductDependency; + productName = SecretAgentKit; + }; 501421612781262300BBAA70 /* Brief */ = { isa = XCSwiftPackageProductDependency; productName = Brief; }; + 501577DE2E6BC647004A37D0 /* Brief */ = { + isa = XCSwiftPackageProductDependency; + productName = Brief; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 50617D7723FCE48D0099B055 /* Project object */; diff --git a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/PackageTests.xcscheme b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/PackageTests.xcscheme new file mode 100644 index 0000000..500661b --- /dev/null +++ b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/PackageTests.xcscheme @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + +