CZ-NIC / django-fido

Django application for FIDO protocol U2F
GNU General Public License v3.0
28 stars 11 forks source link

Maybe rip out the recording user_handle commits? #144

Closed variable closed 2 years ago

variable commented 2 years ago

https://github.com/CZ-NIC/django-fido/pull/142

Sorry I only just came to realise the user_handle from the credential can be decoded back into the username, so recording the user_handle is not necessary. Sorry for mucking around, this whole FIDO thing is new to me. :(

Cheers.

variable commented 2 years ago

Here is what I found, the following js code gets the credential, I added 2 console logs to display the user handle

const result = await navigator.credentials.get({ publicKey })
fido2SuccessAuthenticationCallback(result)

function fido2SuccessAuthenticationCallback(assertion) {
    const form = document.getElementById(DJANGO_FIDO_FORM_ID)
    form.client_data.value = _arrayBufferToBase64(assertion.response.clientDataJSON)
    form.credential_id.value = _arrayBufferToBase64(assertion.rawId)
    form.authenticator_data.value = _arrayBufferToBase64(assertion.response.authenticatorData)
    form.signature.value = _arrayBufferToBase64(assertion.response.signature)
    console.log(assertion.response);
    console.log(_arrayBufferToBase64(assertion.response.userHandle))
    if (assertion.response.userHandle !== undefined) {
        form.user_handle.value = _arrayBufferToBase64(assertion.response.userHandle)
    } else {
        form.user_handle.value = ''
    }
    // form.submit()
}

image

then the base64 string can be decoded in python to get the user name

Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> base64.b64decode('amxpbg==')
b'jlin'
>>> base64.b64decode('amxpbg==').decode('utf-8')
'jlin'
variable commented 2 years ago

Having a read on the spec, I am confused

https://www.w3.org/TR/webauthn-2/#sctn-user-handle-privacy

Since the user handle is not considered personally identifying information in § 14.4.2 Privacy of personally identifying information Stored in Authenticators, the Relying Party MUST NOT include personally identifying information, e.g., e-mail addresses or usernames, in the user handle. This includes hash values of personally identifying information, unless the hash function is salted with salt values private to the Relying Party, since hashing does not prevent probing for guessable input values. It is RECOMMENDED to let the user handle be 64 random bytes, and store this value in the user’s account.

So looks like we need to generate a random string for the user.id instead of using username?

variable commented 2 years ago
    def get_user_id(self, user: AbstractBaseUser) -> str:
        """Return a unique, persistent identifier of a user.

        Default implementation return user's username, but it is only secure if the username can't be reused.
        In such case, it is required to provide another identifier which would differentiate users.
        See https://www.w3.org/TR/webauthn/#dom-publickeycredentialuserentity-id and
        https://tools.ietf.org/html/rfc8266#section-6.1 for details.
        """
        return user.username

If we need to change this to return something opaque (rather than username), then we will need to record the user_handle in authenticator model.

variable commented 2 years ago

Closing this issue because I think we need to comply with the spec and not to use username as user handle.