diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index d66c69a..7769e67 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -365,6 +365,16 @@ }, "shouldTranslate" : false }, + "%@ - %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@ - %2$@" + } + } + } + }, "about_build_log_button" : { "extractionState" : "manual", "localizations" : { @@ -19967,6 +19977,12 @@ } } } + }, + "Review" : { + + }, + "Review All" : { + }, "secret_detail_certificate_path_label" : { "extractionState" : "manual", diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index 12be1ee..a53b853 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -35,13 +35,15 @@ import XPCWrappers self.osVersion = osVersion self.currentVersion = currentVersion Task { - if checkOnLaunch { - try await checkForUpdates() - } - while !Task.isCancelled { - try? await Task.sleep(for: .seconds(Int(checkFrequency))) - try await checkForUpdates() - } + do { + if checkOnLaunch { + try await checkForUpdates() + } + while !Task.isCancelled { + try? await Task.sleep(for: .seconds(Int(checkFrequency))) + try await checkForUpdates() + } + } catch {} } } diff --git a/Sources/Packages/Sources/SecretAgentKit/Agent.swift b/Sources/Packages/Sources/SecretAgentKit/Agent.swift index 42867b9..ca4bc7f 100644 --- a/Sources/Packages/Sources/SecretAgentKit/Agent.swift +++ b/Sources/Packages/Sources/SecretAgentKit/Agent.swift @@ -10,11 +10,8 @@ import SSHProtocolKit public final class Agent: Sendable { private let storeList: SecretStoreList -<<<<<<< HEAD private let authenticationHandler: AuthenticationHandler -======= private let certificateStore: CertificateStore ->>>>>>> main private let witness: SigningWitness? private let publicKeyWriter = OpenSSHPublicKeyWriter() private let signatureWriter = OpenSSHSignatureWriter() @@ -24,17 +21,16 @@ public final class Agent: Sendable { /// - Parameters: /// - storeList: The `SecretStoreList` to make available. /// - witness: A witness to notify of requests. -<<<<<<< HEAD - public init(storeList: SecretStoreList, authenticationHandler: AuthenticationHandler, witness: SigningWitness? = nil) { - logger.debug("Agent is running") - self.storeList = storeList - self.authenticationHandler = authenticationHandler -======= - public init(storeList: SecretStoreList, certificateStore: CertificateStore, witness: SigningWitness? = nil) { + public init( + storeList: SecretStoreList, + certificateStore: CertificateStore, + authenticationHandler: AuthenticationHandler, + witness: SigningWitness? = nil + ) { logger.debug("Agent is running") self.storeList = storeList self.certificateStore = certificateStore ->>>>>>> main + self.authenticationHandler = authenticationHandler self.witness = witness } diff --git a/Sources/Packages/Sources/SecretAgentKit/AuthenticationHandler.swift b/Sources/Packages/Sources/SecretAgentKit/AuthenticationHandler.swift index 61daaf0..7c4d0c9 100644 --- a/Sources/Packages/Sources/SecretAgentKit/AuthenticationHandler.swift +++ b/Sources/Packages/Sources/SecretAgentKit/AuthenticationHandler.swift @@ -75,6 +75,7 @@ public actor AuthenticationHandler { } public func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol { + logger.log("Entering waitForAuthentication for \(request.id)") if let existing = existingAuthenticationContext(for: request) { logger.log("Short circuiting wait, existing valid context already exists.") return existing diff --git a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift index 613f6db..9f1fa08 100644 --- a/Sources/Packages/Sources/SecretAgentKit/SocketController.swift +++ b/Sources/Packages/Sources/SecretAgentKit/SocketController.swift @@ -94,7 +94,7 @@ extension SocketController { guard !data.isEmpty else { logger.debug("Socket controller received empty data, ending continuation.") messagesContinuation.finish() - try fileHandle.close() + try? fileHandle.close() return } messagesContinuation.yield(data) @@ -126,8 +126,8 @@ private extension SocketPort { convenience init(path: String) { var addr = sockaddr_un() - let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in - unsafe path.withCString { cstring in + let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in + path.withCString { cstring in let len = unsafe strlen(cstring) unsafe strncpy(pointer, cstring, len) return len diff --git a/Sources/SecretAgent/App.swift b/Sources/SecretAgent/App.swift index e46ed16..2b7e9c5 100644 --- a/Sources/SecretAgent/App.swift +++ b/Sources/SecretAgent/App.swift @@ -8,6 +8,7 @@ import Brief import Observation import Common import SwiftUI +import CertificateKit @main struct SecretAgent: App { @@ -21,10 +22,12 @@ struct SecretAgent: App { list.add(store: SmartCard.Store()) return list }() + @MainActor private let certificateStore: CertificateStore = CertificateStore() + private let updater = Updater(checkOnLaunch: true) private let notifier = Notifier() private let authenticationHandler = AuthenticationHandler() - private let publicKeyFileStoreController = PublicKeyFileStoreController(directory: URL.publicKeyDirectory) + private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory) @State var pending: ([[SignatureRequest]], (Set) async throws -> Void)? @Environment(\.openWindow) var openWindow @@ -42,18 +45,23 @@ struct SecretAgent: App { } .task { let socketController = SocketController(path: URL.socketPath) - let agent = Agent(storeList: storeList, authenticationHandler: authenticationHandler, witness: notifier) + let agent = Agent( + storeList: storeList, + certificateStore: certificateStore, + authenticationHandler: authenticationHandler, + witness: notifier + ) for await session in socketController.sessions { Task { - let inputParser = try await XPCAgentInputParser() do { + let inputParser = try await XPCAgentInputParser() for await message in session.messages { let request = try await inputParser.parse(data: message) let agentResponse = await agent.handle(request: request, provenance: session.provenance) try session.write(agentResponse) } } catch { - try session.close() + try? session.close() } } } @@ -78,10 +86,19 @@ struct SecretAgent: App { // } // } .task { + try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) { try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) } } + .task { + let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: certificateStore) + try? certsMigrator.migrate() + try? publicKeyFileStoreController.generateCertificates(for: certificateStore.certificates, clear: true) + for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) { + try? publicKeyFileStoreController.generateCertificates(for: certificateStore.certificates, clear: true) + } + } .task { await authenticationHandler.setBatchAuthHandler { @MainActor pending, authorize in self.pending = (pending, authorize) @@ -90,7 +107,6 @@ struct SecretAgent: App { } .task { - try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) notifier.prompt() _ = withObservationTracking { updater.update diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift deleted file mode 100644 index a3a7507..0000000 --- a/Sources/SecretAgent/AppDelegate.swift +++ /dev/null @@ -1,94 +0,0 @@ -import Cocoa -import OSLog -import SecretKit -import SecureEnclaveSecretKit -import SmartCardSecretKit -import SecretAgentKit -import Brief -import Observation -import SSHProtocolKit -import CertificateKit -import Common -import SwiftUI - -extension EnvironmentValues { - - @MainActor fileprivate static let _certificateStore: CertificateStore = CertificateStore() - - @MainActor var certificateStore: CertificateStore { - EnvironmentValues._certificateStore - } - - -} -@main -class AppDelegate: NSObject, NSApplicationDelegate { - - @MainActor private let storeList: SecretStoreList = { - let list = SecretStoreList() - let cryptoKit = SecureEnclave.Store() - let migrator = SecureEnclave.CryptoKitMigrator() - try? migrator.migrate(to: cryptoKit) - list.add(store: cryptoKit) - list.add(store: SmartCard.Store()) - let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: EnvironmentValues._certificateStore) - try? certsMigrator.migrate() - return list - }() - private let updater = Updater(checkOnLaunch: true) - private let notifier = Notifier() - private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory) - @MainActor private lazy var agent: Agent = { - Agent(storeList: storeList, certificateStore: EnvironmentValues._certificateStore, witness: notifier) - }() - private lazy var socketController: SocketController = { - let path = URL.socketPath as String - return SocketController(path: path) - }() - private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "AppDelegate") - - func applicationDidFinishLaunching(_ aNotification: Notification) { - logger.debug("SecretAgent finished launching") - Task { - for await session in socketController.sessions { - Task { - let inputParser = try await XPCAgentInputParser() - do { - for await message in session.messages { - let request = try await inputParser.parse(data: message) - let agentResponse = await agent.handle(request: request, provenance: session.provenance) - try session.write(agentResponse) - } - } catch { - try session.close() - } - } - } - } - Task { - for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) { - try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) - } - } - Task { - for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) { - try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true) - } - } - try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) - try? publicKeyFileStoreController.generateCertificates(for: EnvironmentValues._certificateStore.certificates, clear: true) - notifier.prompt() - _ = withObservationTracking { - updater.update - } onChange: { [updater, notifier] in - Task { - guard !updater.currentVersion.isTestBuild else { return } - await notifier.notify(update: updater.update!) { release in - await updater.ignore(release: release) - } - } - } - } - -} - diff --git a/Sources/SecretAgent/BatchedRequestsView.swift b/Sources/SecretAgent/BatchedRequestsView.swift index 6508654..3effe21 100644 --- a/Sources/SecretAgent/BatchedRequestsView.swift +++ b/Sources/SecretAgent/BatchedRequestsView.swift @@ -31,7 +31,7 @@ struct BatchedRequestsView: View { Spacer() Button("Review") { Task { - try await review([pending.element]) + try? await review([pending.element]) } } } @@ -42,7 +42,7 @@ struct BatchedRequestsView: View { Spacer() Button("Review All") { Task { - try await review(Set(group.element)) + try? await review(Set(group.element)) } } diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index fa405d9..202b380 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -25,7 +25,7 @@ struct Secretive: App { guard !agentLaunchController.developmentBuild else { return } if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running { // Relaunch the agent, since it'll be running from earlier update still - try await agentLaunchController.forceLaunch() + try? await agentLaunchController.forceLaunch() } } } diff --git a/Sources/Secretive/Preview Content/PreviewStore.swift b/Sources/Secretive/Preview Content/PreviewStore.swift index 84e83e8..c51a1b7 100644 --- a/Sources/Secretive/Preview Content/PreviewStore.swift +++ b/Sources/Secretive/Preview Content/PreviewStore.swift @@ -1,5 +1,6 @@ import Foundation import SecretKit +import LocalAuthentication enum Preview {} @@ -38,17 +39,10 @@ extension Preview { self.init(secrets: new) } - func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data { + func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data { return data } - func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? { - nil - } - - func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws { - } - func reloadSecrets() { } @@ -82,16 +76,10 @@ extension Preview { self.init(secrets: new) } - func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: AuthenticationContextProtocol?) throws -> Data { + func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, context: LAContext?) async throws -> Data { return data } - func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? { - nil - } - - func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws { - } func reloadSecrets() { } diff --git a/Sources/Secretive/Views/Views/AgentStatusView.swift b/Sources/Secretive/Views/Views/AgentStatusView.swift index 43c69a1..2313b01 100644 --- a/Sources/Secretive/Views/Views/AgentStatusView.swift +++ b/Sources/Secretive/Views/Views/AgentStatusView.swift @@ -101,7 +101,7 @@ struct AgentNotRunningView: View { guard !loading else { return } loading = true Task { - try await agentLaunchController.forceLaunch() + try? await agentLaunchController.forceLaunch() loading = false if !agentLaunchController.running { diff --git a/Sources/Secretive/Views/Views/CopyableView.swift b/Sources/Secretive/Views/Views/CopyableView.swift index 8765972..83b53e7 100644 --- a/Sources/Secretive/Views/Views/CopyableView.swift +++ b/Sources/Secretive/Views/Views/CopyableView.swift @@ -163,10 +163,9 @@ fileprivate struct BackgroundViewModifier: ViewModifier { } else { if #available(macOS 26.0, *) { content - // Very thin opacity lets user hover anywhere over the view, glassEffect doesn't allow. - .background(.white.opacity(0.01), in: RoundedRectangle(cornerRadius: 15)) .glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15)) .mask(RoundedRectangle(cornerRadius: 15)) + .contentShape(RoundedRectangle(cornerRadius: 15)) .shadow(color: .black.opacity(0.1), radius: 5) } else { content @@ -182,7 +181,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier { let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1) switch interactionState { case .normal: - return base + return base.mix(with: .accentColor, by: 0) case .hovering: return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1) case .clicking, .dragging: