Problem of signature with webauthn on django with djoser

I'm working at the moment on an implementation of webauthn on a project. The main point is to give the possibility to user to use FaceId or fingerprint scan on their mobile on the website.

I tried the djoser version of webauthn but I wanted to give the possibility to user that already have an account so I took the implementation of webauthn of djoser and I updated it to make it working with already created account.

I can ask for the signup request of a webauthn token and create the webauthn token with the front (Angular) where I use @simplewebauthn/browser ("@simplewebauthn/browser": "^6.3.0-alpha.1") . Everything is working fine there.

I use the latest version of djoser by pulling git and the version of webauthn is 0.4.7 linked to djoser.

djoser @git+https://github.com/sunscrapers/djoser.git@abdf622f95dfa2c6278c4bd6d50dfe69559d90c0
webauthn==0.4.7

But when I send back to the backend the result of the registration, I have an error:

Authentication rejected. Error: Invalid signature received..

Here's the SignUpView:

    permission_classes = (AllowAny,)

    def post(self, request, ukey):
        co = get_object_or_404(CredentialOptions, ukey=ukey)

        webauthn_registration_response = WebAuthnRegistrationResponse(
            rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
            origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
            registration_response=request.data,
            challenge=co.challenge,
            none_attestation_permitted=True,
        )
        try:
            webauthn_credential = webauthn_registration_response.verify()
        except RegistrationRejectedException as e:
            return Response(
                {api_settings.NON_FIELD_ERRORS_KEY: format(e)},
                status=status.HTTP_400_BAD_REQUEST,
            )
        user = User.objects.get(username=request.data["username"])
        user_serializer = CustomUserSerializer(user)
        co.challenge = ""
        co.user = user
        co.sign_count = webauthn_credential.sign_count
        co.credential_id = webauthn_credential.credential_id.decode()
        co.public_key = webauthn_credential.public_key.decode()
        co.save()


        return Response(user_serializer.data, status=status.HTTP_201_CREATED)

And I based my work on https://github.com/sunscrapers/djoser/blob/abdf622f95dfa2c6278c4bd6d50dfe69559d90c0/djoser/webauthn/views.py#L53

Here's also the SignUpRequesrtView where I edited some little things to make it working the way I want:

class SignupRequestView(APIView):
    permission_classes = (AllowAny,)

    def post(self, request):
        CredentialOptions.objects.filter(username=request.data["username"]).delete()

        serializer = WebauthnSignupSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        co = serializer.save()

        credential_registration_dict = WebAuthnMakeCredentialOptions(
            challenge=co.challenge,
            rp_name=settings.DJOSER["WEBAUTHN"]["RP_NAME"],
            rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
            user_id=co.ukey,
            username=co.username,
            display_name=co.display_name,
            icon_url="",
        )

        return Response(credential_registration_dict.registration_dict)```

And I also updated the WebAuthnSignupSerializer to retrieve an check if there's an account with the username given and if yes, create the CredentialOptions:

class WebauthnSignupSerializer(serializers.ModelSerializer): class Meta: model = CredentialOptions fields = ("username", "display_name")

def create(self, validated_data):
    validated_data.update(
        {
            "challenge": create_challenge(
                length=settings.DJOSER["WEBAUTHN"]["CHALLENGE_LENGTH"]
            ),
            "ukey": create_ukey(length=settings.DJOSER["WEBAUTHN"]["UKEY_LENGTH"]),
        }
    )
    return super().create(validated_data)

def validate_username(self, username):
    if User.objects.filter(username=username).exists():
        return username
    else:
        raise serializers.ValidationError(f"User {username} does not exist.")```

It looks like there's an issue with the signature of the WebAuthn token. The RegistrationRejectedException indicates that the signature is invalid. This could be due to a variety of reasons, such as an issue with the keys being used to sign the token, or an issue with the way the signature is being verified on the server side.

One thing you can try is to double-check that the keys being used on the client side (in the Angular code) match the keys being used on the server side for verification. You can also try logging the signature on both the client and server side and compare the values to see if there are any discrepancies.

