mirror of
				https://github.com/maxgoedjen/secretive.git
				synced 2025-10-30 23:10:57 +00:00 
			
		
		
		
	More progress.
This commit is contained in:
		
							parent
							
								
									a9d7e7644e
								
							
						
					
					
						commit
						376f26ef38
					
				| @ -9,16 +9,26 @@ public protocol Secret: Identifiable, Hashable { | |||||||
| 
 | 
 | ||||||
| public struct AnySecret: Secret { | public struct AnySecret: Secret { | ||||||
| 
 | 
 | ||||||
|  |     let base: Any | ||||||
|     fileprivate let hashable: AnyHashable |     fileprivate let hashable: AnyHashable | ||||||
|     fileprivate let _id: () -> AnyHashable |     fileprivate let _id: () -> AnyHashable | ||||||
|     fileprivate let _name: () -> String |     fileprivate let _name: () -> String | ||||||
|     fileprivate let _publicKey: () -> Data |     fileprivate let _publicKey: () -> Data | ||||||
| 
 | 
 | ||||||
|     public init<T>(_ secret: T) where T: Secret { |     public init<T>(_ secret: T) where T: Secret { | ||||||
|         self.hashable = secret |         if let secret = secret as? AnySecret { | ||||||
|         _id = { secret.id as AnyHashable } |             base = secret.base | ||||||
|         _name = { secret.name } |             hashable = secret.hashable | ||||||
|         _publicKey = { secret.publicKey } |             _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 { |     public var id: AnyHashable { | ||||||
|  | |||||||
| @ -1,13 +1,21 @@ | |||||||
| import Combine | import Combine | ||||||
| 
 | 
 | ||||||
| public protocol SecretStore: ObservableObject { | public protocol SecretStore: ObservableObject, Identifiable { | ||||||
| 
 | 
 | ||||||
|     associatedtype SecretType: Secret |     associatedtype SecretType: Secret | ||||||
|  | 
 | ||||||
|     var isAvailable: Bool { get } |     var isAvailable: Bool { get } | ||||||
|  |     var id: UUID { get } | ||||||
|     var name: String { get } |     var name: String { get } | ||||||
|     var secrets: [SecretType] { get } |     var secrets: [SecretType] { get } | ||||||
| 
 | 
 | ||||||
|     func sign(data: Data, with secret: SecretType) throws -> Data |     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 |     func delete(secret: SecretType) throws | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| @ -20,26 +28,30 @@ extension NSNotification.Name { | |||||||
| 
 | 
 | ||||||
| public class AnySecretStore: SecretStore { | public class AnySecretStore: SecretStore { | ||||||
| 
 | 
 | ||||||
|     fileprivate let base: Any |     let base: Any | ||||||
|     fileprivate let _isAvailable: () -> Bool |     fileprivate let _isAvailable: () -> Bool | ||||||
|  |     fileprivate let _id: () -> UUID | ||||||
|     fileprivate let _name: () -> String |     fileprivate let _name: () -> String | ||||||
|     fileprivate let _secrets: () -> [AnySecret] |     fileprivate let _secrets: () -> [AnySecret] | ||||||
|     fileprivate let _sign: (Data, AnySecret) throws -> Data |     fileprivate let _sign: (Data, AnySecret) throws -> Data | ||||||
|     fileprivate let _delete: (AnySecret) throws -> Void |  | ||||||
| 
 | 
 | ||||||
|     public init<T>(_ secretStore: T) where T: SecretStore { |     public init<SecretStoreType>(_ secretStore: SecretStoreType) where SecretStoreType: SecretStore { | ||||||
|         base = secretStore |         base = secretStore | ||||||
|         _isAvailable = { secretStore.isAvailable } |         _isAvailable = { secretStore.isAvailable } | ||||||
|         _name = { secretStore.name } |         _name = { secretStore.name } | ||||||
|  |         _id = { secretStore.id } | ||||||
|         _secrets = { secretStore.secrets.map { AnySecret($0) } } |         _secrets = { secretStore.secrets.map { AnySecret($0) } } | ||||||
|         _sign = { try secretStore.sign(data: $0, with: $1 as! T.SecretType) } |         _sign = { try secretStore.sign(data: $0, with: $1 as! SecretStoreType.SecretType) } | ||||||
|         _delete = { try secretStore.delete(secret: $0 as! T.SecretType) } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public var isAvailable: Bool { |     public var isAvailable: Bool { | ||||||
|         return _isAvailable() |         return _isAvailable() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public var id: UUID { | ||||||
|  |         return _id() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public var name: String { |     public var name: String { | ||||||
|         return _name() |         return _name() | ||||||
|     } |     } | ||||||
| @ -52,6 +64,23 @@ public class AnySecretStore: SecretStore { | |||||||
|         try _sign(data, secret) |         try _sign(data, secret) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable { | ||||||
|  | 
 | ||||||
|  |     fileprivate let _create: (String, Bool) throws -> Void | ||||||
|  |     fileprivate let _delete: (AnySecret) throws -> Void | ||||||
|  | 
 | ||||||
|  |     public init<SecretStoreType>(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 { |     public func delete(secret: AnySecret) throws { | ||||||
|         try _delete(secret) |         try _delete(secret) | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										22
									
								
								SecretKit/SecretStoreList.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								SecretKit/SecretStoreList.swift
									
									
									
									
									
										Normal file
									
								
							| @ -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<SecretStoreType: SecretStore>(store: SecretStoreType) { | ||||||
|  |         stores.append(AnySecretStore(store)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public func add<SecretStoreType: SecretStoreModifiable>(store: SecretStoreType) { | ||||||
|  |         let modifiable = AnySecretStoreModifiable(modifiable: store) | ||||||
|  |         modifiableStore = modifiable | ||||||
|  |         stores.append(modifiable) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -4,7 +4,7 @@ import CryptoTokenKit | |||||||
| 
 | 
 | ||||||
| extension SecureEnclave { | extension SecureEnclave { | ||||||
| 
 | 
 | ||||||
|     public class Store: SecretStore { |     public class Store: SecretStoreModifiable { | ||||||
| 
 | 
 | ||||||
|         public var isAvailable: Bool { |         public var isAvailable: Bool { | ||||||
|             // For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false |             // For some reason, as of build time, CryptoKit.SecureEnclave.isAvailable always returns false | ||||||
| @ -12,6 +12,7 @@ extension SecureEnclave { | |||||||
|             // Verify it with TKTokenWatcher manually. |             // Verify it with TKTokenWatcher manually. | ||||||
|             return TKTokenWatcher().tokenIDs.contains("com.apple.setoken") |             return TKTokenWatcher().tokenIDs.contains("com.apple.setoken") | ||||||
|         } |         } | ||||||
|  |         public let id = UUID() | ||||||
|         public let name = NSLocalizedString("Secure Enclave", comment: "Secure Enclave") |         public let name = NSLocalizedString("Secure Enclave", comment: "Secure Enclave") | ||||||
|         @Published public fileprivate(set) var secrets: [Secret] = [] |         @Published public fileprivate(set) var secrets: [Secret] = [] | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,23 +10,24 @@ extension SmartCard { | |||||||
| 
 | 
 | ||||||
|         // TODO: Read actual smart card name, eg "YubiKey 5c" |         // TODO: Read actual smart card name, eg "YubiKey 5c" | ||||||
|         @Published public var isAvailable: Bool = false |         @Published public var isAvailable: Bool = false | ||||||
|  |         public let id = UUID() | ||||||
|         public let name = NSLocalizedString("Smart Card", comment: "Smart Card") |         public let name = NSLocalizedString("Smart Card", comment: "Smart Card") | ||||||
|         @Published public fileprivate(set) var secrets: [Secret] = [] |         @Published public fileprivate(set) var secrets: [Secret] = [] | ||||||
|         fileprivate let watcher = TKTokenWatcher() |         fileprivate let watcher = TKTokenWatcher() | ||||||
|         fileprivate var id: String? |         fileprivate var tokenID: String? | ||||||
| 
 | 
 | ||||||
|         public init() { |         public init() { | ||||||
|             id = watcher.tokenIDs.filter { !$0.contains("setoken") }.first |             tokenID = watcher.tokenIDs.filter { !$0.contains("setoken") }.first | ||||||
|             watcher.setInsertionHandler { string in |             watcher.setInsertionHandler { string in | ||||||
|                 guard self.id == nil else { return } |                 guard self.tokenID == nil else { return } | ||||||
|                 guard !string.contains("setoken") else { return } |                 guard !string.contains("setoken") else { return } | ||||||
|                 self.id = string |                 self.tokenID = string | ||||||
|                 self.reloadSecrets() |                 self.reloadSecrets() | ||||||
|                 self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) |                 self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) | ||||||
|             } |             } | ||||||
|             if let id = id { |             if let tokenID = tokenID { | ||||||
|                 self.isAvailable = true |                 self.isAvailable = true | ||||||
|                 self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: id) |                 self.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID) | ||||||
|             } |             } | ||||||
|             loadSecrets() |             loadSecrets() | ||||||
|         } |         } | ||||||
| @ -42,12 +43,12 @@ extension SmartCard { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public func sign(data: Data, with secret: SecretType) throws -> Data { |         public func sign(data: Data, with secret: SecretType) throws -> Data { | ||||||
|             guard let id = id else { fatalError() } |             guard let tokenID = tokenID else { fatalError() } | ||||||
|             let attributes = [ |             let attributes = [ | ||||||
|                 kSecClass: kSecClassKey, |                 kSecClass: kSecClassKey, | ||||||
|                 kSecAttrKeyClass: kSecAttrKeyClassPrivate, |                 kSecAttrKeyClass: kSecAttrKeyClassPrivate, | ||||||
|                 kSecAttrApplicationLabel: secret.id as CFData, |                 kSecAttrApplicationLabel: secret.id as CFData, | ||||||
|                 kSecAttrTokenID: id, |                 kSecAttrTokenID: tokenID, | ||||||
|                 kSecReturnRef: true |                 kSecReturnRef: true | ||||||
|                 ] as CFDictionary |                 ] as CFDictionary | ||||||
|             var untyped: CFTypeRef? |             var untyped: CFTypeRef? | ||||||
| @ -73,23 +74,23 @@ extension SmartCard { | |||||||
| extension SmartCard.Store { | extension SmartCard.Store { | ||||||
| 
 | 
 | ||||||
|     fileprivate func smartcardRemoved(for tokenID: String? = nil) { |     fileprivate func smartcardRemoved(for tokenID: String? = nil) { | ||||||
|         id = nil |         self.tokenID = nil | ||||||
|         reloadSecrets() |         reloadSecrets() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fileprivate func reloadSecrets() { |     fileprivate func reloadSecrets() { | ||||||
|         DispatchQueue.main.async { |         DispatchQueue.main.async { | ||||||
|             self.isAvailable = self.id != nil |             self.isAvailable = self.tokenID != nil | ||||||
|             self.secrets.removeAll() |             self.secrets.removeAll() | ||||||
|             self.loadSecrets() |             self.loadSecrets() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fileprivate func loadSecrets() { |     fileprivate func loadSecrets() { | ||||||
|         guard let id = id else { return } |         guard let tokenID = tokenID else { return } | ||||||
|         let attributes = [ |         let attributes = [ | ||||||
|             kSecClass: kSecClassKey, |             kSecClass: kSecClassKey, | ||||||
|             kSecAttrTokenID: id, |             kSecAttrTokenID: tokenID, | ||||||
|             kSecReturnRef: true, |             kSecReturnRef: true, | ||||||
|             kSecMatchLimit: kSecMatchLimitAll, |             kSecMatchLimit: kSecMatchLimitAll, | ||||||
|             kSecReturnAttributes: true |             kSecReturnAttributes: true | ||||||
| @ -99,12 +100,12 @@ extension SmartCard.Store { | |||||||
|         guard let typed = untyped as? [[CFString: Any]] else { return } |         guard let typed = untyped as? [[CFString: Any]] else { return } | ||||||
|         let wrapped: [SmartCard.Secret] = typed.map { |         let wrapped: [SmartCard.Secret] = typed.map { | ||||||
|             let name = $0[kSecAttrLabel] as? String ?? "Unnamed" |             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 publicKeyRef = $0[kSecValueRef] as! SecKey | ||||||
|             let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)! |             let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)! | ||||||
|             let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any] |             let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any] | ||||||
|             let publicKey = publicKeyAttributes[kSecValueData] as! Data |             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) |         secrets.append(contentsOf: wrapped) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ | |||||||
| 		50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */; }; | 		50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */; }; | ||||||
| 		50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; }; | 		50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DCF23FCED2C0099B055 /* SecureEnclave.swift */; }; | ||||||
| 		50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50617DD123FCEFA90099B055 /* PreviewStore.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, ); }; }; | 		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 */; }; | 		5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */; }; | ||||||
| 		5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5099A02623FE34FA0062B6F2 /* SmartCard.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 = "<group>"; }; | 		50617DCD23FCECFA0099B055 /* SecureEnclaveSecret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclaveSecret.swift; sourceTree = "<group>"; }; | ||||||
| 		50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = "<group>"; }; | 		50617DCF23FCED2C0099B055 /* SecureEnclave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureEnclave.swift; sourceTree = "<group>"; }; | ||||||
| 		50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = "<group>"; }; | 		50617DD123FCEFA90099B055 /* PreviewStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewStore.swift; sourceTree = "<group>"; }; | ||||||
|  | 		5068389D241471CD00F55094 /* SecretStoreList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretStoreList.swift; sourceTree = "<group>"; }; | ||||||
| 		5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = "<group>"; }; | 		5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSecretView.swift; sourceTree = "<group>"; }; | ||||||
| 		5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = "<group>"; }; | 		5099A02623FE34FA0062B6F2 /* SmartCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCard.swift; sourceTree = "<group>"; }; | ||||||
| 		5099A02823FE35240062B6F2 /* SmartCardStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCardStore.swift; sourceTree = "<group>"; }; | 		5099A02823FE35240062B6F2 /* SmartCardStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartCardStore.swift; sourceTree = "<group>"; }; | ||||||
| @ -327,6 +329,7 @@ | |||||||
| 				50617DAA23FCE4AB0099B055 /* SecretKit.h */, | 				50617DAA23FCE4AB0099B055 /* SecretKit.h */, | ||||||
| 				50617DCA23FCECA10099B055 /* Secret.swift */, | 				50617DCA23FCECA10099B055 /* Secret.swift */, | ||||||
| 				50617DC623FCE4EA0099B055 /* SecretStore.swift */, | 				50617DC623FCE4EA0099B055 /* SecretStore.swift */, | ||||||
|  | 				5068389D241471CD00F55094 /* SecretStoreList.swift */, | ||||||
| 				5099A02C23FE56D70062B6F2 /* Common */, | 				5099A02C23FE56D70062B6F2 /* Common */, | ||||||
| 				50617DCC23FCECEE0099B055 /* SecureEnclave */, | 				50617DCC23FCECEE0099B055 /* SecureEnclave */, | ||||||
| 				5099A02523FE34DE0062B6F2 /* SmartCard */, | 				5099A02523FE34DE0062B6F2 /* SmartCard */, | ||||||
| @ -731,6 +734,7 @@ | |||||||
| 				50617DC923FCE50E0099B055 /* SecureEnclaveStore.swift in Sources */, | 				50617DC923FCE50E0099B055 /* SecureEnclaveStore.swift in Sources */, | ||||||
| 				50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */, | 				50617DCE23FCECFA0099B055 /* SecureEnclaveSecret.swift in Sources */, | ||||||
| 				50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */, | 				50617DD023FCED2C0099B055 /* SecureEnclave.swift in Sources */, | ||||||
|  | 				5068389E241471CD00F55094 /* SecretStoreList.swift in Sources */, | ||||||
| 				5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */, | 				5099A02923FE35240062B6F2 /* SmartCardStore.swift in Sources */, | ||||||
| 				5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */, | 				5099A02B23FE352C0062B6F2 /* SmartCardSecret.swift in Sources */, | ||||||
| 				50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */, | 				50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */, | ||||||
|  | |||||||
| @ -7,15 +7,16 @@ class AppDelegate: NSObject, NSApplicationDelegate { | |||||||
| 
 | 
 | ||||||
|     var window: NSWindow! |     var window: NSWindow! | ||||||
|     @IBOutlet var toolbar: NSToolbar! |     @IBOutlet var toolbar: NSToolbar! | ||||||
|     let secureEnclave = SecureEnclave.Store() |     let storeList: SecretStoreList = { | ||||||
|     let smartCard = SmartCard.Store() |         let list = SecretStoreList() | ||||||
|     lazy var allStores: [AnySecretStore] = { |         list.add(store: SecureEnclave.Store()) | ||||||
|         [AnySecretStore(secureEnclave), AnySecretStore(smartCard)] |         list.add(store: SmartCard.Store()) | ||||||
|  |         return list | ||||||
|     }() |     }() | ||||||
| 
 | 
 | ||||||
|     func applicationDidFinishLaunching(_ aNotification: Notification) { |     func applicationDidFinishLaunching(_ aNotification: Notification) { | ||||||
| 
 | 
 | ||||||
|         let contentView = ContentView(secureEnclave: secureEnclave, smartCard: smartCard) |         let contentView = ContentView(storeList: storeList) | ||||||
|         // Create the window and set the content view. |         // Create the window and set the content view. | ||||||
|         window = NSWindow( |         window = NSWindow( | ||||||
|             contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), |             contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), | ||||||
| @ -27,7 +28,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { | |||||||
|         window.makeKeyAndOrderFront(nil) |         window.makeKeyAndOrderFront(nil) | ||||||
|         window.titleVisibility = .hidden |         window.titleVisibility = .hidden | ||||||
|         window.toolbar = toolbar |         window.toolbar = toolbar | ||||||
|         if secureEnclave.isAvailable { |         if storeList.modifiableStore?.isAvailable ?? false { | ||||||
|             let plus = NSTitlebarAccessoryViewController() |             let plus = NSTitlebarAccessoryViewController() | ||||||
|             plus.view = NSButton(image: NSImage(named: NSImage.addTemplateName)!, target: self, action: #selector(add(sender:))) |             plus.view = NSButton(image: NSImage(named: NSImage.addTemplateName)!, target: self, action: #selector(add(sender:))) | ||||||
|             plus.layoutAttribute = .right |             plus.layoutAttribute = .right | ||||||
| @ -38,7 +39,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { | |||||||
| 
 | 
 | ||||||
|     @IBAction func add(sender: AnyObject?) { |     @IBAction func add(sender: AnyObject?) { | ||||||
|         var addWindow: NSWindow! |         var addWindow: NSWindow! | ||||||
|         let addView = CreateSecretView(store: secureEnclave) { |         let addView = CreateSecretView(store: storeList.modifiableStore!) { | ||||||
|             self.window.endSheet(addWindow) |             self.window.endSheet(addWindow) | ||||||
|         } |         } | ||||||
|         addWindow = NSWindow( |         addWindow = NSWindow( | ||||||
|  | |||||||
| @ -3,57 +3,53 @@ import SecretKit | |||||||
| 
 | 
 | ||||||
| struct ContentView: View { | struct ContentView: View { | ||||||
|      |      | ||||||
|     @ObservedObject var secureEnclave: SecureEnclave.Store |     @ObservedObject var storeList: SecretStoreList | ||||||
|     @ObservedObject var smartCard: SmartCard.Store |     @State var active: AnySecret.ID? | ||||||
|     @State var active: Data? |  | ||||||
|      |      | ||||||
|     @State var showingDeletion = false |     @State var showingDeletion = false | ||||||
|     @State var deletingSecret: SecureEnclave.Secret? |     @State var deletingSecret: AnySecret? | ||||||
|      |      | ||||||
|     var body: some View { |     var body: some View { | ||||||
|         NavigationView { |         NavigationView { | ||||||
|             List(selection: $active) { |             List(selection: $active) { | ||||||
|                 if secureEnclave.isAvailable { |                 ForEach(storeList.stores) { store in | ||||||
|                     Section(header: Text(secureEnclave.name)) { |                     if store.isAvailable { | ||||||
|                         ForEach(secureEnclave.secrets) { secret in |                         Section(header: Text(store.name)) { | ||||||
|                             NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { |                             ForEach(store.secrets) { secret in | ||||||
|                                 Text(secret.name) |                                 NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: self.$active) { | ||||||
|                             }.contextMenu { |                                     Text(secret.name) | ||||||
|                                 Button(action: { self.delete(secret: secret) }) { |                                 }.contextMenu { | ||||||
|                                     Text("Delete") |                                     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 { |             }.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()) |             .listStyle(SidebarListStyle()) | ||||||
|             .frame(minWidth: 100, idealWidth: 240) |             .frame(minWidth: 100, idealWidth: 240) | ||||||
|         } |         } | ||||||
|         .navigationViewStyle(DoubleColumnNavigationViewStyle()) |         .navigationViewStyle(DoubleColumnNavigationViewStyle()) | ||||||
|         .sheet(isPresented: $showingDeletion) { |         .sheet(isPresented: $showingDeletion) { | ||||||
|             DeleteSecretView(secret: self.deletingSecret!, store: self.secureEnclave) { |             if self.storeList.modifiableStore != nil { | ||||||
|                 self.showingDeletion = false |                 DeleteSecretView(secret: self.deletingSecret!, store: self.storeList.modifiableStore!) { | ||||||
|  |                     self.showingDeletion = false | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|          |          | ||||||
|     } |     } | ||||||
|      |      | ||||||
|      |      | ||||||
|     func delete(secret: SecureEnclave.Secret) { |     func delete<SecretType: Secret>(secret: SecretType) { | ||||||
|         deletingSecret = secret |         deletingSecret = AnySecret(secret) | ||||||
|         showingDeletion = true |         self.showingDeletion = true | ||||||
|     } |     } | ||||||
|      |      | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ import SecretKit | |||||||
| 
 | 
 | ||||||
| struct CreateSecretView: View { | struct CreateSecretView: View { | ||||||
|      |      | ||||||
|     @ObservedObject var store: SecureEnclave.Store |     @ObservedObject var store: AnySecretStoreModifiable | ||||||
|      |      | ||||||
|     @State var name = "" |     @State var name = "" | ||||||
|     @State var requiresAuthentication = true |     @State var requiresAuthentication = true | ||||||
|  | |||||||
| @ -1,16 +1,16 @@ | |||||||
| import SwiftUI | import SwiftUI | ||||||
| import SecretKit | import SecretKit | ||||||
| 
 | 
 | ||||||
| struct DeleteSecretView: View { | struct DeleteSecretView<StoreType: SecretStoreModifiable>: View { | ||||||
|      |      | ||||||
|     let secret: SecureEnclave.Secret |     let secret: StoreType.SecretType | ||||||
|     @ObservedObject var store: SecureEnclave.Store |     @ObservedObject var store: StoreType | ||||||
|      |      | ||||||
|     @State var confirm = "" |     @State var confirm = "" | ||||||
|      |      | ||||||
|     fileprivate var dismissalBlock: () -> () |     fileprivate var dismissalBlock: () -> () | ||||||
|      |      | ||||||
|     init(secret: SecureEnclave.Secret, store: SecureEnclave.Store, dismissalBlock: @escaping () -> ()) { |     init(secret: StoreType.SecretType, store: StoreType, dismissalBlock: @escaping () -> ()) { | ||||||
|         self.secret = secret |         self.secret = secret | ||||||
|         self.store = store |         self.store = store | ||||||
|         self.dismissalBlock = dismissalBlock |         self.dismissalBlock = dismissalBlock | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ extension Preview { | |||||||
|     class Store: SecretStore, ObservableObject { |     class Store: SecretStore, ObservableObject { | ||||||
| 
 | 
 | ||||||
|         let isAvailable = true |         let isAvailable = true | ||||||
|  |         let id = UUID() | ||||||
|         let name = "Preview Store" |         let name = "Preview Store" | ||||||
|         @Published var secrets: [Secret] = [] |         @Published var secrets: [Secret] = [] | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user