Agent Tests (#74)

This commit is contained in:
Max Goedjen
2020-03-23 23:22:22 -07:00
committed by GitHub
parent 1f7ebcfe75
commit 4dcc9b113d
15 changed files with 418 additions and 44 deletions

View File

@@ -21,28 +21,34 @@ public class Agent {
extension Agent {
public func handle(fileHandle: FileHandle) {
public func handle(reader: FileHandleReader, writer: FileHandleWriter) {
os_log(.debug, "Agent handling new data")
let data = fileHandle.availableData
let data = reader.availableData
guard !data.isEmpty else { return }
let requestTypeInt = data[4]
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { return }
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else {
writer.write(OpenSSHKeyWriter().lengthAndData(of: SSHAgent.ResponseType.agentFailure.data))
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
return
}
os_log(.debug, "Agent handling request of type %@", requestType.debugDescription)
let subData = Data(data[5...])
handle(requestType: requestType, data: subData, fileHandle: fileHandle)
let response = handle(requestType: requestType, data: subData, reader: reader)
writer.write(response)
}
func handle(requestType: SSHAgent.RequestType, data: Data, fileHandle: FileHandle) {
func handle(requestType: SSHAgent.RequestType, data: Data, reader: FileHandleReader) -> Data {
var response = Data()
do {
switch requestType {
case .requestIdentities:
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
response.append(try identities())
response.append(identities())
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)
case .signRequest:
let provenance = requestTracer.provenance(from: reader)
response.append(SSHAgent.ResponseType.agentSignResponse.data)
response.append(try sign(data: data, from: fileHandle.fileDescriptor))
response.append(try sign(data: data, provenance: provenance))
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription)
}
} catch {
@@ -51,14 +57,14 @@ extension Agent {
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
}
let full = OpenSSHKeyWriter().lengthAndData(of: response)
fileHandle.write(full)
return full
}
}
extension Agent {
func identities() throws -> Data {
func identities() -> Data {
// TODO: RESTORE ONCE XCODE 11.4 IS GM
let secrets = storeList.stores.flatMap { $0.secrets }
// let secrets = storeList.stores.flatMap(\.secrets)
@@ -76,7 +82,7 @@ extension Agent {
return countData + keyData
}
func sign(data: Data, from pid: Int32) throws -> Data {
func sign(data: Data, provenance: SigningRequestProvenance) throws -> Data {
let reader = OpenSSHReader(data: data)
let hash = reader.readNextChunk()
guard let (store, secret) = secret(matching: hash) else {
@@ -84,7 +90,6 @@ extension Agent {
throw AgentError.noMatchingKey
}
let provenance = requestTracer.provenance(from: pid)
if let witness = witness {
try witness.speakNowOrForeverHoldYourPeace(forAccessTo: secret, by: provenance)
}
@@ -103,7 +108,7 @@ extension Agent {
case (.ellipticCurve, 384):
rawRepresentation = try CryptoKit.P384.Signing.ECDSASignature(derRepresentation: derSignature).rawRepresentation
default:
fatalError()
throw AgentError.unsupportedKeyType
}
@@ -154,6 +159,7 @@ extension Agent {
enum AgentError: Error {
case unhandledType
case noMatchingKey
case unsupportedKeyType
}
}

View File

@@ -0,0 +1,26 @@
import Foundation
public protocol FileHandleReader {
var availableData: Data { get }
var fileDescriptor: Int32 { get }
var pidOfConnectedProcess: Int32 { get }
}
public protocol FileHandleWriter {
func write(_ data: Data)
}
extension FileHandle: FileHandleReader, FileHandleWriter {
public var pidOfConnectedProcess: Int32 {
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1)
var len = socklen_t(MemoryLayout<Int32>.size)
getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
return pidPointer.load(as: Int32.self)
}
}

View File

@@ -12,7 +12,7 @@ extension SSHAgent {
switch self {
case .requestIdentities:
return "RequestIdentities"
default:
case .signRequest:
return "SignRequest"
}
}

View File

@@ -1,7 +1,7 @@
import Foundation
import AppKit
public struct SigningRequestProvenance {
public struct SigningRequestProvenance: Equatable {
public var chain: [Process]
public init(root: Process) {
@@ -24,7 +24,7 @@ extension SigningRequestProvenance {
extension SigningRequestProvenance {
public struct Process {
public struct Process: Equatable {
public let pid: Int32
public let name: String

View File

@@ -4,12 +4,8 @@ import Security
struct SigningRequestTracer {
func provenance(from pid: Int32) -> SigningRequestProvenance {
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: 1)
var len = socklen_t(MemoryLayout<Int32>.size)
getsockopt(pid, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
let pid = pidPointer.load(as: Int32.self)
let firstInfo = process(from: pid)
func provenance(from fileHandleReader: FileHandleReader) -> SigningRequestProvenance {
let firstInfo = process(from: fileHandleReader.pidOfConnectedProcess)
var provenance = SigningRequestProvenance(root: firstInfo)
while NSRunningApplication(processIdentifier: provenance.origin.pid) == nil && provenance.origin.parentPID != nil {

View File

@@ -5,7 +5,7 @@ public class SocketController {
fileprivate var fileHandle: FileHandle?
fileprivate var port: SocketPort?
public var handler: ((FileHandle) -> Void)?
public var handler: ((FileHandleReader, FileHandleWriter) -> Void)?
public init(path: String) {
os_log(.debug, "Socket controller setting up at %@", path)
@@ -52,7 +52,7 @@ public class SocketController {
@objc func handleConnectionAccept(notification: Notification) {
os_log(.debug, "Socket controller accepted connection")
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
handler?(new)
handler?(new, new)
new.waitForDataInBackgroundAndNotify()
fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!])
}
@@ -61,7 +61,7 @@ public class SocketController {
os_log(.debug, "Socket controller has new data available")
guard let new = notification.object as? FileHandle else { return }
os_log(.debug, "Socket controller received new file handle")
handler?(new)
handler?(new, new)
}
}