Beispiel #1
0
def validate_challenge_webauthn(data: dict, request: HttpRequest,
                                user: User) -> Device:
    """Validate WebAuthn Challenge"""
    challenge = request.session.get("challenge")
    credential_id = data.get("id")

    device = WebAuthnDevice.objects.filter(credential_id=credential_id).first()
    if not device:
        raise ValidationError("Device does not exist.")

    try:
        authentication_verification = verify_authentication_response(
            credential=AuthenticationCredential.parse_raw(dumps(data)),
            expected_challenge=challenge,
            expected_rp_id=get_rp_id(request),
            expected_origin=get_origin(request),
            credential_public_key=base64url_to_bytes(device.public_key),
            credential_current_sign_count=device.sign_count,
            require_user_verification=False,
        )

    except InvalidAuthenticationResponse as exc:
        LOGGER.warning("Assertion failed", exc=exc)
        raise ValidationError("Assertion failed") from exc

    device.set_sign_count(authentication_verification.new_sign_count)
    return device
Beispiel #2
0
    def get_challenge(self, *args, **kwargs) -> Challenge:
        # clear session variables prior to starting a new registration
        self.request.session.pop("challenge", None)

        challenge = generate_challenge(32)

        # We strip the saved challenge of padding, so that we can do a byte
        # comparison on the URL-safe-without-padding challenge we get back
        # from the browser.
        # We will still pass the padded version down to the browser so that the JS
        # can decode the challenge into binary without too much trouble.
        self.request.session["challenge"] = challenge.rstrip("=")
        user = self.get_pending_user()
        make_credential_options = WebAuthnMakeCredentialOptions(
            challenge,
            RP_NAME,
            get_rp_id(self.request),
            user.uid,
            user.username,
            user.name,
            user.avatar,
        )

        registration_dict = make_credential_options.registration_dict
        registration_dict["authenticatorSelection"] = {
            "requireResidentKey": False,
            "userVerification": "preferred",
        }

        return AuthenticatorWebAuthnChallenge(
            data={
                "type": ChallengeTypes.NATIVE.value,
                "component": "ak-stage-authenticator-webauthn",
                "registration": registration_dict,
            })
Beispiel #3
0
    def get_challenge(self, *args, **kwargs) -> Challenge:
        # clear session variables prior to starting a new registration
        self.request.session.pop("challenge", None)
        stage: AuthenticateWebAuthnStage = self.executor.current_stage
        user = self.get_pending_user()

        # library accepts none so we store null in the database, but if there is a value
        # set, cast it to string to ensure it's not a django class
        authenticator_attachment = stage.authenticator_attachment
        if authenticator_attachment:
            authenticator_attachment = str(authenticator_attachment)

        registration_options: PublicKeyCredentialCreationOptions = generate_registration_options(
            rp_id=get_rp_id(self.request),
            rp_name=self.request.tenant.branding_title,
            user_id=user.uid,
            user_name=user.username,
            user_display_name=user.name,
            authenticator_selection=AuthenticatorSelectionCriteria(
                resident_key=str(stage.resident_key_requirement),
                user_verification=str(stage.user_verification),
                authenticator_attachment=authenticator_attachment,
            ),
        )

        self.request.session["challenge"] = registration_options.challenge
        return AuthenticatorWebAuthnChallenge(
            data={
                "type": ChallengeTypes.NATIVE.value,
                "registration": loads(options_to_json(registration_options)),
            })
Beispiel #4
0
def get_webauthn_challenge_userless(request: HttpRequest) -> dict:
    """Same as `get_webauthn_challenge`, but allows any client device. We can then later check
    who the device belongs to."""
    request.session.pop("challenge", None)
    authentication_options = generate_authentication_options(
        rp_id=get_rp_id(request),
        allow_credentials=[],
    )

    request.session["challenge"] = authentication_options.challenge

    return loads(options_to_json(authentication_options))
Beispiel #5
0
 def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
     # Webauthn Challenge has already been validated
     webauthn_credential: WebAuthnCredential = response.validated_data[
         "response"]
     existing_device = WebAuthnDevice.objects.filter(
         credential_id=webauthn_credential.credential_id).first()
     if not existing_device:
         WebAuthnDevice.objects.create(
             user=self.get_pending_user(),
             public_key=webauthn_credential.public_key,
             credential_id=webauthn_credential.credential_id,
             sign_count=webauthn_credential.sign_count,
             rp_id=get_rp_id(self.request),
         )
     else:
         return self.executor.stage_invalid(
             "Device with Credential ID already exists.")
     return self.executor.stage_ok()
