This commit is contained in:
Max Goedjen
2026-06-11 12:40:57 -07:00
parent 3ad23b0506
commit a66db7fe2e
12 changed files with 66 additions and 142 deletions

View File

@@ -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",

View File

@@ -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 {}
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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<SignatureRequest>) 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

View File

@@ -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)
}
}
}
}
}

View File

@@ -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))
}
}

View File

@@ -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()
}
}
}

View File

@@ -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() {
}

View File

@@ -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 {

View File

@@ -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: