secretive/SecretAgent/Agent.swift

138 lines
4.6 KiB
Swift
Raw Normal View History

2020-03-04 07:14:38 +00:00
import Foundation
import CryptoKit
import OSLog
import SecretKit
import SecretAgentKit
2020-03-09 04:11:59 +00:00
class Agent {
2020-03-04 07:14:38 +00:00
2020-03-09 04:11:59 +00:00
fileprivate let storeList: SecretStoreList
2020-03-04 07:14:38 +00:00
fileprivate let notifier: Notifier
2020-03-09 04:11:59 +00:00
public init(storeList: SecretStoreList, notifier: Notifier) {
2020-03-04 07:14:38 +00:00
os_log(.debug, "Agent is running")
2020-03-09 04:11:59 +00:00
self.storeList = storeList
2020-03-04 07:14:38 +00:00
self.notifier = notifier
}
}
extension Agent {
func handle(fileHandle: FileHandle) {
os_log(.debug, "Agent handling new data")
let data = fileHandle.availableData
guard !data.isEmpty else { return }
let requestTypeInt = data[4]
guard let requestType = SSHAgent.RequestType(rawValue: requestTypeInt) else { return }
os_log(.debug, "Agent handling request of type %@", requestType.debugDescription)
let subData = Data(data[5...])
handle(requestType: requestType, data: subData, fileHandle: fileHandle)
}
func handle(requestType: SSHAgent.RequestType, data: Data, fileHandle: FileHandle) {
var response = Data()
do {
switch requestType {
case .requestIdentities:
response.append(SSHAgent.ResponseType.agentIdentitiesAnswer.data)
response.append(try identities())
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentIdentitiesAnswer.debugDescription)
case .signRequest:
response.append(SSHAgent.ResponseType.agentSignResponse.data)
response.append(try sign(data: data))
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentSignResponse.debugDescription)
}
} catch {
response.removeAll()
response.append(SSHAgent.ResponseType.agentFailure.data)
os_log(.debug, "Agent returned %@", SSHAgent.ResponseType.agentFailure.debugDescription)
}
let full = OpenSSHKeyWriter().lengthAndData(of: response)
fileHandle.write(full)
}
}
extension Agent {
func identities() throws -> Data {
2020-03-09 04:11:59 +00:00
let secrets = storeList.stores.flatMap(\.secrets)
var count = UInt32(secrets.count).bigEndian
2020-03-04 07:14:38 +00:00
let countData = Data(bytes: &count, count: UInt32.bitWidth/8)
var keyData = Data()
let writer = OpenSSHKeyWriter()
2020-03-09 04:11:59 +00:00
for secret in secrets {
2020-03-04 07:14:38 +00:00
let keyBlob = writer.data(secret: secret)
keyData.append(writer.lengthAndData(of: keyBlob))
let curveData = OpenSSHKeyWriter.Constants.curveType.data(using: .utf8)!
keyData.append(writer.lengthAndData(of: curveData))
}
2020-03-09 04:11:59 +00:00
os_log(.debug, "Agent enumerated %@ identities", secrets.count as NSNumber)
2020-03-04 07:14:38 +00:00
return countData + keyData
}
func sign(data: Data) throws -> Data {
let reader = OpenSSHReader(data: data)
let writer = OpenSSHKeyWriter()
let hash = try reader.readNextChunk()
2020-03-09 04:11:59 +00:00
let matching = storeList.stores.compactMap { store -> (AnySecretStore, AnySecret)? in
let allMatching = store.secrets.filter { secret in
hash == writer.data(secret: secret)
}
if let matching = allMatching.first {
return (store, matching)
}
return nil
2020-03-04 07:14:38 +00:00
}
2020-03-09 04:11:59 +00:00
guard let (store, secret) = matching.first else {
2020-03-04 07:14:38 +00:00
throw AgentError.noMatchingKey
}
let dataToSign = try reader.readNextChunk()
let derSignature = try store.sign(data: dataToSign, with: secret)
// TODO: Move this
notifier.notify(accessTo: secret)
let curveData = OpenSSHKeyWriter.Constants.curveType.data(using: .utf8)!
// Convert from DER formatted rep to raw (r||s)
let signature = try CryptoKit.P256.Signing.ECDSASignature(derRepresentation: derSignature)
let rawLength = signature.rawRepresentation.count/2
let r = signature.rawRepresentation[0..<rawLength]
let s = signature.rawRepresentation[rawLength...]
var signatureChunk = Data()
signatureChunk.append(writer.lengthAndData(of: r))
signatureChunk.append(writer.lengthAndData(of: s))
var signedData = Data()
var sub = Data()
sub.append(writer.lengthAndData(of: curveData))
sub.append(writer.lengthAndData(of: signatureChunk))
signedData.append(writer.lengthAndData(of: sub))
os_log(.debug, "Agent signed request")
return signedData
}
}
extension Agent {
enum AgentError: Error {
case unhandledType
case noMatchingKey
}
}
extension SSHAgent.ResponseType {
var data: Data {
var raw = self.rawValue
return Data(bytes: &raw, count: UInt8.bitWidth/8)
}
}