Beispiel #6
0
    def validate_response(self, response: dict) -> dict:
        """Validate webauthn challenge response"""
        challenge = self.request.session["challenge"]

        trusted_attestation_cert_required = True
        self_attestation_permitted = True
        none_attestation_permitted = True

        webauthn_registration_response = WebAuthnRegistrationResponse(
            get_rp_id(self.request),
            get_origin(self.request),
            response,
            challenge,
            trusted_attestation_cert_required=trusted_attestation_cert_required,
            self_attestation_permitted=self_attestation_permitted,
            none_attestation_permitted=none_attestation_permitted,
            uv_required=False,
        )  # User Verification

        try:
            webauthn_credential = webauthn_registration_response.verify()
        except RegistrationRejectedException as exc:
            LOGGER.warning("registration failed", exc=exc)
            raise ValidationError("Registration failed. Error: {}".format(exc))

        # Step 17.
        #
        # Check that the credentialId is not yet registered to any other user.
        # If registration is requested for a credential that is already registered
        # to a different user, the Relying Party SHOULD fail this registration
        # ceremony, or it MAY decide to accept the registration, e.g. while deleting
        # the older registration.
        credential_id_exists = WebAuthnDevice.objects.filter(
            credential_id=webauthn_credential.credential_id).first()
        if credential_id_exists:
            raise ValidationError("Credential ID already exists.")

        webauthn_credential.credential_id = str(
            webauthn_credential.credential_id, "utf-8")
        webauthn_credential.public_key = str(webauthn_credential.public_key,
                                             "utf-8")

        return webauthn_credential
Beispiel #7
0
def get_webauthn_challenge(request: HttpRequest,
                           device: Optional[WebAuthnDevice] = None) -> dict:
    """Send the client a challenge that we'll check later"""
    request.session.pop("challenge", None)

    allowed_credentials = []

    if device:
        # We want all the user's WebAuthn devices and merge their challenges
        for user_device in WebAuthnDevice.objects.filter(
                user=device.user).order_by("name"):
            user_device: WebAuthnDevice
            allowed_credentials.append(user_device.descriptor)

    authentication_options = generate_authentication_options(
        rp_id=get_rp_id(request),
        allow_credentials=allowed_credentials,
    )

    request.session["challenge"] = authentication_options.challenge

    return loads(options_to_json(authentication_options))
Beispiel #8
0
    def validate_response(self, response: dict) -> dict:
        """Validate webauthn challenge response"""
        challenge = self.request.session["challenge"]

        try:
            registration: VerifiedRegistration = verify_registration_response(
                credential=RegistrationCredential.parse_raw(dumps(response)),
                expected_challenge=challenge,
                expected_rp_id=get_rp_id(self.request),
                expected_origin=get_origin(self.request),
            )
        except InvalidRegistrationResponse as exc:
            LOGGER.warning("registration failed", exc=exc)
            raise ValidationError(f"Registration failed. Error: {exc}")

        credential_id_exists = WebAuthnDevice.objects.filter(
            credential_id=bytes_to_base64url(
                registration.credential_id)).first()
        if credential_id_exists:
            raise ValidationError("Credential ID already exists.")

        return registration
Beispiel #9
0
 def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
     # Webauthn Challenge has already been validated
     webauthn_credential: VerifiedRegistration = response.validated_data[
         "response"]
     existing_device = WebAuthnDevice.objects.filter(
         credential_id=bytes_to_base64url(
             webauthn_credential.credential_id)).first()
     if not existing_device:
         WebAuthnDevice.objects.create(
             user=self.get_pending_user(),
             public_key=bytes_to_base64url(
                 webauthn_credential.credential_public_key),
             credential_id=bytes_to_base64url(
                 webauthn_credential.credential_id),
             sign_count=webauthn_credential.sign_count,
             rp_id=get_rp_id(self.request),
             name="WebAuthn Device",
         )
     else:
         return self.executor.stage_invalid(
             "Device with Credential ID already exists.")
     return self.executor.stage_ok()