mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-06-19 11:28:58 +02:00
WIP
This commit is contained in:
@@ -365,6 +365,16 @@
|
|||||||
},
|
},
|
||||||
"shouldTranslate" : false
|
"shouldTranslate" : false
|
||||||
},
|
},
|
||||||
|
"%@ - %@" : {
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "new",
|
||||||
|
"value" : "%1$@ - %2$@"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"about_build_log_button" : {
|
"about_build_log_button" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -19967,6 +19977,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Review" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Review All" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"secret_detail_certificate_path_label" : {
|
"secret_detail_certificate_path_label" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import XPCWrappers
|
|||||||
self.osVersion = osVersion
|
self.osVersion = osVersion
|
||||||
self.currentVersion = currentVersion
|
self.currentVersion = currentVersion
|
||||||
Task {
|
Task {
|
||||||
|
do {
|
||||||
if checkOnLaunch {
|
if checkOnLaunch {
|
||||||
try await checkForUpdates()
|
try await checkForUpdates()
|
||||||
}
|
}
|
||||||
@@ -42,6 +43,7 @@ import XPCWrappers
|
|||||||
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
try? await Task.sleep(for: .seconds(Int(checkFrequency)))
|
||||||
try await checkForUpdates()
|
try await checkForUpdates()
|
||||||
}
|
}
|
||||||
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,8 @@ import SSHProtocolKit
|
|||||||
public final class Agent: Sendable {
|
public final class Agent: Sendable {
|
||||||
|
|
||||||
private let storeList: SecretStoreList
|
private let storeList: SecretStoreList
|
||||||
<<<<<<< HEAD
|
|
||||||
private let authenticationHandler: AuthenticationHandler
|
private let authenticationHandler: AuthenticationHandler
|
||||||
=======
|
|
||||||
private let certificateStore: CertificateStore
|
private let certificateStore: CertificateStore
|
||||||
>>>>>>> main
|
|
||||||
private let witness: SigningWitness?
|
private let witness: SigningWitness?
|
||||||
private let publicKeyWriter = OpenSSHPublicKeyWriter()
|
private let publicKeyWriter = OpenSSHPublicKeyWriter()
|
||||||
private let signatureWriter = OpenSSHSignatureWriter()
|
private let signatureWriter = OpenSSHSignatureWriter()
|
||||||
@@ -24,17 +21,16 @@ public final class Agent: Sendable {
|
|||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - storeList: The `SecretStoreList` to make available.
|
/// - storeList: The `SecretStoreList` to make available.
|
||||||
/// - witness: A witness to notify of requests.
|
/// - witness: A witness to notify of requests.
|
||||||
<<<<<<< HEAD
|
public init(
|
||||||
public init(storeList: SecretStoreList, authenticationHandler: AuthenticationHandler, witness: SigningWitness? = nil) {
|
storeList: SecretStoreList,
|
||||||
logger.debug("Agent is running")
|
certificateStore: CertificateStore,
|
||||||
self.storeList = storeList
|
authenticationHandler: AuthenticationHandler,
|
||||||
self.authenticationHandler = authenticationHandler
|
witness: SigningWitness? = nil
|
||||||
=======
|
) {
|
||||||
public init(storeList: SecretStoreList, certificateStore: CertificateStore, witness: SigningWitness? = nil) {
|
|
||||||
logger.debug("Agent is running")
|
logger.debug("Agent is running")
|
||||||
self.storeList = storeList
|
self.storeList = storeList
|
||||||
self.certificateStore = certificateStore
|
self.certificateStore = certificateStore
|
||||||
>>>>>>> main
|
self.authenticationHandler = authenticationHandler
|
||||||
self.witness = witness
|
self.witness = witness
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public actor AuthenticationHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol {
|
public func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol {
|
||||||
|
logger.log("Entering waitForAuthentication for \(request.id)")
|
||||||
if let existing = existingAuthenticationContext(for: request) {
|
if let existing = existingAuthenticationContext(for: request) {
|
||||||
logger.log("Short circuiting wait, existing valid context already exists.")
|
logger.log("Short circuiting wait, existing valid context already exists.")
|
||||||
return existing
|
return existing
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ extension SocketController {
|
|||||||
guard !data.isEmpty else {
|
guard !data.isEmpty else {
|
||||||
logger.debug("Socket controller received empty data, ending continuation.")
|
logger.debug("Socket controller received empty data, ending continuation.")
|
||||||
messagesContinuation.finish()
|
messagesContinuation.finish()
|
||||||
try fileHandle.close()
|
try? fileHandle.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
messagesContinuation.yield(data)
|
messagesContinuation.yield(data)
|
||||||
@@ -126,8 +126,8 @@ private extension SocketPort {
|
|||||||
convenience init(path: String) {
|
convenience init(path: String) {
|
||||||
var addr = sockaddr_un()
|
var addr = sockaddr_un()
|
||||||
|
|
||||||
let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
|
||||||
unsafe path.withCString { cstring in
|
path.withCString { cstring in
|
||||||
let len = unsafe strlen(cstring)
|
let len = unsafe strlen(cstring)
|
||||||
unsafe strncpy(pointer, cstring, len)
|
unsafe strncpy(pointer, cstring, len)
|
||||||
return len
|
return len
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Brief
|
|||||||
import Observation
|
import Observation
|
||||||
import Common
|
import Common
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import CertificateKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct SecretAgent: App {
|
struct SecretAgent: App {
|
||||||
@@ -21,10 +22,12 @@ struct SecretAgent: App {
|
|||||||
list.add(store: SmartCard.Store())
|
list.add(store: SmartCard.Store())
|
||||||
return list
|
return list
|
||||||
}()
|
}()
|
||||||
|
@MainActor private let certificateStore: CertificateStore = CertificateStore()
|
||||||
|
|
||||||
private let updater = Updater(checkOnLaunch: true)
|
private let updater = Updater(checkOnLaunch: true)
|
||||||
private let notifier = Notifier()
|
private let notifier = Notifier()
|
||||||
private let authenticationHandler = AuthenticationHandler()
|
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)?
|
@State var pending: ([[SignatureRequest]], (Set<SignatureRequest>) async throws -> Void)?
|
||||||
@Environment(\.openWindow) var openWindow
|
@Environment(\.openWindow) var openWindow
|
||||||
@@ -42,18 +45,23 @@ struct SecretAgent: App {
|
|||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
let socketController = SocketController(path: URL.socketPath)
|
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 {
|
for await session in socketController.sessions {
|
||||||
Task {
|
Task {
|
||||||
let inputParser = try await XPCAgentInputParser()
|
|
||||||
do {
|
do {
|
||||||
|
let inputParser = try await XPCAgentInputParser()
|
||||||
for await message in session.messages {
|
for await message in session.messages {
|
||||||
let request = try await inputParser.parse(data: message)
|
let request = try await inputParser.parse(data: message)
|
||||||
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
let agentResponse = await agent.handle(request: request, provenance: session.provenance)
|
||||||
try session.write(agentResponse)
|
try session.write(agentResponse)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
try session.close()
|
try? session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,10 +86,19 @@ struct SecretAgent: App {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
.task {
|
.task {
|
||||||
|
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||||
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
||||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
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 {
|
.task {
|
||||||
await authenticationHandler.setBatchAuthHandler { @MainActor pending, authorize in
|
await authenticationHandler.setBatchAuthHandler { @MainActor pending, authorize in
|
||||||
self.pending = (pending, authorize)
|
self.pending = (pending, authorize)
|
||||||
@@ -90,7 +107,6 @@ struct SecretAgent: App {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
|
||||||
notifier.prompt()
|
notifier.prompt()
|
||||||
_ = withObservationTracking {
|
_ = withObservationTracking {
|
||||||
updater.update
|
updater.update
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ struct BatchedRequestsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Button("Review") {
|
Button("Review") {
|
||||||
Task {
|
Task {
|
||||||
try await review([pending.element])
|
try? await review([pending.element])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ struct BatchedRequestsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Button("Review All") {
|
Button("Review All") {
|
||||||
Task {
|
Task {
|
||||||
try await review(Set(group.element))
|
try? await review(Set(group.element))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ struct Secretive: App {
|
|||||||
guard !agentLaunchController.developmentBuild else { return }
|
guard !agentLaunchController.developmentBuild else { return }
|
||||||
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
if justUpdatedChecker.justUpdatedBuild || !agentLaunchController.running {
|
||||||
// Relaunch the agent, since it'll be running from earlier update still
|
// Relaunch the agent, since it'll be running from earlier update still
|
||||||
try await agentLaunchController.forceLaunch()
|
try? await agentLaunchController.forceLaunch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
enum Preview {}
|
enum Preview {}
|
||||||
|
|
||||||
@@ -38,17 +39,10 @@ extension Preview {
|
|||||||
self.init(secrets: new)
|
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
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? {
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadSecrets() {
|
func reloadSecrets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,16 +76,10 @@ extension Preview {
|
|||||||
self.init(secrets: new)
|
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
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func existingAuthenticationContextProtocol(secret: Preview.Secret) -> AuthenticationContextProtocol? {
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
|
||||||
}
|
|
||||||
|
|
||||||
func reloadSecrets() {
|
func reloadSecrets() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ struct AgentNotRunningView: View {
|
|||||||
guard !loading else { return }
|
guard !loading else { return }
|
||||||
loading = true
|
loading = true
|
||||||
Task {
|
Task {
|
||||||
try await agentLaunchController.forceLaunch()
|
try? await agentLaunchController.forceLaunch()
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
if !agentLaunchController.running {
|
if !agentLaunchController.running {
|
||||||
|
|||||||
@@ -163,10 +163,9 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
|||||||
} else {
|
} else {
|
||||||
if #available(macOS 26.0, *) {
|
if #available(macOS 26.0, *) {
|
||||||
content
|
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))
|
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: 15))
|
||||||
.mask(RoundedRectangle(cornerRadius: 15))
|
.mask(RoundedRectangle(cornerRadius: 15))
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 15))
|
||||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
.shadow(color: .black.opacity(0.1), radius: 5)
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
@@ -182,7 +181,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
|||||||
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
let base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||||
switch interactionState {
|
switch interactionState {
|
||||||
case .normal:
|
case .normal:
|
||||||
return base
|
return base.mix(with: .accentColor, by: 0)
|
||||||
case .hovering:
|
case .hovering:
|
||||||
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
||||||
case .clicking, .dragging:
|
case .clicking, .dragging:
|
||||||
|
|||||||
Reference in New Issue
Block a user