mirror of
				https://github.com/maxgoedjen/secretive.git
				synced 2025-11-04 09:20:56 +00:00 
			
		
		
		
	Merge 9b1c3fdc88 into c63d87cbec
				
					
				
			This commit is contained in:
		
						commit
						19cc93e649
					
				@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					import LocalAuthentication
 | 
				
			||||||
 | 
					import SecretKit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension SmartCard {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// A context describing a persisted authentication.
 | 
				
			||||||
 | 
					    final class PersistentAuthenticationContext: PersistedAuthenticationContext {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// The Secret to persist authentication for.
 | 
				
			||||||
 | 
					        let secret: Secret
 | 
				
			||||||
 | 
					        /// The LAContext used to authorize the persistent context.
 | 
				
			||||||
 | 
					        nonisolated(unsafe) let context: LAContext
 | 
				
			||||||
 | 
					        /// An expiration date for the context.
 | 
				
			||||||
 | 
					        /// - Note -  Monotonic time instead of Date() to prevent people setting the clock back.
 | 
				
			||||||
 | 
					        let monotonicExpiration: UInt64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// Initializes a context.
 | 
				
			||||||
 | 
					        /// - Parameters:
 | 
				
			||||||
 | 
					        ///   - secret: The Secret to persist authentication for.
 | 
				
			||||||
 | 
					        ///   - context: The LAContext used to authorize the persistent context.
 | 
				
			||||||
 | 
					        ///   - duration: The duration of the authorization context, in seconds.
 | 
				
			||||||
 | 
					        init(secret: Secret, context: LAContext, duration: TimeInterval) {
 | 
				
			||||||
 | 
					            self.secret = secret
 | 
				
			||||||
 | 
					            unsafe self.context = context
 | 
				
			||||||
 | 
					            let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
 | 
				
			||||||
 | 
					            self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// A boolean describing whether or not the context is still valid.
 | 
				
			||||||
 | 
					        var valid: Bool {
 | 
				
			||||||
 | 
					            clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var expiration: Date {
 | 
				
			||||||
 | 
					            let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
 | 
				
			||||||
 | 
					            let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
 | 
				
			||||||
 | 
					            return Date(timeIntervalSinceNow: remainingInSeconds)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    actor PersistentAuthenticationHandler: Sendable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        func existingPersistedAuthenticationContext(secret: Secret) -> PersistentAuthenticationContext? {
 | 
				
			||||||
 | 
					            guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
 | 
				
			||||||
 | 
					            return persisted
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
 | 
				
			||||||
 | 
					            let newContext = LAContext()
 | 
				
			||||||
 | 
					            newContext.touchIDAuthenticationAllowableReuseDuration = duration
 | 
				
			||||||
 | 
					            newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let formatter = DateComponentsFormatter()
 | 
				
			||||||
 | 
					            formatter.unitsStyle = .spellOut
 | 
				
			||||||
 | 
					            formatter.allowedUnits = [.hour, .minute, .day]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            let durationString = formatter.string(from: duration)!
 | 
				
			||||||
 | 
					            newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
 | 
				
			||||||
 | 
					            let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
 | 
				
			||||||
 | 
					            guard success else { return }
 | 
				
			||||||
 | 
					            let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
 | 
				
			||||||
 | 
					            persistedAuthenticationContexts[secret] = context
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -34,6 +34,7 @@ extension SmartCard {
 | 
				
			|||||||
        public var secrets: [Secret] {
 | 
					        public var secrets: [Secret] {
 | 
				
			||||||
            state.secrets
 | 
					            state.secrets
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Initializes a Store.
 | 
					        /// Initializes a Store.
 | 
				
			||||||
        public init() {
 | 
					        public init() {
 | 
				
			||||||
@ -58,9 +59,15 @@ extension SmartCard {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
 | 
					        public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
 | 
				
			||||||
            guard let tokenID = await state.tokenID else { fatalError() }
 | 
					            guard let tokenID = await state.tokenID else { fatalError() }
 | 
				
			||||||
            let context = LAContext()
 | 
					            var context: LAContext
 | 
				
			||||||
            context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
 | 
					            if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
 | 
				
			||||||
            context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
 | 
					                context = unsafe existing.context
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                let newContext = LAContext()
 | 
				
			||||||
 | 
					                newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
 | 
				
			||||||
 | 
					                newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
 | 
				
			||||||
 | 
					                context = newContext
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            let attributes = KeychainDictionary([
 | 
					            let attributes = KeychainDictionary([
 | 
				
			||||||
                kSecClass: kSecClassKey,
 | 
					                kSecClass: kSecClassKey,
 | 
				
			||||||
                kSecAttrKeyClass: kSecAttrKeyClassPrivate,
 | 
					                kSecAttrKeyClass: kSecAttrKeyClassPrivate,
 | 
				
			||||||
@ -86,11 +93,12 @@ extension SmartCard {
 | 
				
			|||||||
            return signature as Data
 | 
					            return signature as Data
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
 | 
					        public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
 | 
				
			||||||
            nil
 | 
					            await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public func persistAuthentication(secret: Secret, forDuration: TimeInterval) throws {
 | 
					        public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
 | 
				
			||||||
 | 
					            try await persistentAuthenticationHandler.persistAuthentication(secret: secret, forDuration: duration)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// Reloads all secrets from the store.
 | 
					        /// Reloads all secrets from the store.
 | 
				
			||||||
@ -163,7 +171,7 @@ extension SmartCard.Store {
 | 
				
			|||||||
            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
 | 
				
			||||||
            let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .unknown)
 | 
					            let attributes = Attributes(keyType: KeyType(secAttr: algorithmSecAttr, size: keySize)!, authentication: .presenceRequired)
 | 
				
			||||||
            let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
 | 
					            let secret = SmartCard.Secret(id: tokenID, name: name, publicKey: publicKey, attributes: attributes)
 | 
				
			||||||
            guard signatureAlgorithm(for: secret) != nil else { return nil }
 | 
					            guard signatureAlgorithm(for: secret) != nil else { return nil }
 | 
				
			||||||
            return secret
 | 
					            return secret
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user