diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index 8851127..302fa3d 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -43,7 +43,7 @@ extension Agent { } let requestTypeInt = data[4] guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { - logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)") + logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription) for unknown request type \(requestTypeInt)") return SSHAgent.ResponseType.agentFailure.data.lengthAndData } logger.debug("Agent handling request of type \(requestType.debugDescription)") @@ -66,10 +66,13 @@ extension Agent { response.append(SSHAgent.ResponseType.agentSignResponse.data) response.append(try await sign(data: data, provenance: provenance)) logger.debug("Agent returned \(SSHAgent.ResponseType.agentSignResponse.debugDescription)") + default: + logger.debug("Agent received valid request of type \(requestType.debugDescription), but not currently supported.") + response.append(SSHAgent.ResponseType.agentFailure.data) + } } catch { - response.removeAll() - response.append(SSHAgent.ResponseType.agentFailure.data) + response = SSHAgent.ResponseType.agentFailure.data logger.debug("Agent returned \(SSHAgent.ResponseType.agentFailure.debugDescription)") } return response.lengthAndData @@ -101,7 +104,7 @@ extension Agent { } logger.log("Agent enumerated \(count) identities") var countBigEndian = UInt32(count).bigEndian - let countData = Data(bytes: &countBigEndian, count: UInt32.bitWidth/8) + let countData = Data(bytes: &countBigEndian, count: MemoryLayout.size) return countData + keyData } @@ -112,7 +115,7 @@ extension Agent { /// - Returns: An OpenSSH formatted Data payload containing the signed data response. func sign(data: Data, provenance: SigningRequestProvenance) async throws -> Data { let reader = OpenSSHReader(data: data) - let payloadHash = reader.readNextChunk() + let payloadHash = try reader.readNextChunk() let hash: Data // Check if hash is actually an openssh certificate and reconstruct the public key if it is @@ -129,7 +132,7 @@ extension Agent { try await witness?.speakNowOrForeverHoldYourPeace(forAccessTo: secret, from: store, by: provenance) - let dataToSign = reader.readNextChunk() + let dataToSign = try reader.readNextChunk() let rawRepresentation = try await store.sign(data: dataToSign, with: secret, for: provenance) let signedData = signatureWriter.data(secret: secret, signature: rawRepresentation) diff --git a/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift b/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift index 4c45616..30b4747 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SSHAgentProtocol.swift @@ -10,13 +10,32 @@ extension SSHAgent { case requestIdentities = 11 case signRequest = 13 + case addIdentity = 17 + case removeIdentity = 18 + case removeAllIdentities = 19 + case addIDConstrained = 25 + case addSmartcardKey = 20 + case removeSmartcardKey = 21 + case lock = 22 + case unlock = 23 + case addSmartcardKeyConstrained = 26 + case protocolExtension = 27 + public var debugDescription: String { switch self { - case .requestIdentities: - return "RequestIdentities" - case .signRequest: - return "SignRequest" + case .requestIdentities: "SSH_AGENTC_REQUEST_IDENTITIES" + case .signRequest: "SSH_AGENTC_SIGN_REQUEST" + case .addIdentity: "SSH_AGENTC_ADD_IDENTITY" + case .removeIdentity: "SSH_AGENTC_REMOVE_IDENTITY" + case .removeAllIdentities: "SSH_AGENTC_REMOVE_ALL_IDENTITIES" + case .addIDConstrained: "SSH_AGENTC_ADD_ID_CONSTRAINED" + case .addSmartcardKey: "SSH_AGENTC_ADD_SMARTCARD_KEY" + case .removeSmartcardKey: "SSH_AGENTC_REMOVE_SMARTCARD_KEY" + case .lock: "SSH_AGENTC_LOCK" + case .unlock: "SSH_AGENTC_UNLOCK" + case .addSmartcardKeyConstrained: "SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED" + case .protocolExtension: "SSH_AGENTC_EXTENSION" } } } @@ -28,17 +47,17 @@ extension SSHAgent { case agentSuccess = 6 case agentIdentitiesAnswer = 12 case agentSignResponse = 14 + case agentExtensionFailure = 28 + case agentExtensionResponse = 29 public var debugDescription: String { switch self { - case .agentFailure: - return "AgentFailure" - case .agentSuccess: - return "AgentSuccess" - case .agentIdentitiesAnswer: - return "AgentIdentitiesAnswer" - case .agentSignResponse: - return "AgentSignResponse" + case .agentFailure: "SSH_AGENT_FAILURE" + case .agentSuccess: "SSH_AGENT_SUCCESS" + case .agentIdentitiesAnswer: "SSH_AGENT_IDENTITIES_ANSWER" + case .agentSignResponse: "SSH_AGENT_SIGN_RESPONSE" + case .agentExtensionFailure: "SSH_AGENT_EXTENSION_FAILURE" + case .agentExtensionResponse: "SSH_AGENT_EXTENSION_RESPONSE" } } } diff --git a/Sources/Packages/Sources/SecretKit/OpenSSH/LengthAndData.swift b/Sources/Packages/Sources/SecretKit/OpenSSH/LengthAndData.swift index 33acc06..dd276a7 100644 --- a/Sources/Packages/Sources/SecretKit/OpenSSH/LengthAndData.swift +++ b/Sources/Packages/Sources/SecretKit/OpenSSH/LengthAndData.swift @@ -7,7 +7,7 @@ extension Data { package var lengthAndData: Data { let rawLength = UInt32(count) var endian = rawLength.bigEndian - return Data(bytes: &endian, count: UInt32.bitWidth/8) + self + return Data(bytes: &endian, count: MemoryLayout.size) + self } } diff --git a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift b/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift index ac4ced2..a5af72c 100644 --- a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift +++ b/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHCertificateHandler.swift @@ -30,20 +30,24 @@ public actor OpenSSHCertificateHandler: Sendable { /// - Returns: A ``Data`` object containing the public key in OpenSSH wire format if the ``Data`` is an OpenSSH certificate hash, otherwise nil. public func publicKeyHash(from hash: Data) -> Data? { let reader = OpenSSHReader(data: hash) - let certType = String(decoding: reader.readNextChunk(), as: UTF8.self) - switch certType { - case "ecdsa-sha2-nistp256-cert-v01@openssh.com", - "ecdsa-sha2-nistp384-cert-v01@openssh.com", - "ecdsa-sha2-nistp521-cert-v01@openssh.com": - _ = reader.readNextChunk() // nonce - let curveIdentifier = reader.readNextChunk() - let publicKey = reader.readNextChunk() + do { + let certType = String(decoding: try reader.readNextChunk(), as: UTF8.self) + switch certType { + case "ecdsa-sha2-nistp256-cert-v01@openssh.com", + "ecdsa-sha2-nistp384-cert-v01@openssh.com", + "ecdsa-sha2-nistp521-cert-v01@openssh.com": + _ = try reader.readNextChunk() // nonce + let curveIdentifier = try reader.readNextChunk() + let publicKey = try reader.readNextChunk() - let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "") - return openSSHIdentifier.lengthAndData + - curveIdentifier.lengthAndData + - publicKey.lengthAndData - default: + let openSSHIdentifier = certType.replacingOccurrences(of: "-cert-v01@openssh.com", with: "") + return openSSHIdentifier.lengthAndData + + curveIdentifier.lengthAndData + + publicKey.lengthAndData + default: + return nil + } + } catch { return nil } } diff --git a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHPublicKeyWriter.swift b/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHPublicKeyWriter.swift index 99713e0..30249e0 100644 --- a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHPublicKeyWriter.swift +++ b/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHPublicKeyWriter.swift @@ -97,7 +97,7 @@ extension OpenSSHPublicKeyWriter { extension OpenSSHPublicKeyWriter { - public func rsaPublicKeyBlob(secret: SecretType) -> Data { + func rsaPublicKeyBlob(secret: SecretType) -> Data { // Cheap way to pull out e and n as defined in https://datatracker.ietf.org/doc/html/rfc4253 // Keychain stores it as a thin ASN.1 wrapper with this format: // [4 byte prefix][2 byte prefix][n][2 byte prefix][e] diff --git a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift b/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift index e3ef8aa..22417cb 100644 --- a/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift +++ b/Sources/Packages/Sources/SecretKit/OpenSSH/OpenSSHReader.swift @@ -13,16 +13,30 @@ public final class OpenSSHReader { /// Reads the next chunk of data from the playload. /// - Returns: The next chunk of data. - public func readNextChunk() -> Data { - let lengthRange = 0..<(UInt32.bitWidth/8) - let lengthChunk = remaining[lengthRange] - remaining.removeSubrange(lengthRange) - let littleEndianLength = lengthChunk.bytes.unsafeLoad(as: UInt32.self) - let length = Int(littleEndianLength.bigEndian) + public func readNextChunk(convertEndianness: Bool = true) throws -> Data { + let littleEndianLength = try readNextBytes(as: UInt32.self) + let length = convertEndianness ? Int(littleEndianLength.bigEndian) : Int(littleEndianLength) + guard remaining.count >= length else { throw EndOfData() } let dataRange = 0..(as: T.Type) throws -> T { + let size = MemoryLayout.size + guard remaining.count >= size else { throw EndOfData() } + let lengthRange = 0.. String { + try String(decoding: readNextChunk(), as: UTF8.self) + } + + public struct EndOfData: Error {} + } diff --git a/Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift b/Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift index 39c3029..65d93cf 100644 --- a/Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift +++ b/Sources/Packages/Tests/SecretKitTests/OpenSSHReaderTests.swift @@ -6,13 +6,13 @@ import Testing @Suite struct OpenSSHReaderTests { - @Test func signatureRequest() { + @Test func signatureRequest() throws { let reader = OpenSSHReader(data: Constants.signatureRequest) - let hash = reader.readNextChunk() + let hash = try reader.readNextChunk() #expect(hash == Data(base64Encoded: "AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQ==")) - let dataToSign = reader.readNextChunk() + let dataToSign = try reader.readNextChunk() #expect(dataToSign == Data(base64Encoded: "AAAAICi5xf1ixOestUlxdjvt/BDcM+rzhwy7Vo8cW5YcxA8+MgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRKgm5CWzh8uUtcFQmiaCj5qUtHoDOH6zKpZYJ5UUUP1L55+hFUvKFdN3DwNgc9BHREskSgP7G5p9GgbYIyMBhC8buOVFxFtUxM+k8cktLt77iKkd1qNQq0FkQ55Kxv6QU=")) - let empty = reader.readNextChunk() + let empty = try reader.readNextChunk() #expect(empty.isEmpty) }