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/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/Sources/SecretAgentKit/OpenSSHReader.swift b/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift index f0b4965..d4d5103 100644 --- a/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift +++ b/Sources/Packages/Sources/SecretAgentKit/OpenSSHReader.swift @@ -32,9 +32,12 @@ final class OpenSSHReader { return lengthChunk.bytes.unsafeLoad(as: T.self) } + func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String { + try String(decoding: readNextChunk(convertEndianness: convertEndianness), as: UTF8.self) + } - func readNextChunkAsString() throws(OpenSSHReaderError) -> String { - try String(decoding: readNextChunk(), 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 c542559..bbef669 100644 --- a/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift +++ b/Sources/Packages/Tests/SecretAgentKitTests/AgentTests.swift @@ -8,19 +8,22 @@ 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 actual = OpenSSHReader(data: response) + let expected = OpenSSHReader(data: Constants.Responses.requestIdentitiesMultiple) + print(actual, expected) #expect(response == Constants.Responses.requestIdentitiesMultiple) } @@ -29,40 +32,42 @@ 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 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 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 + 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 +77,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 +90,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 +106,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 +119,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 +128,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 +154,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 } 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 @@ + + + + + + + + + + + + + + + + + + +