29 votes

Enregistrer les données dans le trousseau uniquement accessible avec Touch ID dans Swift 3

Je suis en train de travailler sur une paix de code qui devrait prendre les mesures suivantes:

  • Stocker des données dans le Trousseau d'accès.
  • Obtenir les données uniquement si un utilisateur s'authentifie avec Touch ID ou mot de Passe.

J'ai regardé le Trousseau et l'Authentification avec Touch ID présentation et compris les suivantes:

Si vous définissez le paramètre correct lors de l'ajout d'une nouvelle valeur dans le Trousseau, la prochaine fois vous essayez de sortir, le système affiche automatiquement la Touch ID popup.

J'ai écrit du code, et mon hypothèse ne fonctionne pas. C'est ce que j'ai écrit:

    //
    //  Secret value to store
    //
    let valueData = "The Top Secret Message V1".data(using: .utf8)!;

    //
    //  Create the Access Controll object telling how the new value
    //  should be stored. Force Touch ID by the system on Read.
    //
    let sacObject =
        SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                            kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                            .userPresence,
                            nil);

    //
    //  Create the Key Value array, that holds the query to store 
    //  our data
    //
    let insert_query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccessControl: sacObject!,
        kSecValueData: valueData,
        kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
        //  This two valuse ideifieis the entry, together they become the
        //  primary key in the Database
        kSecAttrService: "app_name",
        kSecAttrAccount: "first_name"
    ];

    //
    //  Execute the query to add our data to Keychain
    //
    let resultCode = SecItemAdd(insert_query as CFDictionary, nil);

Au début, je pensais que l'émulateur avait quelques problèmes, mais non, j'ai été en mesure de vérifier si le Touch ID est présent ou pas avec le code suivant:

    //
    //  Check if the device the code is running on is capapble of 
    //  finger printing.
    //
    let dose_it_can = LAContext()
        .canEvaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics, error: nil);

    if(dose_it_can)
    {
        print("Yes it can");
    }
    else
    {
        print("No it can't");
    }

Et j'ai aussi été en mesure par programme afficher le code de Touche popup avec le code suivant:

    //
    //  Show the Touch ID dialog to check if we can get a print from 
    //  the user
    //
    LAContext().evaluatePolicy(
        LAPolicy.deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Such important reason ;)",
        reply: {
            (status: Bool, evaluationError: Error?) -> Void in

            if(status)
            {
                print("OK");
            }
            else
            {
                print("Not OK");
            }

    });

Pour résumer le tout

Touch ID fonctionne, mais l'enregistrement d'une valeur de Keychain avec le drapeau de force Touch ID par le système lui-même ne fonctionne pas - ce qui me manque?

Pommes exemple

L'exemple d'Apple appelé KeychainTouchID: l'Utilisation de Touch ID avec Trousseau et LocalAuthentication montre également incompatible résultat et Touch ID n'est pas appliquée par le système.

Tech spec

  • Xcode 8.1
  • Swift 3

23voto

Martin R Points 105727

Le Touch ID popup s'affiche uniquement si vous appelez SecItemCopyMatching() sur un fond de la file d'attente. Ceci est indiqué à la page 118 de la PDF de la présentation de Trousseau et l'Authentification avec Touch ID:

La lecture d'un Secret
...

dispatch_async(dispatch_get_global_queue(...), ^(void){
    CFTypeRef dataTypeRef = NULL;
    OSStatus status = SecItemCopyMatching((CFDictionaryRef)query,
                                     &dataTypeRef);
});

Sinon, vous êtes bloquer le thread principal et le popup n'est pas apparaissent. SecItemCopyMatching() puis échoue (après un délai d'attente) avec code d'erreur -25293 = errSecAuthFailed.

L'échec n'est pas immédiatement évident dans votre exemple de projet parce que il imprime la mauvaise variable dans l'erreur, par exemple

if(status != noErr)
{
    print("SELECT Error: \(resultCode)."); // <-- Should be `status`
}

et de même pour la mise à jour et de suppression.

Ici se compose de la version de votre exemple de code avec le nécessaire envoi au fond de la file d'attente pour récupérer le trousseau de clés de l'élément. (Bien sûr, l'INTERFACE utilisateur les mises à jour doivent être envoyés à la file d'attente.)

Il a travaillé comme prévu dans mon test sur un iPhone avec Touch ID: le Touch ID popup s'affiche, et le trousseau article est seulement récupéré après une authentification réussie.

Touch ID d'authentification n'a pas de travail sur le Simulateur iOS.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    //  This two values identify the entry, together they become the
    //  primary key in the database
    let myAttrService = "app_name"
    let myAttrAccount = "first_name"

    // DELETE keychain item (if present from previous run)

    let delete_query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrService: myAttrService,
        kSecAttrAccount: myAttrAccount,
        kSecReturnData: false
    ]
    let delete_status = SecItemDelete(delete_query)
    if delete_status == errSecSuccess {
        print("Deleted successfully.")
    } else if delete_status == errSecItemNotFound {
        print("Nothing to delete.")
    } else {
        print("DELETE Error: \(delete_status).")
    }

    // INSERT keychain item

    let valueData = "The Top Secret Message V1".data(using: .utf8)!
    let sacObject =
        SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                                        .userPresence,
                                        nil)!

    let insert_query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccessControl: sacObject,
        kSecValueData: valueData,
        kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
        kSecAttrService: myAttrService,
        kSecAttrAccount: myAttrAccount
    ]
    let insert_status = SecItemAdd(insert_query as CFDictionary, nil)
    if insert_status == errSecSuccess {
        print("Inserted successfully.")
    } else {
        print("INSERT Error: \(insert_status).")
    }

    DispatchQueue.global().async {
        // RETRIEVE keychain item

        let select_query: NSDictionary = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: myAttrService,
            kSecAttrAccount: myAttrAccount,
            kSecReturnData: true,
            kSecUseOperationPrompt: "Authenticate to access secret message"
        ]
        var extractedData: CFTypeRef?
        let select_status = SecItemCopyMatching(select_query, &extractedData)
        if select_status == errSecSuccess {
            if let retrievedData = extractedData as? Data,
                let secretMessage = String(data: retrievedData, encoding: .utf8) {

                print("Secret message: \(secretMessage)")

                // UI updates must be dispatched back to the main thread.

                DispatchQueue.main.async {
                    self.messageLabel.text = secretMessage
                }

            } else {
                print("Invalid data")
            }
        } else if select_status == errSecUserCanceled {
            print("User canceled the operation.")
        } else {
            print("SELECT Error: \(select_status).")
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X