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] {
 | 
			
		||||
            state.secrets
 | 
			
		||||
        }
 | 
			
		||||
        private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
 | 
			
		||||
 | 
			
		||||
        /// Initializes a Store.
 | 
			
		||||
        public init() {
 | 
			
		||||
@ -58,9 +59,15 @@ extension SmartCard {
 | 
			
		||||
 | 
			
		||||
        public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
 | 
			
		||||
            guard let tokenID = await state.tokenID else { fatalError() }
 | 
			
		||||
            let context = LAContext()
 | 
			
		||||
            context.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
 | 
			
		||||
            context.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
 | 
			
		||||
            var context: LAContext
 | 
			
		||||
            if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
 | 
			
		||||
                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([
 | 
			
		||||
                kSecClass: kSecClassKey,
 | 
			
		||||
                kSecAttrKeyClass: kSecAttrKeyClassPrivate,
 | 
			
		||||
@ -86,11 +93,12 @@ extension SmartCard {
 | 
			
		||||
            return signature as Data
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public func existingPersistedAuthenticationContext(secret: Secret) -> PersistedAuthenticationContext? {
 | 
			
		||||
            nil
 | 
			
		||||
        public func existingPersistedAuthenticationContext(secret: Secret) async -> PersistedAuthenticationContext? {
 | 
			
		||||
            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.
 | 
			
		||||
@ -163,7 +171,7 @@ extension SmartCard.Store {
 | 
			
		||||
            let publicKeySecRef = SecKeyCopyPublicKey(publicKeyRef)!
 | 
			
		||||
            let publicKeyAttributes = SecKeyCopyAttributes(publicKeySecRef) as! [CFString: Any]
 | 
			
		||||
            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)
 | 
			
		||||
            guard signatureAlgorithm(for: secret) != nil else { return nil }
 | 
			
		||||
            return secret
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user