@@ -18,6 +18,14 @@ import struct MozillaAppServices.Login
1818import struct MozillaAppServices. LoginEntry
1919import protocol MozillaAppServices. KeyManager
2020
21+ private let SecClass : String ! = kSecClass as String
22+ private let SecAttrService : String ! = kSecAttrService as String
23+ private let SecAttrGeneric : String ! = kSecAttrGeneric as String
24+ private let SecAttrAccount : String ! = kSecAttrAccount as String
25+ private let SecAttrSynchronizable : String = kSecAttrSynchronizable as String
26+ private let SecValueData : String ! = kSecValueData as String
27+ private let SecAttrAccessible : String ! = kSecAttrAccessible as String
28+
2129typealias LoginsStoreError = LoginsApiError
2230public typealias LoginRecord = Login
2331
@@ -218,18 +226,83 @@ public class RustLoginEncryptionKeys {
218226 self . logger = logger
219227 }
220228
229+ func debugKeychain( _ secret: String , forKey key: String , withAccessibility accessibility: MZKeychainItemAccessibility ) {
230+ guard let secretData = secret. data ( using: . utf8) else {
231+ logger. log ( " [issam] 0 debugKeychain " ,
232+ level: . warning,
233+ category: . storage,
234+ description: " " )
235+ return
236+ }
237+
238+ var keychainQueryDictionary : [ String : Any ] = [ SecClass: kSecClassGenericPassword]
239+
240+ let serviceName = Bundle . main. bundleIdentifier ?? " SwiftKeychainWrapper "
241+
242+ logger. log ( " [issam] 1 debugKeychain " ,
243+ level: . warning,
244+ category: . storage,
245+ description: " \( serviceName) " )
246+
247+ // Uniquely identify this keychain accessor
248+ keychainQueryDictionary [ SecAttrService] = serviceName
249+
250+ // Uniquely identify the account who will be accessing the keychain
251+ let encodedIdentifier : Data ? = key. data ( using: String . Encoding. utf8)
252+
253+ keychainQueryDictionary [ SecAttrGeneric] = encodedIdentifier
254+
255+ logger. log ( " [issam] 2 debugKeychain " ,
256+ level: . warning,
257+ category: . storage,
258+ description: " " )
259+
260+ keychainQueryDictionary [ SecAttrAccount] = encodedIdentifier
261+
262+ keychainQueryDictionary [ SecAttrSynchronizable] = kCFBooleanFalse
263+
264+ let mzKeychainAttrMap : [ MZKeychainItemAccessibility : CFString ] = [
265+ . afterFirstUnlock: kSecAttrAccessibleAfterFirstUnlock,
266+ . afterFirstUnlockThisDeviceOnly: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
267+ // .always: kSecAttrAccessibleAlways,
268+ // .alwaysThisDeviceOnly: kSecAttrAccessibleAlwaysThisDeviceOnly,
269+ . whenPasscodeSetThisDeviceOnly: kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
270+ . whenUnlocked: kSecAttrAccessibleWhenUnlocked,
271+ . whenUnlockedThisDeviceOnly: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
272+ ]
273+ keychainQueryDictionary [ SecAttrAccessible] = mzKeychainAttrMap [ accessibility]
274+ keychainQueryDictionary [ SecValueData] = secretData
275+ let status = SecItemAdd ( keychainQueryDictionary as CFDictionary , nil )
276+ var errorMessage = " Unknown error ( \( status) ) "
277+ if let message = SecCopyErrorMessageString ( status, nil ) {
278+ errorMessage = message as String
279+ }
280+
281+ logger. log ( " [issam] 3 debugKeychain " ,
282+ level: . warning,
283+ category: . storage,
284+ description: " SecItemAdd status: \( status) - \( errorMessage) " )
285+ }
286+
221287 fileprivate func createAndStoreKey( ) throws -> String {
222288 do {
223289 let secret = try createKey ( )
224290 let canary = try createCanary ( text: canaryPhrase, encryptionKey: secret)
225291
226292 DispatchQueue . global ( qos: . background) . sync {
227- self . keychain. set ( secret,
228- forKey: self . loginPerFieldKeychainKey,
229- withAccessibility: MZKeychainItemAccessibility . afterFirstUnlock)
293+ debugKeychain ( secret,
294+ forKey: self . loginPerFieldKeychainKey,
295+ withAccessibility: MZKeychainItemAccessibility . afterFirstUnlock)
296+ let res = self . keychain. set ( secret,
297+ forKey: self . loginPerFieldKeychainKey,
298+ withAccessibility: MZKeychainItemAccessibility . afterFirstUnlock)
230299 self . keychain. set ( canary,
231300 forKey: self . canaryPhraseKey,
232301 withAccessibility: MZKeychainItemAccessibility . afterFirstUnlock)
302+ logger. log ( " [issam] keychain set result " ,
303+ level: . warning,
304+ category: . storage,
305+ description: " \( res) " )
233306 }
234307 return secret
235308 } catch let err as NSError {
@@ -850,128 +923,97 @@ public class RustLogins: LoginsProtocol, KeyManager {
850923 }
851924 }
852925
853- private func getKeychainData( rustKeys: RustLoginEncryptionKeys ) -> ( String ? , String ? ) {
854- var keychainData : ( String ? , String ? ) = ( nil , nil )
855-
926+ private func getKeychainData( rustKeys: RustLoginEncryptionKeys , completion: @escaping ( String ? , String ? ) -> Void ) {
856927 DispatchQueue . global ( qos: . background) . sync {
857928 let key = rustKeys. keychain. string ( forKey: rustKeys. loginPerFieldKeychainKey)
858929 let encryptedCanaryPhrase = rustKeys. keychain. string ( forKey: rustKeys. canaryPhraseKey)
859- keychainData = ( key, encryptedCanaryPhrase)
930+ completion ( key, encryptedCanaryPhrase)
860931 }
861-
862- return keychainData
863932 }
864933
865934 public func getStoredKey( completion: @escaping ( Result < String , NSError > ) -> Void ) {
866935 let rustKeys = RustLoginEncryptionKeys ( )
867- let ( key, encryptedCanaryPhrase) = getKeychainData ( rustKeys: rustKeys)
868- switch ( key, encryptedCanaryPhrase) {
869- case ( . some( key) , . some( encryptedCanaryPhrase) ) :
870- self . handleExpectedKeyAction ( rustKeys: rustKeys,
871- encryptedCanaryPhrase: encryptedCanaryPhrase,
872- key: key,
873- completion: completion)
874- case ( . some( key) , . none) :
875- self . handleUnexpectedKeyAction ( rustKeys: rustKeys, completion: completion)
876- case ( . none, . some( encryptedCanaryPhrase) ) :
877- self . handleMissingKeyAction ( rustKeys: rustKeys, completion: completion)
878- case ( . none, . none) :
879- self . handleFirstTimeCallOrClearedKeychainAction ( rustKeys: rustKeys, completion: completion)
880- default :
881- self . handleIllegalStateAction ( completion: completion)
882- }
883- }
884-
885- private func handleExpectedKeyAction( rustKeys: RustLoginEncryptionKeys ,
886- encryptedCanaryPhrase: String ? ,
887- key: String ? ,
888- completion: @escaping ( Result < String , NSError > ) -> Void ) {
889- // We expected the key to be present, and it is.
890- do {
891- let canaryIsValid = try checkCanary ( canary: encryptedCanaryPhrase!,
892- text: rustKeys. canaryPhrase,
893- encryptionKey: key!)
894- if canaryIsValid {
895- completion ( . success( key!) )
896- } else {
897- self . logger. log ( " Logins key was corrupted, new one generated " ,
936+ // swiftlint:disable:next closure_body_length
937+ getKeychainData ( rustKeys: rustKeys) { ( key, encryptedCanaryPhrase) in
938+ switch ( key, encryptedCanaryPhrase) {
939+ case ( . some( key) , . some( encryptedCanaryPhrase) ) :
940+ // We expected the key to be present, and it is.
941+ do {
942+ let canaryIsValid = try checkCanary ( canary: encryptedCanaryPhrase!,
943+ text: rustKeys. canaryPhrase,
944+ encryptionKey: key!)
945+ if canaryIsValid {
946+ completion ( . success( key!) )
947+ } else {
948+ self . logger. log ( " Logins key was corrupted, new one generated " ,
949+ level: . warning,
950+ category: . storage)
951+ GleanMetrics . LoginsStoreKeyRegeneration. corrupt. record ( )
952+ self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
953+ }
954+ } catch let error as NSError {
955+ self . logger. log ( " Error validating logins encryption key " ,
956+ level: . warning,
957+ category: . storage,
958+ description: error. localizedDescription)
959+ completion ( . failure( error) )
960+ }
961+ case ( . some( key) , . none) :
962+ // The key is present, but we didn't expect it to be there.
963+
964+ self . logger. log ( " Logins key lost due to storage malfunction, new one generated " ,
898965 level: . warning,
899966 category: . storage)
900- GleanMetrics . LoginsStoreKeyRegeneration. corrupt . record ( )
967+ GleanMetrics . LoginsStoreKeyRegeneration. other . record ( )
901968 self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
902- }
903- } catch let error as NSError {
904- self . logger. log ( " Error validating logins encryption key " ,
905- level: . warning,
906- category: . storage,
907- description: error. localizedDescription)
908- completion ( . failure( error) )
909- }
910- }
911-
912- private func handleUnexpectedKeyAction( rustKeys: RustLoginEncryptionKeys ,
913- completion: @escaping ( Result < String , NSError > ) -> Void ) {
914- // The key is present, but we didn't expect it to be there.
915-
916- self . logger. log ( " Logins key lost due to storage malfunction, new one generated " ,
917- level: . warning,
918- category: . storage)
919- GleanMetrics . LoginsStoreKeyRegeneration. other. record ( )
920- self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
921- }
922-
923- private func handleMissingKeyAction( rustKeys: RustLoginEncryptionKeys ,
924- completion: @escaping ( Result < String , NSError > ) -> Void ) {
925- // We expected the key to be present, but it's gone missing on us.
969+ case ( . none, . some( encryptedCanaryPhrase) ) :
970+ // We expected the key to be present, but it's gone missing on us.
926971
927- self . logger. log ( " Logins key lost, new one generated " ,
928- level: . warning,
929- category: . storage)
930- GleanMetrics . LoginsStoreKeyRegeneration. lost. record ( )
931- self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
932- }
933-
934- private func handleFirstTimeCallOrClearedKeychainAction( rustKeys: RustLoginEncryptionKeys ,
935- completion: @escaping ( Result < String , NSError > ) -> Void ) {
936- // We didn't expect the key to be present, which either means this is a first-time
937- // call or the key data has been cleared from the keychain.
972+ self . logger. log ( " Logins key lost, new one generated " ,
973+ level: . warning,
974+ category: . storage)
975+ GleanMetrics . LoginsStoreKeyRegeneration. lost. record ( )
976+ self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
977+ case ( . none, . none) :
978+ // We didn't expect the key to be present, which either means this is a first-time
979+ // call or the key data has been cleared from the keychain.
938980
939- self . hasSyncedLogins ( ) . upon { result in
940- guard result. failureValue == nil else {
941- completion ( . failure( result. failureValue! as NSError ) )
942- return
943- }
981+ self . hasSyncedLogins ( ) . upon { result in
982+ guard result. failureValue == nil else {
983+ completion ( . failure( result. failureValue! as NSError ) )
984+ return
985+ }
944986
945- guard let hasLogins = result. successValue else {
946- let msg = " Failed to verify logins count before attempting to reset key "
947- completion ( . failure( LoginEncryptionKeyError . dbRecordCountVerificationError ( msg) as NSError ) )
948- return
949- }
987+ guard let hasLogins = result. successValue else {
988+ let msg = " Failed to verify logins count before attempting to reset key "
989+ completion ( . failure( LoginEncryptionKeyError . dbRecordCountVerificationError ( msg) as NSError ) )
990+ return
991+ }
950992
951- if hasLogins {
952- // Since the key data isn't present and we have login records in
953- // the database, we both clear the database and reset the key.
954- GleanMetrics . LoginsStoreKeyRegeneration. keychainDataLost. record ( )
955- self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
956- } else {
957- // There are no records in the database so we don't need to wipe any
958- // existing login records. We just need to create a new key.
959- do {
960- let key = try rustKeys. createAndStoreKey ( )
961- completion ( . success( key) )
962- } catch let error as NSError {
963- completion ( . failure( error) )
993+ if hasLogins {
994+ // Since the key data isn't present and we have login records in
995+ // the database, we both clear the database and reset the key.
996+ GleanMetrics . LoginsStoreKeyRegeneration. keychainDataLost. record ( )
997+ self . resetLoginsAndKey ( rustKeys: rustKeys, completion: completion)
998+ } else {
999+ // There are no records in the database so we don't need to wipe any
1000+ // existing login records. We just need to create a new key.
1001+ do {
1002+ let key = try rustKeys. createAndStoreKey ( )
1003+ completion ( . success( key) )
1004+ } catch let error as NSError {
1005+ completion ( . failure( error) )
1006+ }
1007+ }
9641008 }
1009+ default :
1010+ // If none of the above cases apply, we're in a state that shouldn't be
1011+ // possible but is disallowed nonetheless
1012+ completion ( . failure( LoginEncryptionKeyError . illegalState as NSError ) )
9651013 }
9661014 }
9671015 }
9681016
969- private func handleIllegalStateAction( completion: @escaping ( Result < String , NSError > ) -> Void ) {
970- // If none of the above cases apply, we're in a state that shouldn't be
971- // possible but is disallowed nonetheless
972- completion ( . failure( LoginEncryptionKeyError . illegalState as NSError ) )
973- }
974-
9751017 // MARK: - KeyManager
9761018
9771019 /**
@@ -998,12 +1040,18 @@ public class RustLogins: LoginsProtocol, KeyManager {
9981040 */
9991041 public func getKey( ) throws -> Data {
10001042 let rustKeys = RustLoginEncryptionKeys ( )
1001- let ( key, _) = getKeychainData ( rustKeys: rustKeys)
10021043
1003- guard let keyData = key? . data ( using: . utf8) else {
1044+ guard let keyData = rustKeys. keychain. data ( forKey: rustKeys. loginPerFieldKeychainKey) else {
1045+ logger. log ( " [issam] getKey 1 result " ,
1046+ level: . warning,
1047+ category: . storage,
1048+ description: " " )
10041049 throw LoginsStoreError . MissingKey
10051050 }
1006-
1051+ logger. log ( " [issam] getKey 2 result " ,
1052+ level: . warning,
1053+ category: . storage,
1054+ description: " \( keyData. count) " )
10071055 return keyData
10081056 }
10091057}
0 commit comments