Availibilty

This commit is contained in:
Max Goedjen 2020-03-07 15:42:40 -08:00
parent 734df29065
commit a9d7e7644e
No known key found for this signature in database
GPG Key ID: E58C21DD77B9B8E8
6 changed files with 47 additions and 18 deletions

View File

@ -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<T>(_ 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()

View File

@ -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] = []

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ extension Preview {
class Store: SecretStore, ObservableObject {
let isAvailable = true
let name = "Preview Store"
@Published var secrets: [Secret] = []