Another thing to check is the configuration of the WebAuthn settings in the Django project. Make sure that the RP_ID, ORIGIN, and other relevant settings are set correctly in the Django settings.

If you continue to have issues, it may be helpful to consult the documentation for the @simplewebauthn/browser library and the django-webauthn library to see if there are any known issues or troubleshooting steps that you can follow.

It sounds like you're encountering an error when attempting to verify a WebAuthn registration response on the backend. This error message indicates that the signature included in the response is invalid.

There are a few potential reasons why this might be happening. One possible cause is that the data included in the response has been tampered with or corrupted in some way, leading to an invalid signature. Another possibility is that there is an issue with the key pair that was used to sign the response, such as an incorrectly configured private key or an issue with the cryptographic algorithm used to generate the signature.

To troubleshoot this issue, I would recommend trying the following steps:

Confirm that the webauthn_registration_response object being passed to webauthn_registration_response.verify() is correctly constructed and contains all of the required data. This can be done by printing the webauthn_registration_response object to the console and verifying that its properties are correct.

Confirm that the request.data object passed to the SignUpView contains the correct data. This can be done by printing the request.data object to the console and verifying that it includes the expected data.

Verify that the key pair used to sign the response is correctly configured and that the private key is being used to generate the signature. This can be done by checking the configuration of the key pair and ensuring that the correct private key is being used to sign the response.

If all of the above steps have been checked and the issue persists, it may be necessary to debug the webauthn_registration_response.verify() method to understand why the signature is being rejected. This can be done by using a debugger to step through the code and inspect the intermediate values being used to verify the signature.

I hope this helps! Let me know if you have any further questions.

Well, it's not really an answer, but rather a little "assistance".

You can check whether the signature is valid using this little tool (at the bottom of the page): https://webauthn.passwordless.id/demos/playground.html

At least, using that, you will know if your data is correct or if something was stored wrongly. There are so many conversions from bytes to base64url and back that it's not always easy to track. Perhaps it is a data format/convertion issue? Like not converting to bytes, or accidentally double encoding as base64url.

Lastly, the stored public key has a different format depending on the algorithm. Either "raw" or "ASN.1" wrapped, in case you have a problem with the key itself.

Good luck!

I'm a complete newbie so feel free to skip this comment or tell me I'm dumb for what I'm saying.

The error message "Authentication rejected. Error: Invalid signature received" indicates that the signature of the WebAuthn registration response is invalid or does not match the expected value.

In your code, it looks like you are using the verify() method of the webauthn_registration_response object to verify the signature of the registration response. This method checks the signature of the response against the challenge that was sent to the client, and if the signature is invalid or does not match the expected value, it raises a RegistrationRejectedException.

To fix this issue, you can try the following steps:

  1. Check the value of the challenge attribute of the webauthn_registration_response object, and make sure it matches the challenge that was sent to the client in the sign-up request.

  2. Check the value of the registration_response attribute of the webauthn_registration_response object, and make sure it is a valid registration response that was received from the client.

  3. Check the value of the none_attestation_permitted attribute of the webauthn_registration_response object, and make sure it is set to True if the client is using a "none" attestation type.

  4. If the error persists, try adding additional logging or debugging statements to your code to understand why the verify() method is rejecting the registration response.

The verify() method expects a RegistrationResponse object as an argument, but you're passing it the entire request data. You need to extract the registrationResponse field from the request data and pass that to the verify() method instead.

Change this:

webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data,  # <-- update this line
challenge=co.challenge,
none_attestation_permitted=True,)

To this:

webauthn_registration_response = WebAuthnRegistrationResponse(
rp_id=settings.DJOSER["WEBAUTHN"]["RP_ID"],
origin=settings.DJOSER["WEBAUTHN"]["ORIGIN"],
registration_response=request.data['registrationResponse'],  # <-- update this line
challenge=co.challenge,
none_attestation_permitted=True,)
Back to Top