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

How to instantiate AttestedCredentialData from legacy U2F registration #129

Closed cuu508 closed 2 years ago

cuu508 commented 2 years ago

(sorry – this is not an issue, more a question on how to use the libary)

I'm looking at migrating a webapp from U2F to WebAuthn, and want to make sure the existing, registered U2F keys keep working. The registered keys are stored on the server side like so:

{
    "appId": "https://localhost:8000", 
    "version": "U2F_V2", 
    "keyHandle": "AbC0GNAXtomrBtRKWSB7ajFN4MvMvr4-JzN4B1fBYLb0CYKWczWfwwpse3pn-WLxEtWy7CzBH99msfN5UXX0JA", 
    "publicKey": "BDHXJn4OpNR1vLpcGWL1QLPMv1xfKpmuGJw6FsuovpvrYpztIFENLWdvMYot0J9m3bzrzIzqbwxR1BmgCsUH7BU", 
    "transports": ["usb"]
}

How do I instantiate AttestedCredentialData (or PublicKeyCredentialDescriptor?) from this, for passing into U2FFido2Server.authenticate_begin()?

Thanks in advance for any tips!

cuu508 commented 2 years ago

Ah, OK, assuming on the server I have

legacy_credential = {
    "appId": "https://localhost:8000", 
    "version": "U2F_V2", 
    "keyHandle": "AbC0GNAXtomrBtRKWSB7ajFN4MvMvr4-JzN4B1fBYLb0CYKWczWfwwpse3pn-WLxEtWy7CzBH99msfN5UXX0JA", 
    "publicKey": "BDHXJn4OpNR1vLpcGWL1QLPMv1xfKpmuGJw6FsuovpvrYpztIFENLWdvMYot0J9m3bzrzIzqbwxR1BmgCsUH7BU", 
    "transports": ["usb"]
}

looks like I can do

raw_keyhandle = base64.urlsafe_b64decode(legacy_credential["keyHandle"] + "==")
credential = PublicKeyCredentialDescriptor("public-key", raw_keyhandle)
options, state = server.authenticate_begin([credential])

Alternatively, thanks to fido2.server._wrap_credentials, this also works:

raw_keyhandle = base64.urlsafe_b64decode(legacy_credential["keyHandle"] + "==")
credential = {"type": "public-key", "id": raw_keyhandle}
options, state = server.authenticate_begin([credential])

It's important to use base64.urlsafe_b64decode instead of base64.b64decode because they decode -, _, + and / differently.

cuu508 commented 2 years ago

Turns out authenticate_complete only accepts credentials as AttestedCredentialData instances.

After some code diving, here's how I managed to create an AttestedCredentialData instance from legacy U2F registration data:

from fido2.ctap2.base import AttestedCredentialData
from fido2.utils import websafe_decode

key_handle = websafe_decode(legacy_credential["keyHandle"])
public_key = websafe_decode(legacy_credential["publicKey"])
credential = AttestedCredentialData.from_ctap1(key_handle, public_key)