Yubico / python-fido2

Provides library functionality for FIDO 2.0, including communication with a device over USB.
BSD 2-Clause "Simplified" License
432 stars 109 forks source link

U2fClient raises ERR.DEVICE_INELIGIBLE when registration is made with a different app #130

Closed nmariz closed 2 years ago

nmariz commented 2 years ago

I'm getting a ClientError.ERR.DEVICE_INELIGIBLE() when trying to authenticate of U2F credentials against an authenticator using CTAP 1. The device registration is made by another app and I'm using the following:

from fido2.hid import CtapHidDevice
from fido2.client import U2fClient
from fido2.utils import websafe_encode

# Get FIDO device
dev = next(CtapHidDevice.list_devices(), None)
if not dev:
    raise errors.InterfaceError("No FIDO device found")

app_id = "https://example.com"
challenge = websafe_encode(challenge)
credential_id = websafe_encode(credential_id)

client = U2fClient(dev, app_id)
resp = client.sign(
    app_id,
    challenge,
    [{"version": "U2F_V2", "keyHandle": credential_id}]
)

NOTE: The challenge and credential_id are sent by the relying party.

It raises fido2.client.ClientError: (<ERR.DEVICE_INELIGIBLE: 4>, None). Debugging with the Ctap1 class I noticed that raises fido2.ctap1.ApduError: (27264, b'') under. So it's not clear what is happening.

nmariz commented 2 years ago

I've also tried to register instead of using an external app:

import sys

from fido2.hid import CtapHidDevice
from fido2.utils import sha256
from fido2.ctap1 import CTAP1

dev = next(CtapHidDevice.list_devices(), None)
if not dev:
    print("No u2f device found")
    sys.exit(1)

challenge = sha256(b"<server_challenge>")
app_id = sha256(b"https://example.com")

dev.use_ext_apdu = False

ctap1 = CTAP1(dev)
print("version:", ctap1.get_version())

reg = ctap1.register(challenge, app_id)
print("key_handle:", reg.key_handle)

But I get:

fido2.ctap1.ApduError: (27013, b'')

NOTE: I'm using a Yubikey 4

dainnilsson commented 2 years ago

The APDU errors you are getting have the following status codes: 27264: 0x6a80 (hex) - The parameters in the data field are incorrect 27013: 0x6985 (hex) - Conditions of use not satisfied

The first snippet you have, which is resulting in DEVICE_INELIGIBLE, is likely to be passing some data that is incorrect. One thing that immediately looks wrong to me is challenge = websafe_encode(challenge). The U2fClient.sign method expects an unencoded bytes object for the challenge, so depending on what your challenge is from the start, you probably want to either pass it as-is, or use websafe_decode on it first. However, getting the challenge wrong shouldn't be causing the DEVICE_INELIGIBLE error, so I suspect that isn't the only problem. What is the format of the credential_id in your snippet? If it's a bytes object of the raw key_handle, then your code looks correct. If it's already base64 encoded, then you should pass it as-is without encoding it again to sign.

The error in the second snippet means that a User Presence check is required. This is the expected response for a valid request, and means that you should touch the YubiKey button to provide User Presence, and then send the command again. This is how CTAP1 works: you send the same command over and over until the user touches the key and you instead get a "success". Thus, your second snippet is probably working correctly.

nmariz commented 2 years ago

Thanks for the detailed explanation. In fact the issue was on my implementation.