From 9957ab3e40a0a56afce44260d647ea038d1e2638 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Fri, 12 Jan 2024 12:46:44 -0800 Subject: [PATCH] Auth contexts --- .../SecureEnclaveStore.swift | 16 +-- .../SmartCardSecretKit/SmartCardStore.swift | 14 +-- Sources/Secretive/Localizable.xcstrings | 117 ++++++++++++++++++ 3 files changed, 132 insertions(+), 15 deletions(-) diff --git a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift index 3cb92dd..d079bf3 100644 --- a/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift +++ b/Sources/Packages/Sources/SecureEnclaveSecretKit/SecureEnclaveStore.swift @@ -107,10 +107,10 @@ extension SecureEnclave { context = existing.context } else { let newContext = LAContext() - newContext.localizedCancelTitle = "Deny" + newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button") context = newContext } - context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\"" + context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, @@ -140,8 +140,8 @@ extension SecureEnclave { public func verify(signature: Data, for data: Data, with secret: Secret) throws -> Bool { let context = LAContext() - context.localizedReason = "verify a signature using secret \"\(secret.name)\"" - context.localizedCancelTitle = "Deny" + context.localizedReason = String(localized: "auth_context_request_verify_description_\(secret.name)") + context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, @@ -181,16 +181,16 @@ extension SecureEnclave { public func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) throws { let newContext = LAContext() newContext.touchIDAuthenticationAllowableReuseDuration = duration - newContext.localizedCancelTitle = "Deny" + newContext.localizedCancelTitle = String(localized: "auth_context_request_deny_button") let formatter = DateComponentsFormatter() formatter.unitsStyle = .spellOut formatter.allowedUnits = [.hour, .minute, .day] if let durationString = formatter.string(from: duration) { - newContext.localizedReason = "unlock secret \"\(secret.name)\" for \(durationString)" + newContext.localizedReason = String(localized: "auth_context_persist_for_duration_\(secret.name)_\(durationString)") } else { - newContext.localizedReason = "unlock secret \"\(secret.name)\"" + newContext.localizedReason = String(localized: "auth_context_persist_for_duration_unknown_\(secret.name)") } newContext.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: newContext.localizedReason) { [weak self] success, _ in guard success else { return } @@ -260,7 +260,7 @@ extension SecureEnclave.Store { nil)! let wrapped: [SecureEnclave.Secret] = publicTyped.map { - let name = $0[kSecAttrLabel] as? String ?? "Unnamed" + let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret") let id = $0[kSecAttrApplicationLabel] as! Data let publicKeyRef = $0[kSecValueRef] as! SecKey let publicKeyAttributes = SecKeyCopyAttributes(publicKeyRef) as! [CFString: Any] diff --git a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift index 166a513..f6f83c8 100644 --- a/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift +++ b/Sources/Packages/Sources/SmartCardSecretKit/SmartCardStore.swift @@ -50,8 +50,8 @@ extension SmartCard { public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data { guard let tokenID = tokenID else { fatalError() } let context = LAContext() - context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\"" - context.localizedCancelTitle = "Deny" + context.localizedReason = String(localized: "auth_context_request_signature_description_\(provenance.origin.displayName)_\(secret.name)") + context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, @@ -156,7 +156,7 @@ extension SmartCard.Store { SecItemCopyMatching(attributes, &untyped) guard let typed = untyped as? [[CFString: Any]] else { return } let wrapped = typed.map { - let name = $0[kSecAttrLabel] as? String ?? "Unnamed" + let name = $0[kSecAttrLabel] as? String ?? String(localized: "unnamed_secret") let tokenID = $0[kSecAttrApplicationLabel] as! Data let algorithm = Algorithm(secAttr: $0[kSecAttrKeyType] as! NSNumber) let keySize = $0[kSecAttrKeySizeInBits] as! Int @@ -183,8 +183,8 @@ extension SmartCard.Store { /// - 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 = "encrypt data using secret \"\(secret.name)\"" - context.localizedCancelTitle = "Deny" + context.localizedReason = String(localized: "auth_context_request_encrypt_description_\(secret.name)") + context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") let attributes = KeychainDictionary([ kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeySizeInBits: secret.keySize, @@ -212,8 +212,8 @@ extension SmartCard.Store { public func decrypt(data: Data, with secret: SecretType) throws -> Data { guard let tokenID = tokenID else { fatalError() } let context = LAContext() - context.localizedReason = "decrypt data using secret \"\(secret.name)\"" - context.localizedCancelTitle = "Deny" + context.localizedReason = String(localized: "auth_context_request_decrypt_description_\(secret.name)") + context.localizedCancelTitle = String(localized: "auth_context_request_deny_button") let attributes = KeychainDictionary([ kSecClass: kSecClassKey, kSecAttrKeyClass: kSecAttrKeyClassPrivate, diff --git a/Sources/Secretive/Localizable.xcstrings b/Sources/Secretive/Localizable.xcstrings index 2ce6caf..1ecd3b0 100644 --- a/Sources/Secretive/Localizable.xcstrings +++ b/Sources/Secretive/Localizable.xcstrings @@ -167,6 +167,90 @@ } } }, + "auth_context_persist_for_duration_%@_%@" : { + "comment" : "When the user clicks the notification to leave a secret unlocked, they are shown a prompt to approve the action. This is the description, showing which secret will used. The first placeholder is the name of the secret. The second placeholder is a localized description of the time period it will remain unlocked for (eg: \"five minutes\")", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "unlock secret \"%1$@\" for %2$@" + } + } + } + }, + "auth_context_persist_for_duration_unknown_%@" : { + "comment" : "When the user clicks the notification to leave a secret unlocked, they are shown a prompt to approve the action. This is the description, showing which secret will used. The placeholder is the name of the secret. This is a fallback used when a duration is unable to be specified.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "unlock secret \"%1$@\"" + } + } + } + }, + "auth_context_request_decrypt_description_%@" : { + "comment" : "When the user performs a decryption action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used. The placeholder is the name of the secret. NOTE: This is currently not exposed in UI.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "decrypt data using secret \"%1$@\"" + } + } + } + }, + "auth_context_request_deny_button" : { + "comment" : "When the user chooses to perform an action that requires Touch ID/password authentication, they are shown a prompt to approve the action. This is the deny button for that prompt.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deny" + } + } + } + }, + "auth_context_request_encrypt_description_%@" : { + "comment" : "When the user performs an encryption action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used. The placeholder is the name of the secret. NOTE: This is currently not exposed in UI.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "encrypt data using secret \"%1$@\"" + } + } + } + }, + "auth_context_request_signature_description_%@_%@" : { + "comment" : "When the user performs a signature action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used, and where the request is coming from. The first placeholder is the name of the app requesting the operation. The second placeholder is the name of the secret.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "sign a request from \"%1$@\" using secret \"%2$@\"" + } + } + } + }, + "auth_context_request_verify_description_%@" : { + "comment" : "When the user performs a signature verification action using a secret, they are shown a prompt to approve the action. This is the description, showing which secret will be used. The placeholder is the name of the secret. NOTE: This is currently not exposed in UI.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "verify a signature using secret \"%1$@\"" + } + } + } + }, "copyable_click_to_copy_button" : { "localizations" : { "en" : { @@ -747,6 +831,17 @@ } } }, + "secure_enclave" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Secure Enclave" + } + } + } + }, "setup_agent_activity_monitor_description" : { "localizations" : { "en" : { @@ -1023,6 +1118,28 @@ } } }, + "smart_card" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Smart Card" + } + } + } + }, + "unnamed_secret" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unnamed" + } + } + } + }, "update_critical_notice_title" : { "localizations" : { "en" : {