web-dev-qa-db-ja.com

Swift 3のTouch IDでのみアクセス可能なキーチェーンにデータを保存します

私は次のことを行うべきコードの平和に取り組んでいます:

  • キーチェーンにデータを保存します。
  • ユーザーがTouch IDまたはPass Codeで認証された場合にのみデータを取得します。

タッチIDを使用したキーチェーンと認証 のプレゼンテーションを見て、次のことを理解しました。

キーチェーンに新しい値を追加するときに正しいパラメーターを設定すると、次に値を取得しようとすると、システムは自動的にTouch IDポップアップを表示します。

私はいくつかのコードを書きましたが、私の仮定はうまくいきません。これは私が書いたものです:

    //
    //  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);

最初は、エミュレータに問題があると思っていましたが、いいえ、次のコードでTouch IDが存在するかどうかを確認できました。

    //
    //  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");
    }

また、次のコードを使用して、Touch IDポップアップをプログラムで表示することもできました。

    //
    //  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");
            }

    });

まとめると

Touch IDは機能しますが、システム自体によってTouch IDを強制するフラグを使用してキーチェーンに値を保存しても機能しません-何が欠けていますか?

りんごの例

Appleが提供する KeychainTouchID:KeychainおよびLocalAuthenticationでのTouch IDの使用 の例は、一貫性のない結果を示し、Touch IDはシステムによって強制されません。

技術仕様

  • Xcode 8.1
  • スイフト3
29
David Gatti

タッチIDポップアップは、バックグラウンドキューでSecItemCopyMatching()を呼び出した場合にのみ表示されます。これは、PDF Touch IDを使用したキーチェーンと認証 :のプレゼンテーションのページ118に示されています。

シークレットを読む
...

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

そうしないと、メインスレッドがブロックされ、ポップアップが表示されません。 SecItemCopyMatching()は、エラーコード_-25293 = errSecAuthFailed_で失敗します(タイムアウト後)。

エラーは、たとえば、エラーが発生した場合に間違った変数を出力するため、サンプルプロジェクトでは失敗はすぐにはわかりません。

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

更新と削除についても同様です。

これは、キーチェーンアイテムを取得するために必要なバックグラウンドキューへのディスパッチを含むサンプルコードの構成バージョンです。 (もちろん、UIの更新はメインキューにディスパッチする必要があります。)

Touch IDを使用したiPhoneでのテストでは、期待どおりに機能しました。TouchIDポップアップが表示され、認証が成功した後にのみキーチェーンアイテムが取得されます。

タッチID認証は、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).")
        }
    }
}
_
23
Martin R