mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-03-05 17:27:24 +01:00
Cleanup of agent (#58)
* Extract key selection. * Moving agent and socket stuff to SecretAgentKit * Cleanup of agent
This commit is contained in:
@@ -1,150 +0,0 @@
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import OSLog
|
||||
import SecretKit
|
||||
import SecretAgentKit
|
||||
|
||||
class Agent {
|
||||
|
||||
fileprivate let storeList: SecretStoreList
|
||||
fileprivate let notifier: Notifier
|
||||
|
||||
public init(storeList: SecretStoreList, notifier: Notifier) {
|
||||
os_log(.debug, "Agent is running")
|
||||
self.storeList = storeList
|
||||
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 {
|
||||
// TODO: RESTORE ONCE XCODE 11.4 IS GM
|
||||
let secrets = storeList.stores.flatMap { $0.secrets }
|
||||
// let secrets = storeList.stores.flatMap(\.secrets)
|
||||
var count = UInt32(secrets.count).bigEndian
|
||||
let countData = Data(bytes: &count, count: UInt32.bitWidth/8)
|
||||
var keyData = Data()
|
||||
let writer = OpenSSHKeyWriter()
|
||||
for secret in secrets {
|
||||
let keyBlob = writer.data(secret: secret)
|
||||
keyData.append(writer.lengthAndData(of: keyBlob))
|
||||
let curveData = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
|
||||
keyData.append(writer.lengthAndData(of: curveData))
|
||||
}
|
||||
os_log(.debug, "Agent enumerated %@ identities", secrets.count as NSNumber)
|
||||
return countData + keyData
|
||||
}
|
||||
|
||||
func sign(data: Data) throws -> Data {
|
||||
let reader = OpenSSHReader(data: data)
|
||||
let writer = OpenSSHKeyWriter()
|
||||
let hash = try reader.readNextChunk()
|
||||
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
|
||||
}
|
||||
guard let (store, secret) = matching.first else {
|
||||
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 = writer.curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!
|
||||
|
||||
// Convert from DER formatted rep to raw (r||s)
|
||||
|
||||
let rawRepresentation: Data
|
||||
switch (secret.algorithm, secret.keySize) {
|
||||
case (.ellipticCurve, 256):
|
||||
rawRepresentation = try CryptoKit.P256.Signing.ECDSASignature(derRepresentation: derSignature).rawRepresentation
|
||||
case (.ellipticCurve, 384):
|
||||
rawRepresentation = try CryptoKit.P384.Signing.ECDSASignature(derRepresentation: derSignature).rawRepresentation
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
|
||||
let rawLength = rawRepresentation.count/2
|
||||
let r = rawRepresentation[0..<rawLength]
|
||||
let s = 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import Cocoa
|
||||
import SecretKit
|
||||
import SecretAgentKit
|
||||
import OSLog
|
||||
|
||||
@NSApplicationMain
|
||||
@@ -13,7 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}()
|
||||
let notifier = Notifier()
|
||||
lazy var agent: Agent = {
|
||||
Agent(storeList: storeList, notifier: notifier)
|
||||
Agent(storeList: storeList/*, notifier: notifier*/)
|
||||
}()
|
||||
lazy var socketController: SocketController = {
|
||||
let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import SecretKit
|
||||
import SecretAgentKit
|
||||
import UserNotifications
|
||||
|
||||
class Notifier {
|
||||
@@ -10,7 +11,7 @@ class Notifier {
|
||||
}
|
||||
}
|
||||
|
||||
func notify<SecretType: Secret>(accessTo secret: SecretType) {
|
||||
func notify(accessTo secret: AnySecret) {
|
||||
let notificationCenter = UNUserNotificationCenter.current()
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
notificationContent.title = "Signed Request"
|
||||
@@ -20,3 +21,11 @@ class Notifier {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Notifier: SigningWitness {
|
||||
|
||||
func witness(accessTo secret: AnySecret) throws {
|
||||
notify(accessTo: secret)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
|
||||
class SocketController {
|
||||
|
||||
fileprivate var fileHandle: FileHandle?
|
||||
fileprivate var port: SocketPort?
|
||||
var handler: ((FileHandle) -> Void)?
|
||||
|
||||
init(path: String) {
|
||||
os_log(.debug, "Socket controller setting up at %@", path)
|
||||
if let _ = try? FileManager.default.removeItem(atPath: path) {
|
||||
os_log(.debug, "Socket controller removed existing socket")
|
||||
}
|
||||
let exists = FileManager.default.fileExists(atPath: path)
|
||||
assert(!exists)
|
||||
os_log(.debug, "Socket controller path is clear")
|
||||
port = socketPort(at: path)
|
||||
configureSocket(at: path)
|
||||
os_log(.debug, "Socket listening at %@", path)
|
||||
}
|
||||
|
||||
func configureSocket(at path: String) {
|
||||
guard let port = port else { return }
|
||||
fileHandle = FileHandle(fileDescriptor: port.socket, closeOnDealloc: true)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleConnectionAccept(notification:)), name: .NSFileHandleConnectionAccepted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleConnectionDataAvailable(notification:)), name: .NSFileHandleDataAvailable, object: nil)
|
||||
fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!])
|
||||
}
|
||||
|
||||
func socketPort(at path: String) -> SocketPort {
|
||||
var addr = sockaddr_un()
|
||||
addr.sun_family = sa_family_t(AF_UNIX)
|
||||
|
||||
var len: Int = 0
|
||||
_ = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
||||
path.withCString { cstring in
|
||||
len = strlen(cstring)
|
||||
strncpy(pointer, cstring, len)
|
||||
}
|
||||
}
|
||||
addr.sun_len = UInt8(len+2)
|
||||
|
||||
var data: Data!
|
||||
_ = withUnsafePointer(to: &addr) { pointer in
|
||||
data = Data(bytes: pointer, count: MemoryLayout<sockaddr_un>.size)
|
||||
}
|
||||
|
||||
return SocketPort(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)!
|
||||
}
|
||||
|
||||
@objc func handleConnectionAccept(notification: Notification) {
|
||||
os_log(.debug, "Socket controller accepted connection")
|
||||
guard let new = notification.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else { return }
|
||||
handler?(new)
|
||||
new.waitForDataInBackgroundAndNotify()
|
||||
fileHandle?.acceptConnectionInBackgroundAndNotify(forModes: [RunLoop.current.currentMode!])
|
||||
}
|
||||
|
||||
@objc func handleConnectionDataAvailable(notification: Notification) {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user