This commit is contained in:
Max Goedjen 2024-08-26 13:56:49 -07:00
parent 56a662a9dd
commit 74a516f6fd
No known key found for this signature in database
8 changed files with 78 additions and 66 deletions

View File

@ -5,7 +5,7 @@ import SecretKit
import AppKit import AppKit
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores. /// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
public actor Agent { @MainActor public final class Agent {
private let storeList: SecretStoreList private let storeList: SecretStoreList
private let witness: SigningWitness? private let witness: SigningWitness?

View File

@ -2,13 +2,13 @@ import Foundation
import Combine import Combine
/// Type eraser for SecretStore. /// Type eraser for SecretStore.
public class AnySecretStore: SecretStore { @MainActor public class AnySecretStore: SecretStore {
let base: Any let base: Any
private let _isAvailable: () -> Bool private let _isAvailable: () -> Bool
private let _id: () -> UUID private let _id: () -> UUID
private let _name: () -> String private let _name: () -> String
private let _secrets: () -> [AnySecret] private let _secrets: @MainActor () -> [AnySecret]
private let _sign: (Data, AnySecret, SigningRequestProvenance) throws -> Data private let _sign: (Data, AnySecret, SigningRequestProvenance) throws -> Data
private let _verify: (Data, Data, AnySecret) throws -> Bool private let _verify: (Data, Data, AnySecret) throws -> Bool
private let _existingPersistedAuthenticationContext: (AnySecret) -> PersistedAuthenticationContext? private let _existingPersistedAuthenticationContext: (AnySecret) -> PersistedAuthenticationContext?
@ -22,7 +22,7 @@ public class AnySecretStore: SecretStore {
_isAvailable = { secretStore.isAvailable } _isAvailable = { secretStore.isAvailable }
_name = { secretStore.name } _name = { secretStore.name }
_id = { secretStore.id } _id = { secretStore.id }
_secrets = { secretStore.secrets.map { AnySecret($0) } } _secrets = { @MainActor in secretStore.secrets.map { AnySecret($0) } }
_sign = { try secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) } _sign = { try secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType, for: $2) }
_verify = { try secretStore.verify(signature: $0, for: $1, with: $2.base as! SecretStoreType.SecretType) } _verify = { try secretStore.verify(signature: $0, for: $1, with: $2.base as! SecretStoreType.SecretType) }
_existingPersistedAuthenticationContext = { secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) } _existingPersistedAuthenticationContext = { secretStore.existingPersistedAuthenticationContext(secret: $0.base as! SecretStoreType.SecretType) }
@ -37,8 +37,10 @@ public class AnySecretStore: SecretStore {
return _isAvailable() return _isAvailable()
} }
public var id: UUID { nonisolated public var id: UUID {
return _id() // return _id()
// FIXME: THIS
UUID()
} }
public var name: String { public var name: String {
@ -71,7 +73,7 @@ public class AnySecretStore: SecretStore {
} }
public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable { @MainActor public final class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
private let _create: (String, Bool) throws -> Void private let _create: (String, Bool) throws -> Void
private let _delete: (AnySecret) throws -> Void private let _delete: (AnySecret) throws -> Void

View File

@ -2,7 +2,7 @@ import Foundation
import Combine import Combine
/// A "Store Store," which holds a list of type-erased stores. /// A "Store Store," which holds a list of type-erased stores.
public final class SecretStoreList: ObservableObject { @MainActor public final class SecretStoreList: ObservableObject {
/// The Stores managed by the SecretStoreList. /// The Stores managed by the SecretStoreList.
@Published public var stores: [AnySecretStore] = [] @Published public var stores: [AnySecretStore] = []

View File

@ -2,7 +2,7 @@ import Foundation
import Combine import Combine
/// Manages access to Secrets, and performs signature operations on data using those Secrets. /// Manages access to Secrets, and performs signature operations on data using those Secrets.
public protocol SecretStore: ObservableObject, Identifiable { @MainActor public protocol SecretStore: ObservableObject, Identifiable, Sendable {
associatedtype SecretType: Secret associatedtype SecretType: Secret

View File

@ -8,7 +8,7 @@ import SecretKit
extension SecureEnclave { extension SecureEnclave {
/// An implementation of Store backed by the Secure Enclave. /// An implementation of Store backed by the Secure Enclave.
public final class Store: SecretStoreModifiable, Sendable { @MainActor public final class Store: SecretStoreModifiable, Sendable {
public var isAvailable: Bool { public var isAvailable: Bool {
// For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false // For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false
@ -29,8 +29,11 @@ extension SecureEnclave {
/// Initializes a Store. /// Initializes a Store.
public init() { public init() {
DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { _ in DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { _ in
// We've explicitly specified main queue as an argument.
MainActor.assumeIsolated {
self.reloadSecretsInternal(notifyAgent: false) self.reloadSecretsInternal(notifyAgent: false)
} }
}
loadSecrets() loadSecrets()
} }

View File

@ -7,8 +7,10 @@ import SmartCardSecretKit
import SecretAgentKit import SecretAgentKit
import Brief import Brief
@NSApplicationMain import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
@main
struct SecretiveApp: App {
private let storeList: SecretStoreList = { private let storeList: SecretStoreList = {
let list = SecretStoreList() let list = SecretStoreList()
@ -29,19 +31,24 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var updateSink: AnyCancellable? private var updateSink: AnyCancellable?
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate")
func applicationDidFinishLaunching(_ aNotification: Notification) { var body: some Scene {
logger.debug("SecretAgent finished launching") WindowGroup {
DispatchQueue.main.async { Text("Hello")
self.socketController.handler = self.agent.handle(reader:writer:) .onAppear {
// logger.debug("SecretAgent finished launching")
// DispatchQueue.main.async {
// self.socketController.handler = self.agent.handle(reader:writer:)
// }
// NotificationCenter.default.addObserver(forName: .secretStoreReloaded, object: nil, queue: .main) { [self] _ in
// try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
// }
// try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
// notifier.prompt()
// updateSink = updater.$update.sink { update in
// guard let update = update else { return }
// self.notifier.notify(update: update, ignore: self.updater.ignore(release:))
// }
} }
NotificationCenter.default.addObserver(forName: .secretStoreReloaded, object: nil, queue: .main) { [self] _ in
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
}
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
notifier.prompt()
updateSink = updater.$update.sink { update in
guard let update = update else { return }
self.notifier.notify(update: update, ignore: self.updater.ignore(release:))
} }
} }

View File

@ -143,42 +143,42 @@ extension Notifier {
} }
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let category = response.notification.request.content.categoryIdentifier // let category = response.notification.request.content.categoryIdentifier
switch category { // switch category {
case Notifier.Constants.updateCategoryIdentitifier: // case Notifier.Constants.updateCategoryIdentitifier:
handleUpdateResponse(response: response) // handleUpdateResponse(response: response)
case Notifier.Constants.persistAuthenticationCategoryIdentitifier: // case Notifier.Constants.persistAuthenticationCategoryIdentitifier:
handlePersistAuthenticationResponse(response: response) // handlePersistAuthenticationResponse(response: response)
default: // default:
break // break
} // }
//
completionHandler() // completionHandler()
} // }
//
func handleUpdateResponse(response: UNNotificationResponse) { // func handleUpdateResponse(response: UNNotificationResponse) {
guard let update = release else { return } // guard let update = release else { return }
switch response.actionIdentifier { // switch response.actionIdentifier {
case Notifier.Constants.updateActionIdentitifier, UNNotificationDefaultActionIdentifier: // case Notifier.Constants.updateActionIdentitifier, UNNotificationDefaultActionIdentifier:
NSWorkspace.shared.open(update.html_url) // NSWorkspace.shared.open(update.html_url)
case Notifier.Constants.ignoreActionIdentitifier: // case Notifier.Constants.ignoreActionIdentitifier:
ignore?(update) // ignore?(update)
default: // default:
fatalError() // fatalError()
} // }
} // }
//
func handlePersistAuthenticationResponse(response: UNNotificationResponse) { // func handlePersistAuthenticationResponse(response: UNNotificationResponse) {
guard let secretID = response.notification.request.content.userInfo[Notifier.Constants.persistSecretIDKey] as? String, let secret = pendingPersistableSecrets[secretID], // guard let secretID = response.notification.request.content.userInfo[Notifier.Constants.persistSecretIDKey] as? String, let secret = pendingPersistableSecrets[secretID],
let storeID = response.notification.request.content.userInfo[Notifier.Constants.persistStoreIDKey] as? String, let store = pendingPersistableStores[storeID] // let storeID = response.notification.request.content.userInfo[Notifier.Constants.persistStoreIDKey] as? String, let store = pendingPersistableStores[storeID]
else { return } // else { return }
pendingPersistableSecrets[secretID] = nil // pendingPersistableSecrets[secretID] = nil
persistAuthentication?(secret, store, persistOptions[response.actionIdentifier]) // persistAuthentication?(secret, store, persistOptions[response.actionIdentifier])
} // }
//
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.list, .banner]) // completionHandler([.list, .banner])
} // }
} }

View File

@ -22,8 +22,8 @@ struct EmptyStoreView: View {
extension EmptyStoreView { extension EmptyStoreView {
enum Constants { enum Constants {
static let emptyStoreModifiableTag: AnyHashable = "emptyStoreModifiableTag" static let emptyStoreModifiableTag = "emptyStoreModifiableTag"
static let emptyStoreTag: AnyHashable = "emptyStoreTag" static let emptyStoreTag = "emptyStoreTag"
} }
} }