diff --git a/SecretAgentKit/Agent.swift b/SecretAgentKit/Agent.swift index ead95db..3d48fbd 100644 --- a/SecretAgentKit/Agent.swift +++ b/SecretAgentKit/Agent.swift @@ -46,8 +46,9 @@ extension Agent { 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: reader.fileDescriptor)) + response.append(try sign(data: data, provenance: provenance)) os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription) } } catch { @@ -81,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 { @@ -89,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) } diff --git a/SecretAgentKit/FileHandleProtocols.swift b/SecretAgentKit/FileHandleProtocols.swift index 728268d..ba806ff 100644 --- a/SecretAgentKit/FileHandleProtocols.swift +++ b/SecretAgentKit/FileHandleProtocols.swift @@ -4,6 +4,7 @@ public protocol FileHandleReader { var availableData: Data { get } var fileDescriptor: Int32 { get } + var pidOfConnectedProcess: Int32 { get } } @@ -13,4 +14,13 @@ public protocol FileHandleWriter { } -extension FileHandle: FileHandleReader, FileHandleWriter {} +extension FileHandle: FileHandleReader, FileHandleWriter { + + public var pidOfConnectedProcess: Int32 { + let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: 4, 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/SecretAgentKit/SigningRequestProvenance.swift b/SecretAgentKit/SigningRequestProvenance.swift index f06a1fa..b112ae5 100644 --- a/SecretAgentKit/SigningRequestProvenance.swift +++ b/SecretAgentKit/SigningRequestProvenance.swift @@ -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 diff --git a/SecretAgentKit/SigningRequestTracer.swift b/SecretAgentKit/SigningRequestTracer.swift index 625ad5e..8c6288d 100644 --- a/SecretAgentKit/SigningRequestTracer.swift +++ b/SecretAgentKit/SigningRequestTracer.swift @@ -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.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 { diff --git a/SecretAgentKitTests/AgentTests.swift b/SecretAgentKitTests/AgentTests.swift index 8179143..c570018 100644 --- a/SecretAgentKitTests/AgentTests.swift +++ b/SecretAgentKitTests/AgentTests.swift @@ -35,17 +35,26 @@ class AgentTests: XCTestCase { } func testSignature() { - - } - - func testEndToEnd() { - + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret]) + let agent = Agent(storeList: list) + agent.handle(reader: stubReader, writer: stubWriter) + let reader = OpenSSHReader(data: stubWriter.data) + // TODO: VERIFY + print(stubWriter.data.base64EncodedString()) } // MARK: Witness protocol func testWitnessObjectionStopsRequest() { - + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret]) + let witness = StubWitness(speakNow: { _,_ in + return true + }, witness: { _, _ in }) + let agent = Agent(storeList: list, witness: witness) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure) } func testWitnessSignature() { @@ -57,13 +66,34 @@ class AgentTests: XCTestCase { } func testRequestTracing() { - + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret]) + var speakNowTrace: SigningRequestProvenance! = nil + var witnessTrace: SigningRequestProvenance! = nil + let witness = StubWitness(speakNow: { _, trace in + speakNowTrace = trace + return false + }, witness: { _, trace in + witnessTrace = trace + }) + let agent = Agent(storeList: list, witness: witness) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertEqual(witnessTrace, speakNowTrace) + XCTAssertEqual(witnessTrace.origin.name, "Xcode") + XCTAssertEqual(witnessTrace.origin.validSignature, true) + XCTAssertEqual(witnessTrace.origin.parentPID, 1) } // MARK: Exception Handling func testSignatureException() { - + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) + let store = list.stores.first?.base as! Stub.Store + store.shouldThrow = true + let agent = Agent(storeList: list) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure) } // MARK: Unsupported @@ -79,8 +109,8 @@ class AgentTests: XCTestCase { extension AgentTests { - func storeList(with secrets: [SmartCard.Secret]) -> SecretStoreList { - let store = StubStore() + func storeList(with secrets: [Stub.Secret]) -> SecretStoreList { + let store = Stub.Store() store.secrets.append(contentsOf: secrets) let storeList = SecretStoreList() storeList.add(store: store) @@ -93,6 +123,7 @@ extension AgentTests { 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 { @@ -102,8 +133,8 @@ extension AgentTests { } enum Secrets { - static let ecdsa256Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 256)", algorithm: .ellipticCurve, keySize: 256, publicKey: Data(base64Encoded: "BOVEjgAA5PHqRgwykjN5qM21uWCHFSY/Sqo5gkHAkn+e1MMQKHOLga7ucB9b3mif33MBid59GRK9GEPVlMiSQwo=")!) - static let ecdsa384Secret = SmartCard.Secret(id: Data(), name: "Test Key (ECDSA 384)", algorithm: .ellipticCurve, keySize: 384, publicKey: Data(base64Encoded: "BG2MNc/C5OTHFE2tBvbZCVcpOGa8vBMquiTLkH4lwkeqOPxhi+PyYUfQZMTRJNPiTyWPoMBqNiCIFRVv60yPN/AHufHaOgbdTP42EgMlMMImkAjYUEv9DESHTVIs2PW1yQ==")!) + static let ecdsa256Secret = Stub.Secret(keySize: 256, publicKey: Data(base64Encoded: "BKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08=")!, privateKey: Data(base64Encoded: "BKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy09nw780wy/TSfUmzj15iJkV234AaCLNl+H8qFL6qK8VIg==")!) + static let ecdsa384Secret = Stub.Secret(keySize: 384, publicKey: Data(base64Encoded: "BLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDA==")!, privateKey: Data(base64Encoded: "BLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDHNapAOzrt9E+9QC4/KYoXS7Uw4pmdAz53uIj02tttiq3c0ZyIQ7XoscWWRqRrz8Kw==")!) } } diff --git a/SecretAgentKitTests/StubFileHandleReader.swift b/SecretAgentKitTests/StubFileHandleReader.swift index 963fc3f..69cea14 100644 --- a/SecretAgentKitTests/StubFileHandleReader.swift +++ b/SecretAgentKitTests/StubFileHandleReader.swift @@ -5,7 +5,10 @@ struct StubFileHandleReader: FileHandleReader { let availableData: Data var fileDescriptor: Int32 { - return NSRunningApplication.current.processIdentifier + NSWorkspace.shared.runningApplications.filter({ $0.localizedName == "Xcode" }).first!.processIdentifier + } + var pidOfConnectedProcess: Int32 { + NSWorkspace.shared.runningApplications.filter({ $0.localizedName == "Xcode" }).first!.processIdentifier } } diff --git a/SecretAgentKitTests/StubStore.swift b/SecretAgentKitTests/StubStore.swift index cc7b7b6..a226796 100644 --- a/SecretAgentKitTests/StubStore.swift +++ b/SecretAgentKitTests/StubStore.swift @@ -11,9 +11,11 @@ extension Stub { public let id = UUID() public let name = "Stub" public var secrets: [Secret] = [] + public var shouldThrow = false public init() { - // try! create(size: 256) +// try! create(size: 256) +// try! create(size: 384) } public func create(size: Int) throws { @@ -47,6 +49,9 @@ extension Stub { } public func sign(data: Data, with secret: Secret) throws -> Data { + guard !shouldThrow else { + throw NSError() + } let privateKey = SecKeyCreateWithData(secret.privateKey as CFData, [ kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits: secret.keySize, @@ -62,7 +67,7 @@ extension Stub { default: fatalError() } - return SecKeyCreateSignature(privateKey, signatureAlgorithm, data as CFData, nil) as! Data + return SecKeyCreateSignature(privateKey, signatureAlgorithm, data as CFData, nil)! as Data } } diff --git a/SecretAgentKitTests/StubWitness.swift b/SecretAgentKitTests/StubWitness.swift new file mode 100644 index 0000000..230c009 --- /dev/null +++ b/SecretAgentKitTests/StubWitness.swift @@ -0,0 +1,32 @@ +import SecretKit +import SecretAgentKit + +struct StubWitness { + + let speakNow: (AnySecret, SigningRequestProvenance) -> Bool + let witness: (AnySecret, SigningRequestProvenance) -> () + +} + +extension StubWitness: SigningWitness { + + func speakNowOrForeverHoldYourPeace(forAccessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws { + let objection = speakNow(secret, provenance) + if objection { + throw TheresMyChance() + } + } + + func witness(accessTo secret: AnySecret, by provenance: SigningRequestProvenance) throws { + witness(secret, provenance) + } + +} + +extension StubWitness { + + struct TheresMyChance: Error { + + } + +} diff --git a/Secretive.xcodeproj/project.pbxproj b/Secretive.xcodeproj/project.pbxproj index daa9155..bcb61cd 100644 --- a/Secretive.xcodeproj/project.pbxproj +++ b/Secretive.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 507EE34824281FB8003C4FE3 /* StubFileHandleWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */; }; 507EE34A2428263B003C4FE3 /* StubStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE3492428263B003C4FE3 /* StubStore.swift */; }; 507EE34C24282B4C003C4FE3 /* StubStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE3492428263B003C4FE3 /* StubStore.swift */; }; + 507EE34E2428784F003C4FE3 /* StubWitness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34D2428784F003C4FE3 /* StubWitness.swift */; }; 508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58A9241E06B40069DC07 /* PreviewUpdater.swift */; }; 508A58B3241ED2180069DC07 /* AgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */; }; 508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508A58B4241ED48F0069DC07 /* PreviewAgentStatusChecker.swift */; }; @@ -247,6 +248,7 @@ 507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubFileHandleReader.swift; sourceTree = ""; }; 507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubFileHandleWriter.swift; sourceTree = ""; }; 507EE3492428263B003C4FE3 /* StubStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubStore.swift; sourceTree = ""; }; + 507EE34D2428784F003C4FE3 /* StubWitness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubWitness.swift; sourceTree = ""; }; 508A58A9241E06B40069DC07 /* PreviewUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewUpdater.swift; sourceTree = ""; }; 508A58AB241E121B0069DC07 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentStatusChecker.swift; sourceTree = ""; }; @@ -550,6 +552,7 @@ 5099A07B240242BA0062B6F2 /* AgentTests.swift */, 507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */, 507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */, + 507EE34D2428784F003C4FE3 /* StubWitness.swift */, 507EE3492428263B003C4FE3 /* StubStore.swift */, 5099A07D240242BA0062B6F2 /* Info.plist */, ); @@ -984,6 +987,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 507EE34E2428784F003C4FE3 /* StubWitness.swift in Sources */, 507EE34624281F89003C4FE3 /* StubFileHandleReader.swift in Sources */, 507EE34A2428263B003C4FE3 /* StubStore.swift in Sources */, 5099A07C240242BA0062B6F2 /* AgentTests.swift in Sources */,