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 Observation | ||||
| import Security | ||||
| import CryptoTokenKit | ||||
| @preconcurrency import CryptoTokenKit | ||||
| import LocalAuthentication | ||||
| import SecretKit | ||||
| 
 | ||||
| @ -23,6 +23,9 @@ extension SmartCard { | ||||
|         public var isAvailable: Bool { | ||||
|             state.isAvailable | ||||
|         } | ||||
|         @MainActor public var smartcardTokenID: String? { | ||||
|             state.tokenID | ||||
|         } | ||||
| 
 | ||||
|         public let id = UUID() | ||||
|         @MainActor public var name: String { | ||||
| @ -34,17 +37,18 @@ extension SmartCard { | ||||
| 
 | ||||
|         /// Initializes a Store. | ||||
|         public init() { | ||||
|             Task { @MainActor in | ||||
|                 if let tokenID = state.tokenID { | ||||
|             Task { | ||||
|                 await MainActor.run { | ||||
|                     if let tokenID = smartcardTokenID { | ||||
|                         state.isAvailable = true | ||||
|                         state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: tokenID) | ||||
|                     } | ||||
|                     loadSecrets() | ||||
|                 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. | ||||
|                 } | ||||
|                 // Doing this inside a regular mainactor handler casues thread assertions in CryptoTokenKit to blow up when the handler executes. | ||||
|                 await state.watcher.setInsertionHandler { id in | ||||
|                     Task { | ||||
|                         self.smartcardInserted(for: id) | ||||
|                         await self.smartcardInserted(for: id) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @ -126,6 +130,7 @@ extension SmartCard.Store { | ||||
|         state.tokenID = string | ||||
|         state.watcher.addRemovalHandler(self.smartcardRemoved, forTokenID: string) | ||||
|         state.tokenID = string | ||||
|         reloadSecretsInternal() | ||||
|     } | ||||
| 
 | ||||
|     /// 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 { | ||||
| 
 | ||||
|     /// All available tokens, excluding the Secure Enclave. | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user