From d73c1c666e17f35ee8399396dcb2678292829208 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Sep 2025 20:59:47 -0700 Subject: [PATCH 1/3] . --- Sources/Packages/Sources/Brief/Release.swift | 4 - .../Sources/SecretAgentKit/Agent.swift | 2 +- .../SecretAgentKitTests/AgentTests.swift | 90 ++++++++++--------- .../OpenSSHReaderTests.swift | 2 +- .../Tests/SecretAgentKitTests/StubStore.swift | 2 +- 5 files changed, 50 insertions(+), 50 deletions(-) rename Sources/Packages/Tests/{SecretKitTests => SecretAgentKitTests}/OpenSSHReaderTests.swift (98%) diff --git a/Sources/Packages/Sources/Brief/Release.swift b/Sources/Packages/Sources/Brief/Release.swift index 4e02a86..ffc3293 100644 --- a/Sources/Packages/Sources/Brief/Release.swift +++ b/Sources/Packages/Sources/Brief/Release.swift @@ -1,9 +1,5 @@ import Foundation -@objc public protocol ReleaseProtocol: Sendable { - -} - /// A release is a representation of a downloadable update. public struct Release: Codable, Sendable { diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index cd156c6..7fe268d 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -150,7 +150,7 @@ extension SSHAgent.Response { var data: Data { var raw = self.rawValue - return Data(bytes: &raw, count: MemoryLayout.size) + return Data(bytes: &raw, count: MemoryLayout.size) } } diff --git a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift index c542559..3ccc2f1 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift @@ -8,19 +8,27 @@ 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 actualHex = response.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined() + let expectedHex = Constants.Responses.requestIdentitiesMultiple.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined() + print(actualHex) + print(expectedHex) + + let actual = OpenSSHReader(data: response) + let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple) + print(actual, expected) #expect(response == Constants.Responses.requestIdentitiesMultiple) } @@ -29,40 +37,34 @@ 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(.disabled()) 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 signedData = await agent.handle(request: request, provenance: .test) + let rsData = OpenSSHReader(data: signedData) + 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 +74,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 +87,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 +103,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 +116,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 +125,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 +151,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 } From c0370669a9d09d19e4065b157982e71165fdb518 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Sep 2025 22:50:17 -0700 Subject: [PATCH 2/3] Fix ECDSA test. --- .../SecretAgentKit/OpenSSHReader.swift | 8 ++++++-- .../SecretAgentKitTests/AgentTests.swift | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift b/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift index 0ca5042..b6a367d 100644 --- a/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift +++ b/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift @@ -33,8 +33,12 @@ final class OpenSSHReader { } - func readNextChunkAsString() throws -> 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) + } + + func readNextChunkAsSubReader(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> OpenSSHReader { + OpenSSHReader(data: try readNextChunk(convertEndianness: convertEndianness)) } } diff --git a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift index 3ccc2f1..bbef669 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift @@ -21,11 +21,6 @@ import CryptoKit let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities) let response = await agent.handle(request: request, provenance: .test) - let actualHex = response.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined() - let expectedHex = Constants.Responses.requestIdentitiesMultiple.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) }.joined() - print(actualHex) - print(expectedHex) - let actual = OpenSSHReader(data: response) let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple) print(actual, expected) @@ -42,13 +37,21 @@ import CryptoKit #expect(response == Constants.Responses.requestFailure) } - @Test(.disabled()) func ecdsaSignature() async throws { + @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 signedData = await agent.handle(request: request, provenance: .test) - let rsData = OpenSSHReader(data: signedData) + 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 From 2506f00c2ebc93ca28a5d17095e34c5e79212feb Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Sep 2025 23:07:04 -0700 Subject: [PATCH 3/3] Scripts --- .github/workflows/release.yml | 2 + .github/workflows/test.yml | 4 +- Sources/Config/Secretive.xctestplan | 22 +++++++-- .../xcschemes/PackageTests.xcscheme | 46 +++++++++++++++++++ 4 files changed, 68 insertions(+), 6 deletions(-) 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/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/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 @@ + + + + + + + + + + + + + + + + + + +