mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-09-15 08:50:57 +00:00
.
This commit is contained in:
parent
11074999ad
commit
2efba1bc21
@ -6,17 +6,37 @@ private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent.Age
|
|||||||
|
|
||||||
func handleRequest(_ request: XPCListener.IncomingSessionRequest) -> XPCListener.IncomingSessionRequest.Decision {
|
func handleRequest(_ request: XPCListener.IncomingSessionRequest) -> XPCListener.IncomingSessionRequest.Decision {
|
||||||
logger.log("Parser received inbound request")
|
logger.log("Parser received inbound request")
|
||||||
return request.accept { message in
|
return request.accept { xpcDictionary in
|
||||||
|
xpcDictionary.handoffReply(to: .global(qos: .userInteractive)) {
|
||||||
logger.log("Parser accepted inbound request")
|
logger.log("Parser accepted inbound request")
|
||||||
do {
|
do {
|
||||||
let result = try SSHAgentInputParser().parse(data: message)
|
let parser = SSHAgentInputParser()
|
||||||
|
let result = try parser.parse(data: xpcDictionary.decode(as: Data.self))
|
||||||
logger.log("Parser parsed message as type \(result.debugDescription)")
|
logger.log("Parser parsed message as type \(result.debugDescription)")
|
||||||
return result
|
xpcDictionary.reply(result)
|
||||||
} catch {
|
} catch let error as SSHAgentInputParser.AgentParsingError {
|
||||||
logger.error("Parser failed with error \(error)")
|
logger.error("Parser failed with error \(error)")
|
||||||
return nil
|
xpcDictionary.reply(error)
|
||||||
|
} catch {
|
||||||
|
// This should never actually happen because SSHAgentInputParser is a typed thrower, but the type system doesn't seem to know that across framework boundaries?
|
||||||
|
logger.error("Parser failed with unknown error \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct WrappedError<Wrapped: Codable & Error>: Codable {
|
||||||
|
|
||||||
|
public struct DescriptionOnlyError: Error, Codable {
|
||||||
|
let localizedDescription: String
|
||||||
|
}
|
||||||
|
|
||||||
|
public let wrapped: Wrapped
|
||||||
|
|
||||||
|
public init(_ error: Wrapped) {
|
||||||
|
wrapped = error
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -45,6 +45,8 @@ extension Agent {
|
|||||||
response.append(SSHAgent.Response.agentSignResponse.data)
|
response.append(SSHAgent.Response.agentSignResponse.data)
|
||||||
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance))
|
response.append(try await sign(data: context.dataToSign, keyBlob: context.keyBlob, provenance: provenance))
|
||||||
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
logger.debug("Agent returned \(SSHAgent.Response.agentSignResponse.debugDescription)")
|
||||||
|
case .unknown(let value):
|
||||||
|
logger.error("Agent received unknown request of type \(value).")
|
||||||
default:
|
default:
|
||||||
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
logger.debug("Agent received valid request of type \(request.debugDescription), but not currently supported.")
|
||||||
throw UnhandledRequestError()
|
throw UnhandledRequestError()
|
||||||
|
@ -7,25 +7,25 @@ final class OpenSSHReader {
|
|||||||
|
|
||||||
/// Initialize the reader with an OpenSSH data payload.
|
/// Initialize the reader with an OpenSSH data payload.
|
||||||
/// - Parameter data: The data to read.
|
/// - Parameter data: The data to read.
|
||||||
public init(data: Data) {
|
init(data: Data) {
|
||||||
remaining = Data(data)
|
remaining = Data(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the next chunk of data from the playload.
|
/// Reads the next chunk of data from the playload.
|
||||||
/// - Returns: The next chunk of data.
|
/// - 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 littleEndianLength = try readNextBytes(as: UInt32.self)
|
||||||
let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength)
|
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..<length
|
let dataRange = 0..<length
|
||||||
let ret = Data(remaining[dataRange])
|
let ret = Data(remaining[dataRange])
|
||||||
remaining.removeSubrange(dataRange)
|
remaining.removeSubrange(dataRange)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
public func readNextBytes<T>(as: T.Type) throws -> T {
|
func readNextBytes<T>(as: T.Type) throws(OpenSSHReaderError) -> T {
|
||||||
let size = MemoryLayout<T>.size
|
let size = MemoryLayout<T>.size
|
||||||
guard remaining.count >= size else { throw EndOfData() }
|
guard remaining.count >= size else { throw .beyondBounds }
|
||||||
let lengthRange = 0..<size
|
let lengthRange = 0..<size
|
||||||
let lengthChunk = remaining[lengthRange]
|
let lengthChunk = remaining[lengthRange]
|
||||||
remaining.removeSubrange(lengthRange)
|
remaining.removeSubrange(lengthRange)
|
||||||
@ -33,10 +33,12 @@ final class OpenSSHReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public func readNextChunkAsString() throws -> String {
|
func readNextChunkAsString() throws -> String {
|
||||||
try String(decoding: readNextChunk(), as: UTF8.self)
|
try String(decoding: readNextChunk(), as: UTF8.self)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct EndOfData: Error {}
|
}
|
||||||
|
|
||||||
|
public enum OpenSSHReaderError: Error, Codable {
|
||||||
|
case beyondBounds
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parse(data: Data) throws -> SSHAgent.Request {
|
public func parse(data: Data) throws(AgentParsingError) -> SSHAgent.Request {
|
||||||
logger.debug("Parsing new data")
|
logger.debug("Parsing new data")
|
||||||
guard data.count > 4 else {
|
guard data.count > 4 else {
|
||||||
throw InvalidDataProvidedError()
|
throw .invalidData
|
||||||
}
|
}
|
||||||
let specifiedLength = (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
|
let specifiedLength = (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
|
||||||
let rawRequestInt = data[4]
|
let rawRequestInt = data[4]
|
||||||
@ -29,7 +29,11 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
case SSHAgent.Request.requestIdentities.protocolID:
|
case SSHAgent.Request.requestIdentities.protocolID:
|
||||||
return .requestIdentities
|
return .requestIdentities
|
||||||
case SSHAgent.Request.signRequest(.empty).protocolID:
|
case SSHAgent.Request.signRequest(.empty).protocolID:
|
||||||
|
do {
|
||||||
return .signRequest(try signatureRequestContext(from: body))
|
return .signRequest(try signatureRequestContext(from: body))
|
||||||
|
} catch {
|
||||||
|
throw .openSSHReader(error)
|
||||||
|
}
|
||||||
case SSHAgent.Request.addIdentity.protocolID:
|
case SSHAgent.Request.addIdentity.protocolID:
|
||||||
return .addIdentity
|
return .addIdentity
|
||||||
case SSHAgent.Request.removeIdentity.protocolID:
|
case SSHAgent.Request.removeIdentity.protocolID:
|
||||||
@ -59,7 +63,7 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
|
|
||||||
extension SSHAgentInputParser {
|
extension SSHAgentInputParser {
|
||||||
|
|
||||||
func signatureRequestContext(from data: Data) throws -> SSHAgent.Request.SignatureRequestContext {
|
func signatureRequestContext(from data: Data) throws(OpenSSHReaderError) -> SSHAgent.Request.SignatureRequestContext {
|
||||||
let reader = OpenSSHReader(data: data)
|
let reader = OpenSSHReader(data: data)
|
||||||
let rawKeyBlob = try reader.readNextChunk()
|
let rawKeyBlob = try reader.readNextChunk()
|
||||||
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
let keyBlob = certificatePublicKeyBlob(from: rawKeyBlob) ?? rawKeyBlob
|
||||||
@ -95,8 +99,11 @@ extension SSHAgentInputParser {
|
|||||||
|
|
||||||
extension SSHAgentInputParser {
|
extension SSHAgentInputParser {
|
||||||
|
|
||||||
struct AgentUnknownRequestError: Error {}
|
public enum AgentParsingError: Error, Codable {
|
||||||
struct AgentUnhandledRequestError: Error {}
|
case unknownRequest
|
||||||
struct InvalidDataProvidedError: Error {}
|
case unhandledRequest
|
||||||
|
case invalidData
|
||||||
|
case openSSHReader(OpenSSHReaderError)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,13 +35,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
logger.debug("SecretAgent finished launching")
|
logger.debug("SecretAgent finished launching")
|
||||||
Task {
|
Task {
|
||||||
let inputParser = try XPCAgentInputParser()
|
let inputParser = try XPCAgentInputParser()
|
||||||
Task {
|
|
||||||
try? await Task.sleep(for: .seconds(1))
|
|
||||||
var len = (5 as UInt32).littleEndian
|
|
||||||
var raw = SSHAgent.Request.requestIdentities.protocolID
|
|
||||||
let data = Data(bytes: &len, count: MemoryLayout<UInt32>.size) + Data(bytes: &raw, count: MemoryLayout<UInt8>.size)
|
|
||||||
print(try? await inputParser.parse(data: data))
|
|
||||||
}
|
|
||||||
for await session in socketController.sessions {
|
for await session in socketController.sessions {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
|
|
||||||
|
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH
|
||||||
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
||||||
|
|
||||||
private let session: XPCSession
|
private let session: XPCSession
|
||||||
@ -8,11 +9,15 @@ public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
|
|
||||||
public init() throws {
|
public init() throws {
|
||||||
if #available(macOS 26.0, *) {
|
if #available(macOS 26.0, *) {
|
||||||
session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.AgentRequestParser", targetQueue: queue, options: .inactive, requirement: .isFromSameTeam())
|
session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.AgentRequestParser", targetQueue: queue, requirement: .isFromSameTeam())
|
||||||
} else {
|
} else {
|
||||||
session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.AgentRequestParser", targetQueue: queue, options: .inactive)
|
session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.AgentRequestParser", targetQueue: queue)
|
||||||
|
}
|
||||||
|
Task {
|
||||||
|
// Warm up the XPC endpoint.
|
||||||
|
_ = try? await parse(data: Data())
|
||||||
|
|
||||||
}
|
}
|
||||||
try session.activate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parse(data: Data) async throws -> SSHAgent.Request {
|
public func parse(data: Data) async throws -> SSHAgent.Request {
|
||||||
@ -21,10 +26,12 @@ public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
try session.send(data) { result in
|
try session.send(data) { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let result):
|
case .success(let result):
|
||||||
do {
|
if let result = try? result.decode(as: SSHAgent.Request.self) {
|
||||||
continuation.resume(returning: try result.decode(as: SSHAgent.Request.self))
|
continuation.resume(returning: result)
|
||||||
} catch {
|
} else if let error = try? result.decode(as: SSHAgentInputParser.AgentParsingError.self) {
|
||||||
continuation.resume(throwing: error)
|
continuation.resume(throwing: error)
|
||||||
|
} else {
|
||||||
|
continuation.resume(throwing: UnknownMessageError())
|
||||||
}
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
continuation.resume(throwing: error)
|
continuation.resume(throwing: error)
|
||||||
@ -40,4 +47,6 @@ public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
|||||||
session.cancel(reason: "Done")
|
session.cancel(reason: "Done")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UnknownMessageError: Error {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user