diff --git a/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift b/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift new file mode 100644 index 0000000..3f6db48 --- /dev/null +++ b/Sources/Packages/Sources/SecretKit/PublicKeyStandinFileController.swift @@ -0,0 +1,41 @@ +import Foundation +import OSLog + +/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts. +public class PublicKeyFileStoreController { + + private let logger = Logger() + private let directory: String + + /// Initializes a PublicKeyFileStoreController. + public init(homeDirectory: String) { + directory = homeDirectory.appending("/PublicKeys") + } + + /// Writes out the keys specified to disk. + /// - Parameter secrets: The Secrets to generate keys for. + /// - Parameter clear: Whether or not the directory should be erased before writing keys. + public func generatePublicKeys(for secrets: [AnySecret], clear: Bool = false) throws { + logger.log("Writing public keys to disk") + if clear { + try? FileManager.default.removeItem(at: URL(fileURLWithPath: directory)) + } + try? FileManager.default.createDirectory(at: URL(fileURLWithPath: directory), withIntermediateDirectories: false, attributes: nil) + let keyWriter = OpenSSHKeyWriter() + for secret in secrets { + let path = path(for: secret) + guard let data = keyWriter.openSSHString(secret: secret).data(using: .utf8) else { continue } + FileManager.default.createFile(atPath: path, contents: data, attributes: nil) + } + logger.log("Finished writing public keys") + } + + /// The path for a Secret's public key. + /// - Parameter secret: The Secret to return the path for. + /// - Returns: The path to the Secret's public key. + /// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to. + public func path(for secret: SecretType) -> String { + directory.appending("/").appending("\(secret.name.replacingOccurrences(of: " ", with: "-")).pub") + } + +} diff --git a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift index 4ecd6b4..830401c 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift +++ b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift @@ -4,8 +4,6 @@ import CryptoTokenKit import LocalAuthentication import SecretKit -// TODO: Might need to split this up into "sub-stores?" -// ie, each token has its own Store. extension SmartCard { /// An implementation of Store backed by a Smart Card. diff --git a/Sources/SecretAgent/AppDelegate.swift b/Sources/SecretAgent/AppDelegate.swift index 626163a..2c221dd 100644 --- a/Sources/SecretAgent/AppDelegate.swift +++ b/Sources/SecretAgent/AppDelegate.swift @@ -18,6 +18,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { }() private let updater = Updater(checkOnLaunch: false) private let notifier = Notifier() + private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory()) private lazy var agent: Agent = { Agent(storeList: storeList, witness: notifier) }() @@ -32,6 +33,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { DispatchQueue.main.async { self.socketController.handler = self.agent.handle(reader:writer:) } + DistributedNotificationCenter.default().addObserver(forName: .secretStoreUpdated, object: nil, queue: .main) { [self] _ in + try? publicKeyFileStoreController.generatePublicKeys(for: storeList.stores.flatMap({ $0.secrets }), clear: true) + } + try? publicKeyFileStoreController.generatePublicKeys(for: storeList.stores.flatMap({ $0.secrets }), clear: true) notifier.prompt() updateSink = updater.$update.sink { update in guard let update = update else { return } @@ -39,6 +44,5 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - } diff --git a/Sources/Secretive/Views/SecretDetailView.swift b/Sources/Secretive/Views/SecretDetailView.swift index e6e335a..d756679 100644 --- a/Sources/Secretive/Views/SecretDetailView.swift +++ b/Sources/Secretive/Views/SecretDetailView.swift @@ -6,6 +6,7 @@ struct SecretDetailView: View { @State var secret: SecretType private let keyWriter = OpenSSHKeyWriter() + private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID)) var body: some View { ScrollView { @@ -18,6 +19,9 @@ struct SecretDetailView: View { Spacer() .frame(height: 20) CopyableView(title: "Public Key", image: Image(systemName: "key"), text: keyString) + Spacer() + .frame(height: 20) + CopyableView(title: "Public Key Path", image: Image(systemName: "lock.doc"), text: publicKeyFileStoreController.path(for: secret)) Spacer() } } @@ -40,12 +44,7 @@ struct SecretDetailView: View { var keyString: String { keyWriter.openSSHString(secret: secret, comment: "\(dashedKeyName)@\(dashedHostName)") } - - func copy() { - NSPasteboard.general.declareTypes([.string], owner: nil) - NSPasteboard.general.setString(keyString, forType: .string) - } - + } #if DEBUG