From 4dcc9b113d8ad3fae530dddc25e0c20ba2bfca11 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Mon, 23 Mar 2020 23:22:22 -0700 Subject: [PATCH] Agent Tests (#74) --- SecretAgent/AppDelegate.swift | 2 +- SecretAgentKit/Agent.swift | 30 ++-- SecretAgentKit/FileHandleProtocols.swift | 26 +++ SecretAgentKit/SSHAgentProtocol.swift | 2 +- SecretAgentKit/SigningRequestProvenance.swift | 4 +- SecretAgentKit/SigningRequestTracer.swift | 8 +- SecretAgentKit/SocketController.swift | 6 +- SecretAgentKitTests/AgentTests.swift | 162 ++++++++++++++++++ SecretAgentKitTests/SecretAgentKitTests.swift | 11 -- .../StubFileHandleReader.swift | 14 ++ .../StubFileHandleWriter.swift | 11 ++ SecretAgentKitTests/StubStore.swift | 113 ++++++++++++ SecretAgentKitTests/StubWitness.swift | 32 ++++ SecretKit/Secret.swift | 2 +- Secretive.xcodeproj/project.pbxproj | 39 ++++- 15 files changed, 418 insertions(+), 44 deletions(-) create mode 100644 SecretAgentKit/FileHandleProtocols.swift create mode 100644 SecretAgentKitTests/AgentTests.swift delete mode 100644 SecretAgentKitTests/SecretAgentKitTests.swift create mode 100644 SecretAgentKitTests/StubFileHandleReader.swift create mode 100644 SecretAgentKitTests/StubFileHandleWriter.swift create mode 100644 SecretAgentKitTests/StubStore.swift create mode 100644 SecretAgentKitTests/StubWitness.swift diff --git a/SecretAgent/AppDelegate.swift b/SecretAgent/AppDelegate.swift index e1cfe7c..0f0b0a2 100644 --- a/SecretAgent/AppDelegate.swift +++ b/SecretAgent/AppDelegate.swift @@ -28,7 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { os_log(.debug, "SecretAgent finished launching") DispatchQueue.main.async { - self.socketController.handler = self.agent.handle(fileHandle:) + self.socketController.handler = self.agent.handle(reader:writer:) } notifier.prompt() updateSink = updater.$update.sink { update in diff --git a/SecretAgentKit/Agent.swift b/SecretAgentKit/Agent.swift index 25185ca..f10f882 100644 --- a/SecretAgentKit/Agent.swift +++ b/SecretAgentKit/Agent.swift @@ -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 } } diff --git a/SecretAgentKit/FileHandleProtocols.swift b/SecretAgentKit/FileHandleProtocols.swift new file mode 100644 index 0000000..ba806ff --- /dev/null +++ b/SecretAgentKit/FileHandleProtocols.swift @@ -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.size) + getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len) + return pidPointer.load(as: Int32.self) + } + +} diff --git a/SecretAgentKit/SSHAgentProtocol.swift b/SecretAgentKit/SSHAgentProtocol.swift index 6164875..3de8ef5 100644 --- a/SecretAgentKit/SSHAgentProtocol.swift +++ b/SecretAgentKit/SSHAgentProtocol.swift @@ -12,7 +12,7 @@ extension SSHAgent { switch self { case .requestIdentities: return "RequestIdentities" - default: + case .signRequest: return "SignRequest" } } 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/SecretAgentKit/SocketController.swift b/SecretAgentKit/SocketController.swift index ea82b16..1fc51d4 100644 --- a/SecretAgentKit/SocketController.swift +++ b/SecretAgentKit/SocketController.swift @@ -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) } } diff --git a/SecretAgentKitTests/AgentTests.swift b/SecretAgentKitTests/AgentTests.swift new file mode 100644 index 0000000..c5c7bfd --- /dev/null +++ b/SecretAgentKitTests/AgentTests.swift @@ -0,0 +1,162 @@ +import Foundation +import XCTest +import CryptoKit +@testable import SecretKit +@testable import SecretAgentKit + +class AgentTests: XCTestCase { + + let stubWriter = StubFileHandleWriter() + + // MARK: Identity Listing + + func testEmptyStores() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities) + let agent = Agent(storeList: SecretStoreList()) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertEqual(stubWriter.data, Constants.Responses.requestIdentitiesEmpty) + } + + func testIdentitiesList() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) + let agent = Agent(storeList: list) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertEqual(stubWriter.data, Constants.Responses.requestIdentitiesMultiple) + } + + // MARK: Signatures + + func testNoMatchingIdentities() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignatureWithNoneMatching) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) + let agent = Agent(storeList: list) + agent.handle(reader: stubReader, writer: stubWriter) +// XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure) + } + + func testSignature() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) + let requestReader = OpenSSHReader(data: Constants.Requests.requestSignature[5...]) + _ = requestReader.readNextChunk() + let dataToSign = requestReader.readNextChunk() + let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret]) + let agent = Agent(storeList: list) + 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) + let r = rsData.readNextChunk() + let s = rsData.readNextChunk() + var rs = r + rs.append(s) + let signature = try! P256.Signing.ECDSASignature(rawRepresentation: rs) + let valid = try! P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey).isValidSignature(signature, for: dataToSign) + XCTAssertTrue(valid) + } + + // 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() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) + let list = storeList(with: [Constants.Secrets.ecdsa256Secret]) + var witnessed = false + let witness = StubWitness(speakNow: { _, trace in + return false + }, witness: { _, trace in + witnessed = true + }) + let agent = Agent(storeList: list, witness: witness) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertTrue(witnessed) + } + + 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, "Finder") + XCTAssertEqual(witnessTrace.origin.validSignature, true) + XCTAssertEqual(witnessTrace.origin.parentPID, 1) + } + + // MARK: Exception Handling + + func testSignatureException() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature) + 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 + + func testUnhandledAdd() { + let stubReader = StubFileHandleReader(availableData: Constants.Requests.addIdentity) + let agent = Agent(storeList: SecretStoreList()) + agent.handle(reader: stubReader, writer: stubWriter) + XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure) + } + +} + +extension AgentTests { + + func storeList(with secrets: [Stub.Secret]) -> SecretStoreList { + let store = Stub.Store() + store.secrets.append(contentsOf: secrets) + let storeList = SecretStoreList() + storeList.add(store: store) + return storeList + } + + enum Constants { + + 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 requestFailure = Data(base64Encoded: "AAAAAQU=")! + } + + enum Secrets { + 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/SecretAgentKitTests.swift b/SecretAgentKitTests/SecretAgentKitTests.swift deleted file mode 100644 index 540c1fc..0000000 --- a/SecretAgentKitTests/SecretAgentKitTests.swift +++ /dev/null @@ -1,11 +0,0 @@ -import XCTest -@testable import SecretAgentKit - -class SecretAgentKitTests: XCTestCase { - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - -} diff --git a/SecretAgentKitTests/StubFileHandleReader.swift b/SecretAgentKitTests/StubFileHandleReader.swift new file mode 100644 index 0000000..a9bf274 --- /dev/null +++ b/SecretAgentKitTests/StubFileHandleReader.swift @@ -0,0 +1,14 @@ +import SecretAgentKit +import AppKit + +struct StubFileHandleReader: FileHandleReader { + + let availableData: Data + var fileDescriptor: Int32 { + NSWorkspace.shared.runningApplications.filter({ $0.localizedName == "Finder" }).first!.processIdentifier + } + var pidOfConnectedProcess: Int32 { + fileDescriptor + } + +} diff --git a/SecretAgentKitTests/StubFileHandleWriter.swift b/SecretAgentKitTests/StubFileHandleWriter.swift new file mode 100644 index 0000000..5b63eff --- /dev/null +++ b/SecretAgentKitTests/StubFileHandleWriter.swift @@ -0,0 +1,11 @@ +import SecretAgentKit + +class StubFileHandleWriter: FileHandleWriter { + + var data = Data() + + func write(_ data: Data) { + self.data.append(data) + } + +} diff --git a/SecretAgentKitTests/StubStore.swift b/SecretAgentKitTests/StubStore.swift new file mode 100644 index 0000000..a226796 --- /dev/null +++ b/SecretAgentKitTests/StubStore.swift @@ -0,0 +1,113 @@ +import SecretKit +import CryptoKit + +struct Stub {} + +extension Stub { + + public class Store: SecretStore { + + public let isAvailable = true + 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: 384) + } + + public func create(size: Int) throws { + let flags: SecAccessControlCreateFlags = [] + let access = + SecAccessControlCreateWithFlags(kCFAllocatorDefault, + kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + flags, + nil) as Any + + let attributes = [ + kSecAttrLabel: name, + kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeySizeInBits: size, + kSecPrivateKeyAttrs: [ + kSecAttrIsPermanent: true, + kSecAttrAccessControl: access + ] + ] as CFDictionary + + var privateKey: SecKey! = nil + var publicKey: SecKey! = nil + SecKeyGeneratePair(attributes, &publicKey, &privateKey) + let publicAttributes = SecKeyCopyAttributes(publicKey) as! [CFString: Any] + let privateAttributes = SecKeyCopyAttributes(privateKey) as! [CFString: Any] + let publicData = (publicAttributes[kSecValueData] as! Data) + let privateData = (privateAttributes[kSecValueData] as! Data) + let secret = Secret(keySize: size, publicKey: publicData, privateKey: privateData) + print(secret) + print("Public Key OpenSSH: \(OpenSSHKeyWriter().openSSHString(secret: secret))") + } + + 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, + kSecAttrKeyClass: kSecAttrKeyClassPrivate + ] as CFDictionary + , nil)! + let signatureAlgorithm: SecKeyAlgorithm + switch secret.keySize { + case 256: + signatureAlgorithm = .ecdsaSignatureMessageX962SHA256 + case 384: + signatureAlgorithm = .ecdsaSignatureMessageX962SHA384 + default: + fatalError() + } + return SecKeyCreateSignature(privateKey, signatureAlgorithm, data as CFData, nil)! as Data + } + + } + +} + +extension Stub { + + struct Secret: SecretKit.Secret, CustomDebugStringConvertible { + + let id = UUID().uuidString.data(using: .utf8)! + let name = UUID().uuidString + let algorithm = Algorithm.ellipticCurve + + let keySize: Int + let publicKey: Data + let privateKey: Data + + init(keySize: Int, publicKey: Data, privateKey: Data) { + self.keySize = keySize + self.publicKey = publicKey + self.privateKey = privateKey + } + + var debugDescription: String { + """ + Key Size \(keySize) + Private: \(privateKey.base64EncodedString()) + Public: \(publicKey.base64EncodedString()) + """ + } + + } + +} + + +extension Stub.Store { + + struct StubError: Error { + } + +} 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/SecretKit/Secret.swift b/SecretKit/Secret.swift index 7e54b4c..db71045 100644 --- a/SecretKit/Secret.swift +++ b/SecretKit/Secret.swift @@ -7,7 +7,7 @@ public protocol Secret: Identifiable, Hashable { } -public enum Algorithm { +public enum Algorithm: Hashable { case ellipticCurve public init(secAttr: NSNumber) { let secAttrString = secAttr.stringValue as CFString diff --git a/Secretive.xcodeproj/project.pbxproj b/Secretive.xcodeproj/project.pbxproj index bfb8b48..01a2ce8 100644 --- a/Secretive.xcodeproj/project.pbxproj +++ b/Secretive.xcodeproj/project.pbxproj @@ -45,6 +45,11 @@ 507CE4F02420A4C50029F750 /* SigningWitness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4EF2420A4C50029F750 /* SigningWitness.swift */; }; 507CE4F42420A8C10029F750 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; }; 507CE4F62420A96F0029F750 /* SigningRequestTracer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */; }; + 507EE34224281E12003C4FE3 /* FileHandleProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */; }; + 507EE34624281F89003C4FE3 /* StubFileHandleReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */; }; + 507EE34824281FB8003C4FE3 /* StubFileHandleWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */; }; + 507EE34A2428263B003C4FE3 /* 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 */; }; @@ -56,7 +61,7 @@ 5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02A23FE352C0062B6F2 /* SmartCardSecret.swift */; }; 5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */; }; 5099A075240242BA0062B6F2 /* SecretAgentKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5099A06C240242BA0062B6F2 /* SecretAgentKit.framework */; }; - 5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */; }; + 5099A07C240242BA0062B6F2 /* AgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A07B240242BA0062B6F2 /* AgentTests.swift */; }; 5099A07E240242BA0062B6F2 /* SecretAgentKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 5099A06E240242BA0062B6F2 /* SecretAgentKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */; }; 50A3B79124026B7600D209EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50A3B79024026B7600D209EA /* Assets.xcassets */; }; @@ -238,6 +243,11 @@ 507CE4EF2420A4C50029F750 /* SigningWitness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningWitness.swift; sourceTree = ""; }; 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningRequestProvenance.swift; sourceTree = ""; }; 507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SigningRequestTracer.swift; sourceTree = ""; }; + 507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHandleProtocols.swift; sourceTree = ""; }; + 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 = ""; }; @@ -252,7 +262,7 @@ 5099A06E240242BA0062B6F2 /* SecretAgentKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecretAgentKit.h; sourceTree = ""; }; 5099A06F240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5099A074240242BA0062B6F2 /* SecretAgentKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SecretAgentKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretAgentKitTests.swift; sourceTree = ""; }; + 5099A07B240242BA0062B6F2 /* AgentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgentTests.swift; sourceTree = ""; }; 5099A07D240242BA0062B6F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5099A089240242C20062B6F2 /* SSHAgentProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHAgentProtocol.swift; sourceTree = ""; }; 50A3B78A24026B7500D209EA /* SecretAgent.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SecretAgent.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -529,6 +539,7 @@ 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */, 507CE4F52420A96F0029F750 /* SigningRequestTracer.swift */, 50A3B79F24026B9900D209EA /* Agent.swift */, + 507EE34124281E12003C4FE3 /* FileHandleProtocols.swift */, 5099A06F240242BA0062B6F2 /* Info.plist */, ); path = SecretAgentKit; @@ -537,7 +548,11 @@ 5099A07A240242BA0062B6F2 /* SecretAgentKitTests */ = { isa = PBXGroup; children = ( - 5099A07B240242BA0062B6F2 /* SecretAgentKitTests.swift */, + 5099A07B240242BA0062B6F2 /* AgentTests.swift */, + 507EE34524281F89003C4FE3 /* StubFileHandleReader.swift */, + 507EE34724281FB8003C4FE3 /* StubFileHandleWriter.swift */, + 507EE34D2428784F003C4FE3 /* StubWitness.swift */, + 507EE3492428263B003C4FE3 /* StubStore.swift */, 5099A07D240242BA0062B6F2 /* Info.plist */, ); path = SecretAgentKitTests; @@ -957,6 +972,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 507EE34224281E12003C4FE3 /* FileHandleProtocols.swift in Sources */, 507CE4EE2420A3CA0029F750 /* SocketController.swift in Sources */, 5099A08A240242C20062B6F2 /* SSHAgentProtocol.swift in Sources */, 507CE4ED2420A3C70029F750 /* Agent.swift in Sources */, @@ -970,7 +986,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5099A07C240242BA0062B6F2 /* SecretAgentKitTests.swift in Sources */, + 507EE34E2428784F003C4FE3 /* StubWitness.swift in Sources */, + 507EE34624281F89003C4FE3 /* StubFileHandleReader.swift in Sources */, + 507EE34A2428263B003C4FE3 /* StubStore.swift in Sources */, + 5099A07C240242BA0062B6F2 /* AgentTests.swift in Sources */, + 507EE34824281FB8003C4FE3 /* StubFileHandleWriter.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1627,8 +1647,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Z72PRUAWF6; INFOPLIST_FILE = SecretKitTests/Info.plist; @@ -1639,6 +1659,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Test; @@ -1677,7 +1698,8 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Z72PRUAWF6; INFOPLIST_FILE = SecretAgentKitTests/Info.plist; @@ -1688,6 +1710,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = com.maxgoedjen.SecretAgentKitTests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; name = Test; @@ -1755,6 +1778,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Z72PRUAWF6; INFOPLIST_FILE = SecretAgentKitTests/Info.plist; @@ -1773,6 +1797,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Z72PRUAWF6; INFOPLIST_FILE = SecretAgentKitTests/Info.plist;