From e87c181e86301c186d3c4a536dd02a67de42ac27 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 7 Mar 2020 15:04:36 -0800 Subject: [PATCH 01/12] Adding type erasers --- SecretKit/Secret.swift | 31 ++++++++++++++++- SecretKit/SecretStore.swift | 34 +++++++++++++++++++ .../SecureEnclave/SecureEnclaveSecret.swift | 8 +++++ SecretKit/SmartCard/SmartCardSecret.swift | 10 +++++- Secretive/AppDelegate.swift | 3 ++ 5 files changed, 84 insertions(+), 2 deletions(-) diff --git a/SecretKit/Secret.swift b/SecretKit/Secret.swift index a4c40b5..1d4fe0a 100644 --- a/SecretKit/Secret.swift +++ b/SecretKit/Secret.swift @@ -1,6 +1,35 @@ -public protocol Secret: Identifiable, Hashable { +public protocol Secret: Identifiable { var name: String { get } var publicKey: Data { get } } + + + +public struct AnySecret: Secret { + + fileprivate let _id: () -> AnyHashable + fileprivate let _name: () -> String + fileprivate let _publicKey: () -> Data + + public init(_ secret: T) where T: Secret { + _id = { secret.id as AnyHashable } + _name = { secret.name } + _publicKey = { secret.publicKey } + } + + public var id: AnyHashable { + return _id() + } + + public var name: String { + return _name() + } + + public var publicKey: Data { + return _publicKey() + } + +} + diff --git a/SecretKit/SecretStore.swift b/SecretKit/SecretStore.swift index d944836..8adc893 100644 --- a/SecretKit/SecretStore.swift +++ b/SecretKit/SecretStore.swift @@ -16,3 +16,37 @@ extension NSNotification.Name { static let secretStoreUpdated = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.updated") } + +public class AnySecretStore: SecretStore { + + fileprivate let _name: () -> String + fileprivate let _secrets: () -> [AnySecret] + fileprivate let _sign: (Data, AnySecret) throws -> Data + fileprivate let _delete: (AnySecret) throws -> Void + + public init(_ secretStore: T) where T: SecretStore { + _name = { secretStore.name } + _secrets = { secretStore.secrets.map { AnySecret($0) } } + _sign = { try secretStore.sign(data: $0, with: $1 as! T.SecretType) } + _delete = { try secretStore.delete(secret: $0 as! T.SecretType) } + } + + public var name: String { + return _name() + } + + public var secrets: [AnySecret] { + return _secrets() + } + + public func sign(data: Data, with secret: AnySecret) throws -> Data { + try _sign(data, secret) + } + + public func delete(secret: AnySecret) throws { + try _delete(secret) + } + +} + + diff --git a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift index 3ab9f5f..33f65e4 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift @@ -4,11 +4,19 @@ import Combine extension SecureEnclave { public struct Secret: SecretKit.Secret { + public init(id: Data, name: String, publicKey: Data) { + self.id = id + self.name = name + self.publicKey = publicKey + } + public let id: Data public let name: String public let publicKey: Data + + } } diff --git a/SecretKit/SmartCard/SmartCardSecret.swift b/SecretKit/SmartCard/SmartCardSecret.swift index 6444470..b5dd310 100644 --- a/SecretKit/SmartCard/SmartCardSecret.swift +++ b/SecretKit/SmartCard/SmartCardSecret.swift @@ -4,9 +4,17 @@ import Combine extension SmartCard { public struct Secret: SecretKit.Secret { + public init(id: Data, name: String, publicKey: Data) { + self.id = id +// self.name = name + self.publicKey = publicKey + } + public let id: Data - public let name: String + public var name: String { + UUID().uuidString + } public let publicKey: Data } diff --git a/Secretive/AppDelegate.swift b/Secretive/AppDelegate.swift index 746dd9d..3f10f41 100644 --- a/Secretive/AppDelegate.swift +++ b/Secretive/AppDelegate.swift @@ -9,6 +9,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet var toolbar: NSToolbar! let secureEnclave = SecureEnclave.Store() let smartCard = SmartCard.Store() + lazy var allStores: [AnySecretStore] = { + [AnySecretStore(secureEnclave), AnySecretStore(smartCard)] + }() func applicationDidFinishLaunching(_ aNotification: Notification) { From dc5cce0b39613079c6901ed3e681d8ee58bcc850 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 7 Mar 2020 15:06:10 -0800 Subject: [PATCH 02/12] Add base --- SecretKit/SecretStore.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SecretKit/SecretStore.swift b/SecretKit/SecretStore.swift index 8adc893..8dd3129 100644 --- a/SecretKit/SecretStore.swift +++ b/SecretKit/SecretStore.swift @@ -19,12 +19,14 @@ extension NSNotification.Name { public class AnySecretStore: SecretStore { + fileprivate let base: Any fileprivate let _name: () -> String fileprivate let _secrets: () -> [AnySecret] fileprivate let _sign: (Data, AnySecret) throws -> Data fileprivate let _delete: (AnySecret) throws -> Void public init(_ secretStore: T) where T: SecretStore { + base = secretStore _name = { secretStore.name } _secrets = { secretStore.secrets.map { AnySecret($0) } } _sign = { try secretStore.sign(data: $0, with: $1 as! T.SecretType) } From 8863a42308325c41aa3ac0e3bdb50cb96bfa1a00 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 7 Mar 2020 15:09:18 -0800 Subject: [PATCH 03/12] Restore hashable --- SecretKit/Secret.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/SecretKit/Secret.swift b/SecretKit/Secret.swift index 1d4fe0a..49a6536 100644 --- a/SecretKit/Secret.swift +++ b/SecretKit/Secret.swift @@ -1,4 +1,4 @@ -public protocol Secret: Identifiable { +public protocol Secret: Identifiable, Hashable { var name: String { get } var publicKey: Data { get } @@ -9,11 +9,13 @@ public protocol Secret: Identifiable { public struct AnySecret: Secret { + fileprivate let hashable: AnyHashable fileprivate let _id: () -> AnyHashable fileprivate let _name: () -> String fileprivate let _publicKey: () -> Data public init(_ secret: T) where T: Secret { + self.hashable = secret _id = { secret.id as AnyHashable } _name = { secret.name } _publicKey = { secret.publicKey } @@ -31,5 +33,13 @@ public struct AnySecret: Secret { return _publicKey() } + public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool { + lhs.hashable == rhs.hashable + } + + public func hash(into hasher: inout Hasher) { + hashable.hash(into: &hasher) + } + } From 734df290650b3097775d80f787288909a5b87d51 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 7 Mar 2020 15:20:59 -0800 Subject: [PATCH 04/12] Remove changes --- .../SecureEnclave/SecureEnclaveSecret.swift | 8 -------- SecretKit/SmartCard/SmartCardSecret.swift | 12 ++--------- SecretKit/SmartCard/SmartCardStore.swift | 14 +++++++++++-- Secretive/AppDelegate.swift | 2 +- Secretive/ContentView.swift | 20 +++++++++++++------ 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift index 33f65e4..3ab9f5f 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveSecret.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveSecret.swift @@ -4,19 +4,11 @@ import Combine extension SecureEnclave { public struct Secret: SecretKit.Secret { - public init(id: Data, name: String, publicKey: Data) { - self.id = id - self.name = name - self.publicKey = publicKey - } - public let id: Data public let name: String public let publicKey: Data - - } } diff --git a/SecretKit/SmartCard/SmartCardSecret.swift b/SecretKit/SmartCard/SmartCardSecret.swift index b5dd310..1054f5f 100644 --- a/SecretKit/SmartCard/SmartCardSecret.swift +++ b/SecretKit/SmartCard/SmartCardSecret.swift @@ -4,17 +4,9 @@ import Combine extension SmartCard { public struct Secret: SecretKit.Secret { - public init(id: Data, name: String, publicKey: Data) { - self.id = id -// self.name = name - self.publicKey = publicKey - } - - + public let id: Data - public var name: String { - UUID().uuidString - } + public var name: String public let publicKey: Data } diff --git a/SecretKit/SmartCard/SmartCardStore.swift b/SecretKit/SmartCard/SmartCardStore.swift index 2f7361a..9707614 100644 --- a/SecretKit/SmartCard/SmartCardStore.swift +++ b/SecretKit/SmartCard/SmartCardStore.swift @@ -20,8 +20,11 @@ extension SmartCard { guard self.id == nil else { return } guard !string.contains("setoken") else { return } self.id = string - self.secrets.removeAll() - self.loadSecrets() + self.reloadSecrets() + self.watcher.addRemovalHandler(self.reloadSecrets, forTokenID: string) + } + if let id = id { + self.watcher.addRemovalHandler(self.reloadSecrets, forTokenID: id) } loadSecrets() } @@ -67,6 +70,13 @@ extension SmartCard { extension SmartCard.Store { + fileprivate func reloadSecrets(for tokenID: String? = nil) { + DispatchQueue.main.async { + self.secrets.removeAll() + self.loadSecrets() + } + } + fileprivate func loadSecrets() { guard let id = id else { return } let attributes = [ diff --git a/Secretive/AppDelegate.swift b/Secretive/AppDelegate.swift index 3f10f41..84003e1 100644 --- a/Secretive/AppDelegate.swift +++ b/Secretive/AppDelegate.swift @@ -15,7 +15,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { - let contentView = ContentView(store: secureEnclave) + let contentView = ContentView(secureEnclave: secureEnclave, smartCard: smartCard) // Create the window and set the content view. window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), diff --git a/Secretive/ContentView.swift b/Secretive/ContentView.swift index f7bc4cc..8bc2e40 100644 --- a/Secretive/ContentView.swift +++ b/Secretive/ContentView.swift @@ -3,8 +3,9 @@ import SecretKit struct ContentView: View { - @ObservedObject var store: SecureEnclave.Store - @State var active: SecureEnclave.Secret.ID? + @ObservedObject var secureEnclave: SecureEnclave.Store + @ObservedObject var smartCard: SmartCard.Store + @State var active: Data? @State var showingDeletion = false @State var deletingSecret: SecureEnclave.Secret? @@ -12,8 +13,8 @@ struct ContentView: View { var body: some View { NavigationView { List(selection: $active) { - Section(header: Text(store.name)) { - ForEach(store.secrets) { secret in + Section(header: Text(secureEnclave.name)) { + ForEach(secureEnclave.secrets) { secret in NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { Text(secret.name) }.contextMenu { @@ -23,15 +24,22 @@ struct ContentView: View { } } } + Section(header: Text(smartCard.name)) { + ForEach(smartCard.secrets) { secret in + NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { + Text(secret.name) + } + } + } }.onAppear { - self.active = self.store.secrets.first?.id + self.active = self.secureEnclave.secrets.first?.id ?? self.smartCard.secrets.first?.id } .listStyle(SidebarListStyle()) .frame(minWidth: 100, idealWidth: 240) } .navigationViewStyle(DoubleColumnNavigationViewStyle()) .sheet(isPresented: $showingDeletion) { - DeleteSecretView(secret: self.deletingSecret!, store: self.store) { + DeleteSecretView(secret: self.deletingSecret!, store: self.secureEnclave) { self.showingDeletion = false } } From a9d7e7644ebc3e3be1b694c78b4c290983b2a3f3 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 7 Mar 2020 15:42:40 -0800 Subject: [PATCH 05/12] Availibilty --- SecretKit/SecretStore.swift | 7 +++++ .../SecureEnclave/SecureEnclaveStore.swift | 7 +++++ SecretKit/SmartCard/SmartCardStore.swift | 14 +++++++--- Secretive/AppDelegate.swift | 10 ++++--- Secretive/ContentView.swift | 26 +++++++++++-------- Secretive/Preview Content/PreviewStore.swift | 1 + 6 files changed, 47 insertions(+), 18 deletions(-) diff --git a/SecretKit/SecretStore.swift b/SecretKit/SecretStore.swift index 8dd3129..a2f6615 100644 --- a/SecretKit/SecretStore.swift +++ b/SecretKit/SecretStore.swift @@ -3,6 +3,7 @@ import Combine public protocol SecretStore: ObservableObject { associatedtype SecretType: Secret + var isAvailable: Bool { get } var name: String { get } var secrets: [SecretType] { get } @@ -20,6 +21,7 @@ extension NSNotification.Name { public class AnySecretStore: SecretStore { fileprivate let base: Any + fileprivate let _isAvailable: () -> Bool fileprivate let _name: () -> String fileprivate let _secrets: () -> [AnySecret] fileprivate let _sign: (Data, AnySecret) throws -> Data @@ -27,11 +29,16 @@ public class AnySecretStore: SecretStore { public init(_ secretStore: T) where T: SecretStore { base = secretStore + _isAvailable = { secretStore.isAvailable } _name = { secretStore.name } _secrets = { secretStore.secrets.map { AnySecret($0) } } _sign = { try secretStore.sign(data: $0, with: $1 as! T.SecretType) } _delete = { try secretStore.delete(secret: $0 as! T.SecretType) } } + + public var isAvailable: Bool { + return _isAvailable() + } public var name: String { return _name() diff --git a/SecretKit/SecureEnclave/SecureEnclaveStore.swift b/SecretKit/SecureEnclave/SecureEnclaveStore.swift index 8e9ac2e..4eb3bbd 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveStore.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveStore.swift @@ -1,10 +1,17 @@ import Foundation import Security +import CryptoTokenKit extension SecureEnclave { public class Store: SecretStore { + public var isAvailable: Bool { + // For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false + // error msg "Received error sending GET UNIQUE DEVICE command" + // Verify it with TKTokenWatcher manually. + return TKTokenWatcher().tokenIDs.contains("com.apple.setoken") + } public let name = NSLocalizedString("Secure Enclave", comment: "Secure Enclave") @Published public fileprivate(set) var secrets: [Secret] = [] diff --git a/SecretKit/SmartCard/SmartCardStore.swift b/SecretKit/SmartCard/SmartCardStore.swift index 9707614..c95ea8a 100644 --- a/SecretKit/SmartCard/SmartCardStore.swift +++ b/SecretKit/SmartCard/SmartCardStore.swift @@ -9,6 +9,7 @@ extension SmartCard { public class Store: SecretStore { // TODO: Read actual smart card name, eg "YubiKey 5c" + @Published public var isAvailable: Bool = false public let name = NSLocalizedString("Smart Card", comment: "Smart Card") @Published public fileprivate(set) var secrets: [Secret] = [] fileprivate let watcher = TKTokenWatcher() @@ -21,10 +22,11 @@ extension SmartCard { guard !string.contains("setoken") else { return } self.id = string self.reloadSecrets() - self.watcher.addRemovalHandler(self.reloadSecrets, forTokenID: string) + self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) } if let id = id { - self.watcher.addRemovalHandler(self.reloadSecrets, forTokenID: id) + self.isAvailable = true + self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: id) } loadSecrets() } @@ -70,8 +72,14 @@ extension SmartCard { extension SmartCard.Store { - fileprivate func reloadSecrets(for tokenID: String? = nil) { + fileprivate func smartcardRemoved(for tokenID: String? = nil) { + id = nil + reloadSecrets() + } + + fileprivate func reloadSecrets() { DispatchQueue.main.async { + self.isAvailable = self.id != nil self.secrets.removeAll() self.loadSecrets() } diff --git a/Secretive/AppDelegate.swift b/Secretive/AppDelegate.swift index 84003e1..9caaae9 100644 --- a/Secretive/AppDelegate.swift +++ b/Secretive/AppDelegate.swift @@ -27,10 +27,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { window.makeKeyAndOrderFront(nil) window.titleVisibility = .hidden window.toolbar = toolbar - let plus = NSTitlebarAccessoryViewController() - plus.view = NSButton(image: NSImage(named: NSImage.addTemplateName)!, target: self, action: #selector(add(sender:))) - plus.layoutAttribute = .right - window.addTitlebarAccessoryViewController(plus) + if secureEnclave.isAvailable { + let plus = NSTitlebarAccessoryViewController() + plus.view = NSButton(image: NSImage(named: NSImage.addTemplateName)!, target: self, action: #selector(add(sender:))) + plus.layoutAttribute = .right + window.addTitlebarAccessoryViewController(plus) + } runSetupIfNeeded() } diff --git a/Secretive/ContentView.swift b/Secretive/ContentView.swift index 8bc2e40..0eb07cd 100644 --- a/Secretive/ContentView.swift +++ b/Secretive/ContentView.swift @@ -13,21 +13,25 @@ struct ContentView: View { var body: some View { NavigationView { List(selection: $active) { - Section(header: Text(secureEnclave.name)) { - ForEach(secureEnclave.secrets) { secret in - NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { - Text(secret.name) - }.contextMenu { - Button(action: { self.delete(secret: secret) }) { - Text("Delete") + if secureEnclave.isAvailable { + Section(header: Text(secureEnclave.name)) { + ForEach(secureEnclave.secrets) { secret in + NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { + Text(secret.name) + }.contextMenu { + Button(action: { self.delete(secret: secret) }) { + Text("Delete") + } } } } } - Section(header: Text(smartCard.name)) { - ForEach(smartCard.secrets) { secret in - NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { - Text(secret.name) + if smartCard.isAvailable { + Section(header: Text(smartCard.name)) { + ForEach(smartCard.secrets) { secret in + NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { + Text(secret.name) + } } } } diff --git a/Secretive/Preview Content/PreviewStore.swift b/Secretive/Preview Content/PreviewStore.swift index cfc41d0..16f52a0 100644 --- a/Secretive/Preview Content/PreviewStore.swift +++ b/Secretive/Preview Content/PreviewStore.swift @@ -19,6 +19,7 @@ extension Preview { class Store: SecretStore, ObservableObject { + let isAvailable = true let name = "Preview Store" @Published var secrets: [Secret] = [] From 376f26ef38cf489595ffebb7e9086b49096aaffc Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 20:03:40 -0700 Subject: [PATCH 06/12] More progress. --- SecretKit/Secret.swift | 18 +++++-- SecretKit/SecretStore.swift | 43 +++++++++++++--- SecretKit/SecretStoreList.swift | 22 ++++++++ .../SecureEnclave/SecureEnclaveStore.swift | 3 +- SecretKit/SmartCard/SmartCardStore.swift | 29 +++++------ Secretive.xcodeproj/project.pbxproj | 4 ++ Secretive/AppDelegate.swift | 15 +++--- Secretive/ContentView.swift | 50 +++++++++---------- Secretive/CreateSecretView.swift | 2 +- Secretive/DeleteSecretView.swift | 8 +-- Secretive/Preview Content/PreviewStore.swift | 1 + 11 files changed, 130 insertions(+), 65 deletions(-) create mode 100644 SecretKit/SecretStoreList.swift diff --git a/SecretKit/Secret.swift b/SecretKit/Secret.swift index 49a6536..2e486d4 100644 --- a/SecretKit/Secret.swift +++ b/SecretKit/Secret.swift @@ -9,16 +9,26 @@ public protocol Secret: Identifiable, Hashable { public struct AnySecret: Secret { + let base: Any fileprivate let hashable: AnyHashable fileprivate let _id: () -> AnyHashable fileprivate let _name: () -> String fileprivate let _publicKey: () -> Data public init(_ secret: T) where T: Secret { - self.hashable = secret - _id = { secret.id as AnyHashable } - _name = { secret.name } - _publicKey = { secret.publicKey } + if let secret = secret as? AnySecret { + base = secret.base + hashable = secret.hashable + _id = secret._id + _name = secret._name + _publicKey = secret._publicKey + } else { + base = secret as Any + self.hashable = secret + _id = { secret.id as AnyHashable } + _name = { secret.name } + _publicKey = { secret.publicKey } + } } public var id: AnyHashable { diff --git a/SecretKit/SecretStore.swift b/SecretKit/SecretStore.swift index a2f6615..89130d6 100644 --- a/SecretKit/SecretStore.swift +++ b/SecretKit/SecretStore.swift @@ -1,13 +1,21 @@ import Combine -public protocol SecretStore: ObservableObject { +public protocol SecretStore: ObservableObject, Identifiable { associatedtype SecretType: Secret + var isAvailable: Bool { get } + var id: UUID { get } var name: String { get } var secrets: [SecretType] { get } func sign(data: Data, with secret: SecretType) throws -> Data + +} + +public protocol SecretStoreModifiable: SecretStore { + + func create(name: String, requiresAuthentication: Bool) throws func delete(secret: SecretType) throws } @@ -20,26 +28,30 @@ extension NSNotification.Name { public class AnySecretStore: SecretStore { - fileprivate let base: Any + let base: Any fileprivate let _isAvailable: () -> Bool + fileprivate let _id: () -> UUID fileprivate let _name: () -> String fileprivate let _secrets: () -> [AnySecret] fileprivate let _sign: (Data, AnySecret) throws -> Data - fileprivate let _delete: (AnySecret) throws -> Void - public init(_ secretStore: T) where T: SecretStore { + public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore { base = secretStore _isAvailable = { secretStore.isAvailable } _name = { secretStore.name } + _id = { secretStore.id } _secrets = { secretStore.secrets.map { AnySecret($0) } } - _sign = { try secretStore.sign(data: $0, with: $1 as! T.SecretType) } - _delete = { try secretStore.delete(secret: $0 as! T.SecretType) } + _sign = { try secretStore.sign(data: $0, with: $1 as! SecretStoreType.SecretType) } } - + public var isAvailable: Bool { return _isAvailable() } + public var id: UUID { + return _id() + } + public var name: String { return _name() } @@ -52,6 +64,23 @@ public class AnySecretStore: SecretStore { try _sign(data, secret) } +} + +public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable { + + fileprivate let _create: (String, Bool) throws -> Void + fileprivate let _delete: (AnySecret) throws -> Void + + public init(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { + _create = { try secretStore.create(name: $0, requiresAuthentication: $1) } + _delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) } + super.init(secretStore) + } + + public func create(name: String, requiresAuthentication: Bool) throws { + try _create(name, requiresAuthentication) + } + public func delete(secret: AnySecret) throws { try _delete(secret) } diff --git a/SecretKit/SecretStoreList.swift b/SecretKit/SecretStoreList.swift new file mode 100644 index 0000000..5810b97 --- /dev/null +++ b/SecretKit/SecretStoreList.swift @@ -0,0 +1,22 @@ +import Foundation +import Combine + +public class SecretStoreList: ObservableObject { + + @Published public var stores: [AnySecretStore] = [] + @Published public var modifiableStore: AnySecretStoreModifiable? + + public init() { + } + + public func add(store: SecretStoreType) { + stores.append(AnySecretStore(store)) + } + + public func add(store: SecretStoreType) { + let modifiable = AnySecretStoreModifiable(modifiable: store) + modifiableStore = modifiable + stores.append(modifiable) + } + +} diff --git a/SecretKit/SecureEnclave/SecureEnclaveStore.swift b/SecretKit/SecureEnclave/SecureEnclaveStore.swift index 4eb3bbd..7c56e00 100644 --- a/SecretKit/SecureEnclave/SecureEnclaveStore.swift +++ b/SecretKit/SecureEnclave/SecureEnclaveStore.swift @@ -4,7 +4,7 @@ import CryptoTokenKit extension SecureEnclave { - public class Store: SecretStore { + public class Store: SecretStoreModifiable { public var isAvailable: Bool { // For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false @@ -12,6 +12,7 @@ extension SecureEnclave { // Verify it with TKTokenWatcher manually. return TKTokenWatcher().tokenIDs.contains("com.apple.setoken") } + public let id = UUID() public let name = NSLocalizedString("Secure Enclave", comment: "Secure Enclave") @Published public fileprivate(set) var secrets: [Secret] = [] diff --git a/SecretKit/SmartCard/SmartCardStore.swift b/SecretKit/SmartCard/SmartCardStore.swift index c95ea8a..0adfb0d 100644 --- a/SecretKit/SmartCard/SmartCardStore.swift +++ b/SecretKit/SmartCard/SmartCardStore.swift @@ -10,23 +10,24 @@ extension SmartCard { // TODO: Read actual smart card name, eg "YubiKey 5c" @Published public var isAvailable: Bool = false + public let id = UUID() public let name = NSLocalizedString("Smart Card", comment: "Smart Card") @Published public fileprivate(set) var secrets: [Secret] = [] fileprivate let watcher = TKTokenWatcher() - fileprivate var id: String? + fileprivate var tokenID: String? public init() { - id = watcher.tokenIDs.filter { !$0.contains("setoken") }.first + tokenID = watcher.tokenIDs.filter { !$0.contains("setoken") }.first watcher.setInsertionHandler { string in - guard self.id == nil else { return } + guard self.tokenID == nil else { return } guard !string.contains("setoken") else { return } - self.id = string + self.tokenID = string self.reloadSecrets() self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) } - if let id = id { + if let tokenID = tokenID { self.isAvailable = true - self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: id) + self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID) } loadSecrets() } @@ -42,12 +43,12 @@ extension SmartCard { } public func sign(data: Data, with secret: SecretType) throws -> Data { - guard let id = id else { fatalError() } + guard let tokenID = tokenID else { fatalError() } let attributes = [ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrApplicationLabel: secret.id as CFData, - kSecAttrTokenID: id, + kSecAttrTokenID: tokenID, kSecReturnRef: true ] as CFDictionary var untyped: CFTypeRef? @@ -73,23 +74,23 @@ extension SmartCard { extension SmartCard.Store { fileprivate func smartcardRemoved(for tokenID: String? = nil) { - id = nil + self.tokenID = nil reloadSecrets() } fileprivate func reloadSecrets() { DispatchQueue.main.async { - self.isAvailable = self.id != nil + self.isAvailable = self.tokenID != nil self.secrets.removeAll() self.loadSecrets() } } fileprivate func loadSecrets() { - guard let id = id else { return } + guard let tokenID = tokenID else { return } let attributes = [ kSecClass: kSecClassKey, - kSecAttrTokenID: id, + kSecAttrTokenID: tokenID, kSecReturnRef: true, kSecMatchLimit: kSecMatchLimitAll, kSecReturnAttributes: true @@ -99,12 +100,12 @@ extension SmartCard.Store { guard let typed = untyped as? [[CFString: Any]] else { return } let wrapped: [SmartCard.Secret] = typed.map { let name = $0[kSecAttrLabel] as? String ?? "Unnamed" - let id = $0[kSecAttrApplicationLabel] as! Data + let tokenID = $0[kSecAttrApplicationLabel] as! Data let publicKeyRef = $0[kSecValueRef] as! SecKey let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)! let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any] let publicKey = publicKeyAttributes[kSecValueData] as! Data - return SmartCard.Secret(id: id, name: name, publicKey: publicKey) + return SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey) } secrets.append(contentsOf: wrapped) } diff --git a/Secretive.xcodeproj/project.pbxproj b/Secretive.xcodeproj/project.pbxproj index 35e10cf..bce2499 100644 --- a/Secretive.xcodeproj/project.pbxproj +++ b/Secretive.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */; }; 50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; }; 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; }; + 5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5068389D241471CD00F55094 /* SecretStoreList.swift */; }; 506AB87E2412334700335D91 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */; }; 5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02623FE34FA0062B6F2 /* SmartCard.swift */; }; @@ -172,6 +173,7 @@ 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclaveSecret.swift; sourceTree = ""; }; 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = ""; }; 50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = ""; }; + 5068389D241471CD00F55094 /* SecretStoreList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretStoreList.swift; sourceTree = ""; }; 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = ""; }; 5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = ""; }; 5099A02823FE35240062B6F2 /* SmartCardStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCardStore.swift; sourceTree = ""; }; @@ -327,6 +329,7 @@ 50617DAA23FCE4AB0099B055 /* SecretKit.h */, 50617DCA23FCECA10099B055 /* Secret.swift */, 50617DC623FCE4EA0099B055 /* SecretStore.swift */, + 5068389D241471CD00F55094 /* SecretStoreList.swift */, 5099A02C23FE56D70062B6F2 /* Common */, 50617DCC23FCECEE0099B055 /* SecureEnclave */, 5099A02523FE34DE0062B6F2 /* SmartCard */, @@ -731,6 +734,7 @@ 50617DC923FCE50E0099B055 /* SecureEnclaveStore.swift in Sources */, 50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */, 50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */, + 5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */, 5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */, 5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */, 50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */, diff --git a/Secretive/AppDelegate.swift b/Secretive/AppDelegate.swift index 9caaae9..300b805 100644 --- a/Secretive/AppDelegate.swift +++ b/Secretive/AppDelegate.swift @@ -7,15 +7,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! @IBOutlet var toolbar: NSToolbar! - let secureEnclave = SecureEnclave.Store() - let smartCard = SmartCard.Store() - lazy var allStores: [AnySecretStore] = { - [AnySecretStore(secureEnclave), AnySecretStore(smartCard)] + let storeList: SecretStoreList = { + let list = SecretStoreList() + list.add(store: SecureEnclave.Store()) + list.add(store: SmartCard.Store()) + return list }() func applicationDidFinishLaunching(_ aNotification: Notification) { - let contentView = ContentView(secureEnclave: secureEnclave, smartCard: smartCard) + let contentView = ContentView(storeList: storeList) // Create the window and set the content view. window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), @@ -27,7 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { window.makeKeyAndOrderFront(nil) window.titleVisibility = .hidden window.toolbar = toolbar - if secureEnclave.isAvailable { + if storeList.modifiableStore?.isAvailable ?? false { let plus = NSTitlebarAccessoryViewController() plus.view = NSButton(image: NSImage(named: NSImage.addTemplateName)!, target: self, action: #selector(add(sender:))) plus.layoutAttribute = .right @@ -38,7 +39,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { @IBAction func add(sender: AnyObject?) { var addWindow: NSWindow! - let addView = CreateSecretView(store: secureEnclave) { + let addView = CreateSecretView(store: storeList.modifiableStore!) { self.window.endSheet(addWindow) } addWindow = NSWindow( diff --git a/Secretive/ContentView.swift b/Secretive/ContentView.swift index 0eb07cd..951dfa6 100644 --- a/Secretive/ContentView.swift +++ b/Secretive/ContentView.swift @@ -3,57 +3,53 @@ import SecretKit struct ContentView: View { - @ObservedObject var secureEnclave: SecureEnclave.Store - @ObservedObject var smartCard: SmartCard.Store - @State var active: Data? + @ObservedObject var storeList: SecretStoreList + @State var active: AnySecret.ID? @State var showingDeletion = false - @State var deletingSecret: SecureEnclave.Secret? + @State var deletingSecret: AnySecret? var body: some View { NavigationView { List(selection: $active) { - if secureEnclave.isAvailable { - Section(header: Text(secureEnclave.name)) { - ForEach(secureEnclave.secrets) { secret in - NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { - Text(secret.name) - }.contextMenu { - Button(action: { self.delete(secret: secret) }) { - Text("Delete") + ForEach(storeList.stores) { store in + if store.isAvailable { + Section(header: Text(store.name)) { + ForEach(store.secrets) { secret in + NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { + Text(secret.name) + }.contextMenu { + if store is AnySecretStoreModifiable { + Button(action: { self.delete(secret: secret) }) { + Text("Delete") + } + } } } } } } - if smartCard.isAvailable { - Section(header: Text(smartCard.name)) { - ForEach(smartCard.secrets) { secret in - NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { - Text(secret.name) - } - } - } - } }.onAppear { - self.active = self.secureEnclave.secrets.first?.id ?? self.smartCard.secrets.first?.id + self.active = self.storeList.stores.compactMap { $0.secrets.first }.first?.id } .listStyle(SidebarListStyle()) .frame(minWidth: 100, idealWidth: 240) } .navigationViewStyle(DoubleColumnNavigationViewStyle()) .sheet(isPresented: $showingDeletion) { - DeleteSecretView(secret: self.deletingSecret!, store: self.secureEnclave) { - self.showingDeletion = false + if self.storeList.modifiableStore != nil { + DeleteSecretView(secret: self.deletingSecret!, store: self.storeList.modifiableStore!) { + self.showingDeletion = false + } } } } - func delete(secret: SecureEnclave.Secret) { - deletingSecret = secret - showingDeletion = true + func delete(secret: SecretType) { + deletingSecret = AnySecret(secret) + self.showingDeletion = true } } diff --git a/Secretive/CreateSecretView.swift b/Secretive/CreateSecretView.swift index e9cdb6a..88c1bd2 100644 --- a/Secretive/CreateSecretView.swift +++ b/Secretive/CreateSecretView.swift @@ -3,7 +3,7 @@ import SecretKit struct CreateSecretView: View { - @ObservedObject var store: SecureEnclave.Store + @ObservedObject var store: AnySecretStoreModifiable @State var name = "" @State var requiresAuthentication = true diff --git a/Secretive/DeleteSecretView.swift b/Secretive/DeleteSecretView.swift index 6004096..0efe7a1 100644 --- a/Secretive/DeleteSecretView.swift +++ b/Secretive/DeleteSecretView.swift @@ -1,16 +1,16 @@ import SwiftUI import SecretKit -struct DeleteSecretView: View { +struct DeleteSecretView: View { - let secret: SecureEnclave.Secret - @ObservedObject var store: SecureEnclave.Store + let secret: StoreType.SecretType + @ObservedObject var store: StoreType @State var confirm = "" fileprivate var dismissalBlock: () -> () - init(secret: SecureEnclave.Secret, store: SecureEnclave.Store, dismissalBlock: @escaping () -> ()) { + init(secret: StoreType.SecretType, store: StoreType, dismissalBlock: @escaping () -> ()) { self.secret = secret self.store = store self.dismissalBlock = dismissalBlock diff --git a/Secretive/Preview Content/PreviewStore.swift b/Secretive/Preview Content/PreviewStore.swift index 16f52a0..6567697 100644 --- a/Secretive/Preview Content/PreviewStore.swift +++ b/Secretive/Preview Content/PreviewStore.swift @@ -20,6 +20,7 @@ extension Preview { class Store: SecretStore, ObservableObject { let isAvailable = true + let id = UUID() let name = "Preview Store" @Published var secrets: [Secret] = [] From 2f3f5681e73bed7ccb9106d21d315ae1af8c6895 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 20:08:27 -0700 Subject: [PATCH 07/12] Split out into separate files --- SecretKit/Common/Erasers/AnySecret.swift | 48 ++++++++++++++ SecretKit/Common/Erasers/AnySecretStore.swift | 64 +++++++++++++++++++ .../{ => OpenSSH}/OpenSSHKeyWriter.swift | 0 .../Common/OpenSSH}/OpenSSHReader.swift | 0 SecretKit/{ => Common}/SecretStoreList.swift | 0 SecretKit/Secret.swift | 49 -------------- SecretKit/SecretStore.swift | 63 ------------------ Secretive.xcodeproj/project.pbxproj | 32 ++++++++-- 8 files changed, 140 insertions(+), 116 deletions(-) create mode 100644 SecretKit/Common/Erasers/AnySecret.swift create mode 100644 SecretKit/Common/Erasers/AnySecretStore.swift rename SecretKit/Common/{ => OpenSSH}/OpenSSHKeyWriter.swift (100%) rename {SecretAgentKit => SecretKit/Common/OpenSSH}/OpenSSHReader.swift (100%) rename SecretKit/{ => Common}/SecretStoreList.swift (100%) diff --git a/SecretKit/Common/Erasers/AnySecret.swift b/SecretKit/Common/Erasers/AnySecret.swift new file mode 100644 index 0000000..11587bc --- /dev/null +++ b/SecretKit/Common/Erasers/AnySecret.swift @@ -0,0 +1,48 @@ +import Foundation + +public struct AnySecret: Secret { + + let base: Any + fileprivate let hashable: AnyHashable + fileprivate let _id: () -> AnyHashable + fileprivate let _name: () -> String + fileprivate let _publicKey: () -> Data + + public init(_ secret: T) where T: Secret { + if let secret = secret as? AnySecret { + base = secret.base + hashable = secret.hashable + _id = secret._id + _name = secret._name + _publicKey = secret._publicKey + } else { + base = secret as Any + self.hashable = secret + _id = { secret.id as AnyHashable } + _name = { secret.name } + _publicKey = { secret.publicKey } + } + } + + public var id: AnyHashable { + return _id() + } + + public var name: String { + return _name() + } + + public var publicKey: Data { + return _publicKey() + } + + public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool { + lhs.hashable == rhs.hashable + } + + public func hash(into hasher: inout Hasher) { + hashable.hash(into: &hasher) + } + +} + diff --git a/SecretKit/Common/Erasers/AnySecretStore.swift b/SecretKit/Common/Erasers/AnySecretStore.swift new file mode 100644 index 0000000..6bf9809 --- /dev/null +++ b/SecretKit/Common/Erasers/AnySecretStore.swift @@ -0,0 +1,64 @@ +import Foundation + +public class AnySecretStore: SecretStore { + + let base: Any + fileprivate let _isAvailable: () -> Bool + fileprivate let _id: () -> UUID + fileprivate let _name: () -> String + fileprivate let _secrets: () -> [AnySecret] + fileprivate let _sign: (Data, AnySecret) throws -> Data + + public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore { + base = secretStore + _isAvailable = { secretStore.isAvailable } + _name = { secretStore.name } + _id = { secretStore.id } + _secrets = { secretStore.secrets.map { AnySecret($0) } } + _sign = { try secretStore.sign(data: $0, with: $1 as! SecretStoreType.SecretType) } + } + + public var isAvailable: Bool { + return _isAvailable() + } + + public var id: UUID { + return _id() + } + + public var name: String { + return _name() + } + + public var secrets: [AnySecret] { + return _secrets() + } + + public func sign(data: Data, with secret: AnySecret) throws -> Data { + try _sign(data, secret) + } + +} + +public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable { + + fileprivate let _create: (String, Bool) throws -> Void + fileprivate let _delete: (AnySecret) throws -> Void + + public init(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { + _create = { try secretStore.create(name: $0, requiresAuthentication: $1) } + _delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) } + super.init(secretStore) + } + + public func create(name: String, requiresAuthentication: Bool) throws { + try _create(name, requiresAuthentication) + } + + public func delete(secret: AnySecret) throws { + try _delete(secret) + } + +} + + diff --git a/SecretKit/Common/OpenSSHKeyWriter.swift b/SecretKit/Common/OpenSSH/OpenSSHKeyWriter.swift similarity index 100% rename from SecretKit/Common/OpenSSHKeyWriter.swift rename to SecretKit/Common/OpenSSH/OpenSSHKeyWriter.swift diff --git a/SecretAgentKit/OpenSSHReader.swift b/SecretKit/Common/OpenSSH/OpenSSHReader.swift similarity index 100% rename from SecretAgentKit/OpenSSHReader.swift rename to SecretKit/Common/OpenSSH/OpenSSHReader.swift diff --git a/SecretKit/SecretStoreList.swift b/SecretKit/Common/SecretStoreList.swift similarity index 100% rename from SecretKit/SecretStoreList.swift rename to SecretKit/Common/SecretStoreList.swift diff --git a/SecretKit/Secret.swift b/SecretKit/Secret.swift index 2e486d4..a4c40b5 100644 --- a/SecretKit/Secret.swift +++ b/SecretKit/Secret.swift @@ -4,52 +4,3 @@ public protocol Secret: Identifiable, Hashable { var publicKey: Data { get } } - - - -public struct AnySecret: Secret { - - let base: Any - fileprivate let hashable: AnyHashable - fileprivate let _id: () -> AnyHashable - fileprivate let _name: () -> String - fileprivate let _publicKey: () -> Data - - public init(_ secret: T) where T: Secret { - if let secret = secret as? AnySecret { - base = secret.base - hashable = secret.hashable - _id = secret._id - _name = secret._name - _publicKey = secret._publicKey - } else { - base = secret as Any - self.hashable = secret - _id = { secret.id as AnyHashable } - _name = { secret.name } - _publicKey = { secret.publicKey } - } - } - - public var id: AnyHashable { - return _id() - } - - public var name: String { - return _name() - } - - public var publicKey: Data { - return _publicKey() - } - - public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool { - lhs.hashable == rhs.hashable - } - - public func hash(into hasher: inout Hasher) { - hashable.hash(into: &hasher) - } - -} - diff --git a/SecretKit/SecretStore.swift b/SecretKit/SecretStore.swift index 89130d6..ef0488d 100644 --- a/SecretKit/SecretStore.swift +++ b/SecretKit/SecretStore.swift @@ -25,66 +25,3 @@ extension NSNotification.Name { static let secretStoreUpdated = NSNotification.Name("com.maxgoedjen.Secretive.secretStore.updated") } - -public class AnySecretStore: SecretStore { - - let base: Any - fileprivate let _isAvailable: () -> Bool - fileprivate let _id: () -> UUID - fileprivate let _name: () -> String - fileprivate let _secrets: () -> [AnySecret] - fileprivate let _sign: (Data, AnySecret) throws -> Data - - public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore { - base = secretStore - _isAvailable = { secretStore.isAvailable } - _name = { secretStore.name } - _id = { secretStore.id } - _secrets = { secretStore.secrets.map { AnySecret($0) } } - _sign = { try secretStore.sign(data: $0, with: $1 as! SecretStoreType.SecretType) } - } - - public var isAvailable: Bool { - return _isAvailable() - } - - public var id: UUID { - return _id() - } - - public var name: String { - return _name() - } - - public var secrets: [AnySecret] { - return _secrets() - } - - public func sign(data: Data, with secret: AnySecret) throws -> Data { - try _sign(data, secret) - } - -} - -public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable { - - fileprivate let _create: (String, Bool) throws -> Void - fileprivate let _delete: (AnySecret) throws -> Void - - public init(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable { - _create = { try secretStore.create(name: $0, requiresAuthentication: $1) } - _delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) } - super.init(secretStore) - } - - public func create(name: String, requiresAuthentication: Bool) throws { - try _create(name, requiresAuthentication) - } - - public func delete(secret: AnySecret) throws { - try _delete(secret) - } - -} - - diff --git a/Secretive.xcodeproj/project.pbxproj b/Secretive.xcodeproj/project.pbxproj index bce2499..28f48c4 100644 --- a/Secretive.xcodeproj/project.pbxproj +++ b/Secretive.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; }; 50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.swift */; }; 5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5068389D241471CD00F55094 /* SecretStoreList.swift */; }; + 506838A12415EA5600F55094 /* AnySecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A02415EA5600F55094 /* AnySecret.swift */; }; + 506838A32415EA5D00F55094 /* AnySecretStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506838A22415EA5D00F55094 /* AnySecretStore.swift */; }; 506AB87E2412334700335D91 /* SecretAgent.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 50A3B78A24026B7500D209EA /* SecretAgent.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */; }; 5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02623FE34FA0062B6F2 /* SmartCard.swift */; }; @@ -174,6 +176,8 @@ 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = ""; }; 50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = ""; }; 5068389D241471CD00F55094 /* SecretStoreList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretStoreList.swift; sourceTree = ""; }; + 506838A02415EA5600F55094 /* AnySecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecret.swift; sourceTree = ""; }; + 506838A22415EA5D00F55094 /* AnySecretStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnySecretStore.swift; sourceTree = ""; }; 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = ""; }; 5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = ""; }; 5099A02823FE35240062B6F2 /* SmartCardStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCardStore.swift; sourceTree = ""; }; @@ -195,7 +199,7 @@ 50A3B79D24026B9900D209EA /* SocketController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocketController.swift; sourceTree = ""; }; 50A3B79F24026B9900D209EA /* Agent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Agent.swift; sourceTree = ""; }; 50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = ""; }; - 50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretAgentKit/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; }; + 50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretKit/Common/OpenSSH/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; }; 50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = ""; }; 50C385A8240B636500AF2719 /* SetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -329,7 +333,6 @@ 50617DAA23FCE4AB0099B055 /* SecretKit.h */, 50617DCA23FCECA10099B055 /* Secret.swift */, 50617DC623FCE4EA0099B055 /* SecretStore.swift */, - 5068389D241471CD00F55094 /* SecretStoreList.swift */, 5099A02C23FE56D70062B6F2 /* Common */, 50617DCC23FCECEE0099B055 /* SecureEnclave */, 5099A02523FE34DE0062B6F2 /* SmartCard */, @@ -357,6 +360,24 @@ path = SecureEnclave; sourceTree = ""; }; + 5068389F2415EA4F00F55094 /* Erasers */ = { + isa = PBXGroup; + children = ( + 506838A02415EA5600F55094 /* AnySecret.swift */, + 506838A22415EA5D00F55094 /* AnySecretStore.swift */, + ); + path = Erasers; + sourceTree = ""; + }; + 506838A42415EA6800F55094 /* OpenSSH */ = { + isa = PBXGroup; + children = ( + 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */, + 50C385A2240789E600AF2719 /* OpenSSHReader.swift */, + ); + path = OpenSSH; + sourceTree = ""; + }; 5099A02523FE34DE0062B6F2 /* SmartCard */ = { isa = PBXGroup; children = ( @@ -370,8 +391,9 @@ 5099A02C23FE56D70062B6F2 /* Common */ = { isa = PBXGroup; children = ( - 5099A02D23FE56E10062B6F2 /* OpenSSHKeyWriter.swift */, - 50C385A2240789E600AF2719 /* OpenSSHReader.swift */, + 5068389F2415EA4F00F55094 /* Erasers */, + 506838A42415EA6800F55094 /* OpenSSH */, + 5068389D241471CD00F55094 /* SecretStoreList.swift */, ); path = Common; sourceTree = ""; @@ -732,9 +754,11 @@ 50617DCB23FCECA10099B055 /* Secret.swift in Sources */, 5099A02E23FE56E10062B6F2 /* OpenSSHKeyWriter.swift in Sources */, 50617DC923FCE50E0099B055 /* SecureEnclaveStore.swift in Sources */, + 506838A32415EA5D00F55094 /* AnySecretStore.swift in Sources */, 50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */, 50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */, 5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */, + 506838A12415EA5600F55094 /* AnySecret.swift in Sources */, 5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */, 5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */, 50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */, From 286e2dc5582a241f2d6820563c5eab7c5acb278e Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 20:44:15 -0700 Subject: [PATCH 08/12] Combine stuff --- SecretKit/Common/Erasers/AnySecretStore.swift | 5 +++++ SecretKit/Common/SecretStoreList.swift | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/SecretKit/Common/Erasers/AnySecretStore.swift b/SecretKit/Common/Erasers/AnySecretStore.swift index 6bf9809..9e71d2c 100644 --- a/SecretKit/Common/Erasers/AnySecretStore.swift +++ b/SecretKit/Common/Erasers/AnySecretStore.swift @@ -1,4 +1,5 @@ import Foundation +import Combine public class AnySecretStore: SecretStore { @@ -8,6 +9,7 @@ public class AnySecretStore: SecretStore { fileprivate let _name: () -> String fileprivate let _secrets: () -> [AnySecret] fileprivate let _sign: (Data, AnySecret) throws -> Data + fileprivate var sink: AnyCancellable? public init(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore { base = secretStore @@ -16,6 +18,9 @@ public class AnySecretStore: SecretStore { _id = { secretStore.id } _secrets = { secretStore.secrets.map { AnySecret($0) } } _sign = { try secretStore.sign(data: $0, with: $1 as! SecretStoreType.SecretType) } + sink = secretStore.objectWillChange.sink { _ in + self.objectWillChange.send() + } } public var isAvailable: Bool { diff --git a/SecretKit/Common/SecretStoreList.swift b/SecretKit/Common/SecretStoreList.swift index 5810b97..7d7003d 100644 --- a/SecretKit/Common/SecretStoreList.swift +++ b/SecretKit/Common/SecretStoreList.swift @@ -5,18 +5,27 @@ public class SecretStoreList: ObservableObject { @Published public var stores: [AnySecretStore] = [] @Published public var modifiableStore: AnySecretStoreModifiable? + fileprivate var sinks: [AnyCancellable] = [] public init() { } public func add(store: SecretStoreType) { - stores.append(AnySecretStore(store)) + addInternal(store: AnySecretStore(store)) } public func add(store: SecretStoreType) { let modifiable = AnySecretStoreModifiable(modifiable: store) modifiableStore = modifiable - stores.append(modifiable) + addInternal(store: modifiable) + } + + public func addInternal(store: AnySecretStore) { + stores.append(store) + let sink = store.objectWillChange.sink { + self.objectWillChange.send() + } + sinks.append(sink) } } From a0bd55d1bfeee642f3d856e61dc6d2adba8c1fbf Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 21:11:59 -0700 Subject: [PATCH 09/12] Add agent support --- SecretAgent/Agent.swift | 27 ++++++++++++------- SecretAgent/AppDelegate.swift | 9 +++++-- SecretKit/Common/Erasers/AnySecretStore.swift | 4 +-- SecretKit/Common/SecretStoreList.swift | 6 ++++- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/SecretAgent/Agent.swift b/SecretAgent/Agent.swift index c00317a..fa94204 100644 --- a/SecretAgent/Agent.swift +++ b/SecretAgent/Agent.swift @@ -4,14 +4,14 @@ import OSLog import SecretKit import SecretAgentKit -class Agent { +class Agent { - fileprivate let store: StoreType + fileprivate let storeList: SecretStoreList fileprivate let notifier: Notifier - public init(store: StoreType, notifier: Notifier) { + public init(storeList: SecretStoreList, notifier: Notifier) { os_log(.debug, "Agent is running") - self.store = store + self.storeList = storeList self.notifier = notifier } @@ -57,17 +57,18 @@ extension Agent { extension Agent { func identities() throws -> Data { - var count = UInt32(store.secrets.count).bigEndian + let secrets = storeList.stores.flatMap(\.secrets) + var count = UInt32(secrets.count).bigEndian let countData = Data(bytes: &count, count: UInt32.bitWidth/8) var keyData = Data() let writer = OpenSSHKeyWriter() - for secret in store.secrets { + for secret in secrets { let keyBlob = writer.data(secret: secret) keyData.append(writer.lengthAndData(of: keyBlob)) let curveData = OpenSSHKeyWriter.Constants.curveType.data(using: .utf8)! keyData.append(writer.lengthAndData(of: curveData)) } - os_log(.debug, "Agent enumerated %@ identities", store.secrets.count as NSNumber) + os_log(.debug, "Agent enumerated %@ identities", secrets.count as NSNumber) return countData + keyData } @@ -75,10 +76,16 @@ extension Agent { let reader = OpenSSHReader(data: data) let writer = OpenSSHKeyWriter() let hash = try reader.readNextChunk() - let matching = store.secrets.filter { secret in - hash == writer.data(secret: secret) + let matching = storeList.stores.compactMap { store -> (AnySecretStore, AnySecret)? in + let allMatching = store.secrets.filter { secret in + hash == writer.data(secret: secret) + } + if let matching = allMatching.first { + return (store, matching) + } + return nil } - guard let secret = matching.first else { + guard let (store, secret) = matching.first else { throw AgentError.noMatchingKey } let dataToSign = try reader.readNextChunk() diff --git a/SecretAgent/AppDelegate.swift b/SecretAgent/AppDelegate.swift index 5421581..4881158 100644 --- a/SecretAgent/AppDelegate.swift +++ b/SecretAgent/AppDelegate.swift @@ -5,10 +5,15 @@ import OSLog @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - let store = SecureEnclave.Store() + let storeList: SecretStoreList = { + let list = SecretStoreList() + list.add(store: SecureEnclave.Store()) + list.add(store: SmartCard.Store()) + return list + }() let notifier = Notifier() lazy var agent: Agent = { - Agent(store: store, notifier: notifier) + Agent(storeList: storeList, notifier: notifier) }() lazy var socketController: SocketController = { let path = (NSHomeDirectory() as NSString).appendingPathComponent("socket.ssh") as String diff --git a/SecretKit/Common/Erasers/AnySecretStore.swift b/SecretKit/Common/Erasers/AnySecretStore.swift index 9e71d2c..322c31b 100644 --- a/SecretKit/Common/Erasers/AnySecretStore.swift +++ b/SecretKit/Common/Erasers/AnySecretStore.swift @@ -17,7 +17,7 @@ public class AnySecretStore: SecretStore { _name = { secretStore.name } _id = { secretStore.id } _secrets = { secretStore.secrets.map { AnySecret($0) } } - _sign = { try secretStore.sign(data: $0, with: $1 as! SecretStoreType.SecretType) } + _sign = { try secretStore.sign(data: $0, with: $1.base as! SecretStoreType.SecretType) } sink = secretStore.objectWillChange.sink { _ in self.objectWillChange.send() } @@ -65,5 +65,3 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable { } } - - diff --git a/SecretKit/Common/SecretStoreList.swift b/SecretKit/Common/SecretStoreList.swift index 7d7003d..7becd40 100644 --- a/SecretKit/Common/SecretStoreList.swift +++ b/SecretKit/Common/SecretStoreList.swift @@ -20,7 +20,11 @@ public class SecretStoreList: ObservableObject { addInternal(store: modifiable) } - public func addInternal(store: AnySecretStore) { +} + +extension SecretStoreList { + + fileprivate func addInternal(store: AnySecretStore) { stores.append(store) let sink = store.objectWillChange.sink { self.objectWillChange.send() From 6c012e9d24d00c1fa57250209dff5d7249d33fac Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 21:16:43 -0700 Subject: [PATCH 10/12] Restrict key types smart card will load --- SecretKit/SmartCard/SmartCardStore.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SecretKit/SmartCard/SmartCardStore.swift b/SecretKit/SmartCard/SmartCardStore.swift index 0adfb0d..b1ed962 100644 --- a/SecretKit/SmartCard/SmartCardStore.swift +++ b/SecretKit/SmartCard/SmartCardStore.swift @@ -90,6 +90,8 @@ extension SmartCard.Store { guard let tokenID = tokenID else { return } let attributes = [ kSecClass: kSecClassKey, + kSecAttrKeyType: kSecAttrKeyTypeEC, + kSecAttrKeySizeInBits: 256, kSecAttrTokenID: tokenID, kSecReturnRef: true, kSecMatchLimit: kSecMatchLimitAll, From 5e76dd4024d27ce824feb515ee71129f373c67ff Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 21:18:13 -0700 Subject: [PATCH 11/12] Make name immutable --- SecretKit/SmartCard/SmartCardSecret.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecretKit/SmartCard/SmartCardSecret.swift b/SecretKit/SmartCard/SmartCardSecret.swift index 1054f5f..85d3d9e 100644 --- a/SecretKit/SmartCard/SmartCardSecret.swift +++ b/SecretKit/SmartCard/SmartCardSecret.swift @@ -6,7 +6,7 @@ extension SmartCard { public struct Secret: SecretKit.Secret { public let id: Data - public var name: String + public let name: String public let publicKey: Data } From d0b9a0872c11a016b5046a5df436804ff9d57242 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 8 Mar 2020 21:19:47 -0700 Subject: [PATCH 12/12] Trim --- SecretKit/SmartCard/SmartCardSecret.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SecretKit/SmartCard/SmartCardSecret.swift b/SecretKit/SmartCard/SmartCardSecret.swift index 85d3d9e..6444470 100644 --- a/SecretKit/SmartCard/SmartCardSecret.swift +++ b/SecretKit/SmartCard/SmartCardSecret.swift @@ -4,7 +4,7 @@ import Combine extension SmartCard { public struct Secret: SecretKit.Secret { - + public let id: Data public let name: String public let publicKey: Data