Skip to content

Commit 55dde86

Browse files
authoredMar 14, 2025
Refactor FXIOS-11628 autofill keychain calls (#25152)
Replaced credit cards MZKeychainWrapper calls with RustKeychain calls
1 parent 996d0f7 commit 55dde86

File tree

7 files changed

+352
-60
lines changed

7 files changed

+352
-60
lines changed
 

‎firefox-ios/Client/Frontend/Settings/Main/Debug/FeatureFlags/FeatureFlagsDebugViewController.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ final class FeatureFlagsDebugViewController: SettingsTableViewController, Featur
146146
FeatureFlagsBoolSetting(
147147
with: .useRustKeychain,
148148
titleText: format(string: "Enable Rust Keychain"),
149-
statusText: format(string: "Toggle to enable Rust Components rust keychain")
149+
statusText: format(string: "Toggle to enable rust keychain")
150150
) { [weak self] _ in
151151
self?.reloadView()
152152
},

‎firefox-ios/Providers/Profile.swift

+20-11
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ open class BrowserProfile: Profile {
482482
isDirectory: true
483483
).appendingPathComponent("autofill.db").path
484484

485-
lazy var autofill = RustAutofill(databasePath: autofillDbPath)
485+
lazy var autofill = RustAutofill(databasePath: autofillDbPath, rustKeychainEnabled: rustKeychainEnabled)
486486

487487
#if !MOZ_TARGET_NOTIFICATIONSERVICE && !MOZ_TARGET_SHARETO && !MOZ_TARGET_CREDENTIAL_PROVIDER
488488
lazy var searchEnginesManager: SearchEnginesManager = {
@@ -768,38 +768,47 @@ open class BrowserProfile: Profile {
768768
let rustLoginsKeys = RustLoginEncryptionKeys()
769769
let rustAutofillKey = RustAutofillEncryptionKeys()
770770
var loginsKey: String?
771-
let creditCardKey = legacyKeychain.string(forKey: rustAutofillKey.ccKeychainKey)
771+
var loginsCanary: String?
772+
773+
var creditCardKey: String?
774+
var creditCardCanary: String?
772775

773776
if rustKeychainEnabled {
774-
(loginsKey, _) = keychain.getLoginsKeyData()
777+
(creditCardKey, creditCardCanary) = keychain.getCreditCardKeyData()
778+
(loginsKey, loginsCanary) = keychain.getLoginsKeyData()
775779

776780
// Remove all items, removal is not key-by-key specific (due to the risk of failing to delete something),
777781
// simply restore what is needed.
778782
keychain.removeAllKeys()
779783

780-
if let loginsKey = loginsKey {
781-
keychain.setLoginsKey(loginsKey)
784+
if let creditCardKey = creditCardKey, let creditCardCanary = creditCardCanary {
785+
keychain.setCreditCardsKeyData(keyValue: creditCardKey, canaryValue: creditCardCanary)
786+
}
787+
788+
if let loginsKey = loginsKey, let loginsCanary = loginsCanary {
789+
keychain.setLoginsKeyData(keyValue: loginsKey, canaryValue: loginsCanary)
782790
}
783791
} else {
792+
creditCardKey = legacyKeychain.string(forKey: rustAutofillKey.ccKeychainKey)
784793
loginsKey = legacyKeychain.string(forKey: rustLoginsKeys.loginPerFieldKeychainKey)
785794

786795
// Remove all items, removal is not key-by-key specific (due to the risk of failing to delete something),
787796
// simply restore what is needed.
788797
legacyKeychain.removeAllKeys()
789798

799+
if let creditCardKey = creditCardKey {
800+
legacyKeychain.set(creditCardKey,
801+
forKey: rustAutofillKey.ccKeychainKey,
802+
withAccessibility: .afterFirstUnlock)
803+
}
804+
790805
if let loginsKey = loginsKey {
791806
legacyKeychain.set(loginsKey,
792807
forKey: rustLoginsKeys.loginPerFieldKeychainKey,
793808
withAccessibility: .afterFirstUnlock)
794809
}
795810
}
796811

797-
if let creditCardKey = creditCardKey {
798-
legacyKeychain.set(creditCardKey,
799-
forKey: rustAutofillKey.ccKeychainKey,
800-
withAccessibility: .afterFirstUnlock)
801-
}
802-
803812
// Tell any observers that our account has changed.
804813
NotificationCenter.default.post(name: .FirefoxAccountChanged, object: nil)
805814

‎firefox-ios/Storage/MockRustKeychain.swift

+12-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class MockRustKeychain: RustKeychain {
1313
private init() {
1414
super.init(serviceName: "Test")
1515
}
16-
1716
override public func removeObject(key: String) {
1817
_ = storage.removeValue(forKey: key)
1918
}
@@ -22,14 +21,24 @@ class MockRustKeychain: RustKeychain {
2221
storage.removeAll()
2322
}
2423

25-
override public func setLoginsKey(_ value: String) {
26-
storage[loginsKeyIdentifier] = value
24+
override public func setLoginsKeyData(keyValue: String, canaryValue: String) {
25+
storage[loginsKeyIdentifier] = keyValue
26+
storage[loginsCanaryKeyIdentifier] = canaryValue
27+
}
28+
29+
override public func setCreditCardsKeyData(keyValue: String, canaryValue: String) {
30+
storage[creditCardKeyIdentifier] = keyValue
31+
storage[creditCardCanaryKeyIdentifier] = canaryValue
2732
}
2833

2934
override public func getLoginsKeyData() -> (String?, String?) {
3035
return (storage[loginsKeyIdentifier], storage[loginsCanaryKeyIdentifier])
3136
}
3237

38+
override public func getCreditCardKeyData() -> (String?, String?) {
39+
return (storage[creditCardKeyIdentifier], storage[creditCardCanaryKeyIdentifier])
40+
}
41+
3342
override public class func wipeKeychain() {}
3443

3544
override func createLoginsKeyData() throws -> String {

‎firefox-ios/Storage/Rust/RustAutofill.swift

+22-7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class RustAutofill {
3434

3535
private(set) var isOpen = false
3636
private var didAttemptToMoveToBackup = false
37+
private var rustKeychainEnabled = false
3738
private let logger: Logger
3839

3940
// MARK: - Initialization
@@ -43,10 +44,13 @@ public class RustAutofill {
4344
/// - Parameters:
4445
/// - databasePath: The path to the Autofill database file.
4546
/// - logger: An optional logger for recording informational and error messages. Default is shared DefaultLogger.
46-
public init(databasePath: String, logger: Logger = DefaultLogger.shared) {
47+
public init(databasePath: String,
48+
logger: Logger = DefaultLogger.shared,
49+
rustKeychainEnabled: Bool = false) {
4750
self.databasePath = databasePath
4851
queue = DispatchQueue(label: "RustAutofill queue: \(databasePath)")
4952
self.logger = logger
53+
self.rustKeychainEnabled = rustKeychainEnabled
5054
}
5155

5256
// MARK: - Database Operations
@@ -140,7 +144,7 @@ public class RustAutofill {
140144
return nil
141145
}
142146
let keys = RustAutofillEncryptionKeys()
143-
return keys.decryptCreditCardNum(encryptedCCNum: encryptedCCNum)
147+
return keys.decryptCreditCardNum(encryptedCCNum: encryptedCCNum, rustKeychainEnabled: rustKeychainEnabled)
144148
}
145149

146150
/// Retrieves a credit card from the database by its identifier.
@@ -519,7 +523,7 @@ public class RustAutofill {
519523
// There are no records in the database so we don't need to scrub any
520524
// existing credit card records. We just need to create a new key.
521525
do {
522-
let key = try rustKeys.createAndStoreKey()
526+
let key = try self.createKey(rustKeys: rustKeys)
523527
completion(.success(key))
524528
} catch let error as NSError {
525529
completion(.failure(error))
@@ -570,9 +574,14 @@ public class RustAutofill {
570574
private func getKeychainData(rustKeys: RustAutofillEncryptionKeys,
571575
completion: @escaping (String?, String?) -> Void) {
572576
DispatchQueue.global(qos: .background).sync {
573-
let key = rustKeys.keychain.string(forKey: rustKeys.ccKeychainKey)
574-
let encryptedCanaryPhrase = rustKeys.keychain.string(forKey: rustKeys.ccCanaryPhraseKey)
575-
completion(key, encryptedCanaryPhrase)
577+
if rustKeychainEnabled {
578+
let (key, encryptedCanaryPhrase) = rustKeys.keychain.getCreditCardKeyData()
579+
completion(key, encryptedCanaryPhrase)
580+
} else {
581+
let key = rustKeys.legacyKeychain.string(forKey: rustKeys.ccKeychainKey)
582+
let encryptedCanaryPhrase = rustKeys.legacyKeychain.string(forKey: rustKeys.ccCanaryPhraseKey)
583+
completion(key, encryptedCanaryPhrase)
584+
}
576585
}
577586
}
578587

@@ -582,7 +591,7 @@ public class RustAutofill {
582591
switch result {
583592
case .success(()):
584593
do {
585-
let key = try rustKeys.createAndStoreKey()
594+
let key = try self.createKey(rustKeys: rustKeys)
586595
completion(.success(key))
587596
} catch let error as NSError {
588597
self.logger.log("Error creating credit card encryption key",
@@ -597,6 +606,12 @@ public class RustAutofill {
597606
}
598607
}
599608

609+
private func createKey(rustKeys: RustAutofillEncryptionKeys) throws -> String {
610+
return self.rustKeychainEnabled ?
611+
try rustKeys.keychain.createCreditCardsKeyData() :
612+
try rustKeys.createAndStoreKey()
613+
}
614+
600615
private func hasCreditCards(completion: @escaping (Result<Bool, Error>) -> Void) {
601616
return listCreditCards { (creditCards, err) in
602617
guard err == nil else {

‎firefox-ios/Storage/Rust/RustAutofillEncryptionKeys.swift

+24-36
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public extension AutofillApiError {
2727
public class RustAutofillEncryptionKeys {
2828
public let ccKeychainKey = "appservices.key.creditcard.perfield"
2929

30-
let keychain = MZKeychainWrapper.sharedClientAppContainerKeychain
30+
let legacyKeychain = MZKeychainWrapper.sharedClientAppContainerKeychain
31+
let keychain = RustKeychain.sharedClientAppContainerKeychain
3132
let ccCanaryPhraseKey = "creditCardCanaryPhrase"
3233
let canaryPhrase = "a string for checking validity of the key"
3334

@@ -43,20 +44,20 @@ public class RustAutofillEncryptionKeys {
4344
let canary = try self.createCanary(text: canaryPhrase, key: secret)
4445

4546
DispatchQueue.global(qos: .background).sync {
46-
keychain.set(secret,
47-
forKey: ccKeychainKey,
48-
withAccessibility: MZKeychainItemAccessibility.afterFirstUnlock)
49-
keychain.set(canary,
50-
forKey: ccCanaryPhraseKey,
51-
withAccessibility: MZKeychainItemAccessibility.afterFirstUnlock)
47+
legacyKeychain.set(secret,
48+
forKey: ccKeychainKey,
49+
withAccessibility: MZKeychainItemAccessibility.afterFirstUnlock)
50+
legacyKeychain.set(canary,
51+
forKey: ccCanaryPhraseKey,
52+
withAccessibility: MZKeychainItemAccessibility.afterFirstUnlock)
5253
}
5354

5455
return secret
5556
} catch let err as NSError {
5657
if let autofillStoreError = err as? AutofillApiError {
57-
logAutofillStoreError(err: autofillStoreError,
58-
errorDomain: err.domain,
59-
errorMessage: "Error while creating and storing credit card key")
58+
keychain.logAutofillStoreError(err: autofillStoreError,
59+
errorDomain: err.domain,
60+
errorMessage: "Error while creating and storing credit card key")
6061

6162
throw AutofillEncryptionKeyError.noKeyCreated
6263
} else {
@@ -70,16 +71,24 @@ public class RustAutofillEncryptionKeys {
7071
}
7172
}
7273

73-
func decryptCreditCardNum(encryptedCCNum: String) -> String? {
74-
guard let key = self.keychain.string(forKey: self.ccKeychainKey) else { return nil }
74+
func decryptCreditCardNum(encryptedCCNum: String, rustKeychainEnabled: Bool) -> String? {
75+
var keyValue: String?
76+
77+
if rustKeychainEnabled {
78+
(keyValue, _) = keychain.getCreditCardKeyData()
79+
} else {
80+
keyValue = legacyKeychain.string(forKey: self.ccKeychainKey)
81+
}
82+
83+
guard let key = keyValue else { return nil }
7584

7685
do {
7786
return try decryptString(key: key, ciphertext: encryptedCCNum)
7887
} catch let err as NSError {
7988
if let autofillStoreError = err as? AutofillApiError {
80-
logAutofillStoreError(err: autofillStoreError,
81-
errorDomain: err.domain,
82-
errorMessage: "Error while decrypting credit card")
89+
keychain.logAutofillStoreError(err: autofillStoreError,
90+
errorDomain: err.domain,
91+
errorMessage: "Error while decrypting credit card")
8392
} else {
8493
logger.log("Unknown error while decrypting credit card",
8594
level: .warning,
@@ -100,25 +109,4 @@ public class RustAutofillEncryptionKeys {
100109
key: String) throws -> String {
101110
return try encryptString(key: key, cleartext: text)
102111
}
103-
104-
private func logAutofillStoreError(err: AutofillApiError,
105-
errorDomain: String,
106-
errorMessage: String) {
107-
var message: String {
108-
switch err {
109-
case .SqlError(let message),
110-
.CryptoError(let message),
111-
.NoSuchRecord(let message),
112-
.UnexpectedAutofillApiError(let message):
113-
return message
114-
case .InterruptedError:
115-
return "Interrupted Error"
116-
}
117-
}
118-
119-
logger.log(errorMessage,
120-
level: .warning,
121-
category: .storage,
122-
description: "\(errorDomain) - \(err.descriptionValue): \(message)")
123-
}
124112
}

‎firefox-ios/Storage/RustKeychain.swift

+37-2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ open class RustKeychain {
4545
public let loginsCanaryKeyIdentifier = "canaryPhrase"
4646
let loginsCanaryPhrase = "a string for checking validity of the key"
4747

48+
public let creditCardKeyIdentifier = "appservices.key.creditcard.perfield"
49+
public let creditCardCanaryKeyIdentifier = "creditCardCanaryPhrase"
50+
let creditCardCanaryPhrase = "a string for checking validity of the key"
51+
4852
public static var sharedClientAppContainerKeychain: RustKeychain {
4953
let baseBundleIdentifier = AppInfo.baseBundleIdentifier
5054

@@ -85,14 +89,25 @@ open class RustKeychain {
8589
logErrorFromStatus(status, errMsg: "Failed to remove all keys")
8690
}
8791

88-
public func setLoginsKey(_ value: String) {
89-
addOrUpdateKeychainKey(value, key: loginsKeyIdentifier)
92+
public func setLoginsKeyData(keyValue: String, canaryValue: String) {
93+
addOrUpdateKeychainKey(keyValue, key: loginsKeyIdentifier)
94+
addOrUpdateKeychainKey(canaryValue, key: loginsCanaryKeyIdentifier)
95+
}
96+
97+
public func setCreditCardsKeyData(keyValue: String, canaryValue: String) {
98+
addOrUpdateKeychainKey(keyValue, key: creditCardKeyIdentifier)
99+
addOrUpdateKeychainKey(canaryValue, key: creditCardCanaryKeyIdentifier)
90100
}
91101

92102
public func getLoginsKeyData() -> (String?, String?) {
93103
return getEncryptionKeyData(keyIdentifier: loginsKeyIdentifier, canaryKeyIdentifier: loginsCanaryKeyIdentifier)
94104
}
95105

106+
public func getCreditCardKeyData() -> (String?, String?) {
107+
return getEncryptionKeyData(keyIdentifier: creditCardKeyIdentifier,
108+
canaryKeyIdentifier: creditCardCanaryKeyIdentifier)
109+
}
110+
96111
public class func wipeKeychain() {
97112
deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items
98113
deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items
@@ -120,6 +135,26 @@ open class RustKeychain {
120135
description: "\(errorDomain) - \(err.descriptionValue): \(message)")
121136
}
122137

138+
func createCreditCardsKeyData() throws -> String {
139+
do {
140+
return try createAndStoreKey(canaryPhrase: creditCardCanaryPhrase,
141+
canaryIdentifier: creditCardCanaryKeyIdentifier,
142+
keyIdentifier: creditCardKeyIdentifier)
143+
} catch let err as NSError {
144+
if let autofillStoreError = err as? AutofillApiError {
145+
logAutofillStoreError(err: autofillStoreError,
146+
errorDomain: err.domain,
147+
errorMessage: "Error while creating and storing credit card key")
148+
} else {
149+
logger.log("Unknown error while creating and storing credit card key",
150+
level: .warning,
151+
category: .storage,
152+
description: err.localizedDescription)
153+
}
154+
}
155+
throw LoginEncryptionKeyError.noKeyCreated
156+
}
157+
123158
func createLoginsKeyData() throws -> String {
124159
do {
125160
return try createAndStoreKey(canaryPhrase: loginsCanaryPhrase,

0 commit comments

Comments
 (0)
Failed to load comments.