mirror of
				https://github.com/maxgoedjen/secretive.git
				synced 2025-10-26 13:00:58 +00:00 
			
		
		
		
	Fixes for insertion handler on smartcard (#622)
This commit is contained in:
		
							parent
							
								
									e3938caecb
								
							
						
					
					
						commit
						2ba73ff680
					
				| @ -1,7 +1,7 @@ | |||||||
| import Foundation | import Foundation | ||||||
| import Observation | import Observation | ||||||
| import Security | import Security | ||||||
| import CryptoTokenKit | @preconcurrency import CryptoTokenKit | ||||||
| import LocalAuthentication | import LocalAuthentication | ||||||
| import SecretKit | import SecretKit | ||||||
| 
 | 
 | ||||||
| @ -23,6 +23,9 @@ extension SmartCard { | |||||||
|         public var isAvailable: Bool { |         public var isAvailable: Bool { | ||||||
|             state.isAvailable |             state.isAvailable | ||||||
|         } |         } | ||||||
|  |         @MainActor public var smartcardTokenID: String? { | ||||||
|  |             state.tokenID | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         public let id = UUID() |         public let id = UUID() | ||||||
|         @MainActor public var name: String { |         @MainActor public var name: String { | ||||||
| @ -34,17 +37,18 @@ extension SmartCard { | |||||||
| 
 | 
 | ||||||
|         /// Initializes a Store. |         /// Initializes a Store. | ||||||
|         public init() { |         public init() { | ||||||
|             Task { @MainActor in |             Task { | ||||||
|                 if let tokenID = state.tokenID { |                 await MainActor.run { | ||||||
|                     state.isAvailable = true |                     if let tokenID = smartcardTokenID { | ||||||
|                     state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID) |                         state.isAvailable = true | ||||||
|  |                         state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID) | ||||||
|  |                     } | ||||||
|  |                     loadSecrets() | ||||||
|                 } |                 } | ||||||
|                 loadSecrets() |                 // Doing this inside a regular mainactor handler casues thread assertions in CryptoTokenKit to blow up when the handler executes. | ||||||
|                 state.watcher.setInsertionHandler { id in |                 await state.watcher.setInsertionHandler { id in | ||||||
|                     // Setting insertion handler will cause it to be called immediately. |  | ||||||
|                     // Make a thread jump so we don't hit a recursive lock attempt. |  | ||||||
|                     Task { |                     Task { | ||||||
|                         self.smartcardInserted(for: id) |                         await self.smartcardInserted(for: id) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @ -120,12 +124,13 @@ extension SmartCard.Store { | |||||||
|     /// Resets the token ID and reloads secrets. |     /// Resets the token ID and reloads secrets. | ||||||
|     /// - Parameter tokenID: The ID of the token that was inserted. |     /// - Parameter tokenID: The ID of the token that was inserted. | ||||||
|     @MainActor private func smartcardInserted(for tokenID: String? = nil) { |     @MainActor private func smartcardInserted(for tokenID: String? = nil) { | ||||||
|             guard let string = state.watcher.nonSecureEnclaveTokens.first else { return } |         guard let string = state.watcher.nonSecureEnclaveTokens.first else { return } | ||||||
|             guard state.tokenID == nil else { return } |         guard state.tokenID == nil else { return } | ||||||
|             guard !string.contains("setoken") else { return } |         guard !string.contains("setoken") else { return } | ||||||
|             state.tokenID = string |         state.tokenID = string | ||||||
|             state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) |         state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) | ||||||
|             state.tokenID = string |         state.tokenID = string | ||||||
|  |         reloadSecretsInternal() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Resets the token ID and reloads secrets. |     /// Resets the token ID and reloads secrets. | ||||||
| @ -172,88 +177,6 @@ extension SmartCard.Store { | |||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| // MARK: Smart Card specific encryption/decryption/verification |  | ||||||
| extension SmartCard.Store { |  | ||||||
| 
 |  | ||||||
|     /// Encrypts a payload with a specified key. |  | ||||||
|     /// - Parameters: |  | ||||||
|     ///   - data: The payload to encrypt. |  | ||||||
|     ///   - secret: The secret to encrypt with. |  | ||||||
|     /// - Returns: The encrypted data. |  | ||||||
|     /// - Warning: Encryption functions are deliberately only exposed on a library level, and are not exposed in Secretive itself to prevent users from data loss. Any pull requests which expose this functionality in the app will not be merged. |  | ||||||
|     public func encrypt(data: Data, with secret: SecretType) throws -> Data { |  | ||||||
|         let context = LAContext() |  | ||||||
|         context.localizedReason = String(localized: .authContextRequestEncryptDescription(secretName: secret.name)) |  | ||||||
|         context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) |  | ||||||
|         let attributes = KeychainDictionary([ |  | ||||||
|             kSecAttrKeyType: secret.algorithm.secAttrKeyType, |  | ||||||
|             kSecAttrKeySizeInBits: secret.keySize, |  | ||||||
|             kSecAttrKeyClass: kSecAttrKeyClassPublic, |  | ||||||
|             kSecUseAuthenticationContext: context |  | ||||||
|         ]) |  | ||||||
|         var encryptError: SecurityError? |  | ||||||
|         let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &encryptError) |  | ||||||
|         guard let untypedSafe = untyped else { |  | ||||||
|             throw KeychainError(statusCode: errSecSuccess) |  | ||||||
|         } |  | ||||||
|         let key = untypedSafe as! SecKey |  | ||||||
|         guard let signature = SecKeyCreateEncryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else { |  | ||||||
|             throw SigningError(error: encryptError) |  | ||||||
|         } |  | ||||||
|         return signature as Data |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /// Decrypts a payload with a specified key. |  | ||||||
|     /// - Parameters: |  | ||||||
|     ///   - data: The payload to decrypt. |  | ||||||
|     ///   - secret: The secret to decrypt with. |  | ||||||
|     /// - Returns: The decrypted data. |  | ||||||
|     /// - Warning: Encryption functions are deliberately only exposed on a library level, and are not exposed in Secretive itself to prevent users from data loss. Any pull requests which expose this functionality in the app will not be merged. |  | ||||||
|     public func decrypt(data: Data, with secret: SecretType) async throws -> Data { |  | ||||||
|         guard let tokenID = await state.tokenID else { fatalError() } |  | ||||||
|         let context = LAContext() |  | ||||||
|         context.localizedReason = String(localized: .authContextRequestDecryptDescription(secretName: secret.name)) |  | ||||||
|         context.localizedCancelTitle = String(localized: .authContextRequestDenyButton) |  | ||||||
|         let attributes = KeychainDictionary([ |  | ||||||
|             kSecClass: kSecClassKey, |  | ||||||
|             kSecAttrKeyClass: kSecAttrKeyClassPrivate, |  | ||||||
|             kSecAttrApplicationLabel: secret.id as CFData, |  | ||||||
|             kSecAttrTokenID: tokenID, |  | ||||||
|             kSecUseAuthenticationContext: context, |  | ||||||
|             kSecReturnRef: true |  | ||||||
|         ]) |  | ||||||
|         var untyped: CFTypeRef? |  | ||||||
|         let status = SecItemCopyMatching(attributes, &untyped) |  | ||||||
|         if status != errSecSuccess { |  | ||||||
|             throw KeychainError(statusCode: status) |  | ||||||
|         } |  | ||||||
|         guard let untypedSafe = untyped else { |  | ||||||
|             throw KeychainError(statusCode: errSecSuccess) |  | ||||||
|         } |  | ||||||
|         let key = untypedSafe as! SecKey |  | ||||||
|         var encryptError: SecurityError? |  | ||||||
|         guard let signature = SecKeyCreateDecryptedData(key, encryptionAlgorithm(for: secret), data as CFData, &encryptError) else { |  | ||||||
|             throw SigningError(error: encryptError) |  | ||||||
|         } |  | ||||||
|         return signature as Data |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private func encryptionAlgorithm(for secret: SecretType) -> SecKeyAlgorithm { |  | ||||||
|         switch (secret.algorithm, secret.keySize) { |  | ||||||
|         case (.ellipticCurve, 256): |  | ||||||
|             return .eciesEncryptionCofactorVariableIVX963SHA256AESGCM |  | ||||||
|         case (.ellipticCurve, 384): |  | ||||||
|             return .eciesEncryptionCofactorVariableIVX963SHA384AESGCM |  | ||||||
|         case (.rsa, 1024), (.rsa, 2048): |  | ||||||
|             return .rsaEncryptionOAEPSHA512AESGCM |  | ||||||
|         default: |  | ||||||
|             fatalError() |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| extension TKTokenWatcher { | extension TKTokenWatcher { | ||||||
| 
 | 
 | ||||||
|     /// All available tokens, excluding the Secure Enclave. |     /// All available tokens, excluding the Secure Enclave. | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user