def test_parses_uv_false(self) -> None:
        auth_data = _generate_auth_data()[0]

        output = parse_authenticator_data(auth_data)

        assert output.flags.up is True
        assert output.flags.uv is False
    def test_parses_uv_false(self) -> None:
        auth_data = _generate_auth_data()[0]

        output = parse_authenticator_data(auth_data)

        self.assertTrue(output.flags.up)
        self.assertFalse(output.flags.uv)
    def test_correctly_parses_simple(self) -> None:
        (auth_data, rp_id_hash, sign_count, _, _,
         _) = _generate_auth_data(10, up=True, uv=True)

        output = parse_authenticator_data(auth_data)

        assert output.rp_id_hash == rp_id_hash
        assert output.flags.up is True
        assert output.flags.uv is True
        assert output.flags.at is False
        assert output.flags.ed is False
        assert output.sign_count == sign_count
    def test_correctly_parses_attested_credential_data(self) -> None:
        (
            auth_data,
            _,
            _,
            aaguid,
            credential_id,
            credential_public_key,
        ) = _generate_auth_data(10, up=True, uv=True, at=True)

        output = parse_authenticator_data(auth_data)

        cred_data = output.attested_credential_data
        assert cred_data
        assert cred_data.aaguid == aaguid
        assert cred_data.credential_id == credential_id
        assert cred_data.credential_public_key == credential_public_key
    def test_parses_only_extension_data(self) -> None:
        # Pulled from Conformance Testing suite
        auth_data = base64url_to_bytes(
            "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2OBAAAAjaFxZXhhbXBsZS5leHRlbnNpb254dlRoaXMgaXMgYW4gZXhhbXBsZSBleHRlbnNpb24hIElmIHlvdSByZWFkIHRoaXMgbWVzc2FnZSwgeW91IHByb2JhYmx5IHN1Y2Nlc3NmdWxseSBwYXNzaW5nIGNvbmZvcm1hbmNlIHRlc3RzLiBHb29kIGpvYiE"
        )

        output = parse_authenticator_data(auth_data)

        extensions = output.extensions
        self.assertIsNotNone(extensions)
        assert extensions  # Make mypy happy
        parsed_extensions = cbor2.loads(extensions)
        self.assertEqual(
            parsed_extensions, {
                'example.extension':
                'This is an example extension! If you read this message, you probably successfully passing conformance tests. Good job!',
            })
    def test_parses_attested_credential_data_and_extension_data(self) -> None:
        auth_data = bytes.fromhex(
            "50569158be61d7a1ba084f80e45e938fd326e0a8dff07b37036e6c82303ae26bc1000004377b3024675546afcb92e4495c8a1e193f00dca30058b8d74f6bd74de90baeb34afb51e3578e1ac4ca9f79a7f88473d8254d5762ca82d68f3bf63f49e9b284caab4d45d6f9bb468d0c1b7f0f727378c1db8adb4802cb7c5ad9c5eb905bf0ba03f79bd1f04d63765452d49c4087acfad340516dc892eafd87d498ae9e6fd6f06a3f423108ebdc032d93e82fdd6deacc1b638fd56838a482f01232ad01e266e016a50b8121816997a167f41139900fe46094b8ef30aad14ee08cc457366a033bb4a0554dcf9c9589f9622d4f84481541014c870291c87d7a3bbe3d8b07eb02509de5721e3f728aa5eac41e9c5af02869a4010103272006215820e613b86a8d4ebae24e84a0270b6773f7bb30d1d59f5ec379910ebe7c87714274a16b6372656450726f7465637401"
        )
        output = parse_authenticator_data(auth_data)

        cred_data = output.attested_credential_data
        self.assertIsNotNone(cred_data)
        assert cred_data  # Make mypy happy
        self.assertEqual(
            bytes_to_base64url(cred_data.credential_public_key),
            "pAEBAycgBiFYIOYTuGqNTrriToSgJwtnc_e7MNHVn17DeZEOvnyHcUJ0")

        extensions = output.extensions
        self.assertIsNotNone(extensions)
        assert extensions  # Make mypy happy

        parsed_extensions = cbor2.loads(extensions)
        self.assertEqual(parsed_extensions, {'credProtect': 1})
    def test_correctly_parses_attested_credential_data(self) -> None:
        auth_data = base64url_to_bytes(
            "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAJch83ZdWwUm4niTLNjZU81AAIHa7Ksm5br3hAh3UjxP9-4rqu8BEsD-7SZ2xWe1_yHv6pAEDAzkBACBZAQDcxA7Ehs9goWB2Hbl6e9v-aUub9rvy2M7Hkvf-iCzMGE63e3sCEW5Ru33KNy4um46s9jalcBHtZgtEnyeRoQvszis-ws5o4Da0vQfuzlpBmjWT1dV6LuP-vs9wrfObW4jlA5bKEIhv63-jAxOtdXGVzo75PxBlqxrmrr5IR9n8Fw7clwRsDkjgRHaNcQVbwq_qdNwU5H3hZKu9szTwBS5NGRq01EaDF2014YSTFjwtAmZ3PU1tcO_QD2U2zg6eB5grfWDeAJtRE8cbndDWc8aLL0aeC37Q36-TVsGe6AhBgHEw6eO3I3NW5r9v_26CqMPBDwmEundeq1iGyKfMloobIUMBAAE"
        )

        output = parse_authenticator_data(auth_data)

        cred_data = output.attested_credential_data
        self.assertIsNotNone(cred_data)
        assert cred_data  # Make mypy happy
        self.assertEqual(
            cred_data.aaguid,
            base64url_to_bytes("yHzdl1bBSbieJMs2NlTzUA"),
        )
        self.assertEqual(
            cred_data.credential_id,
            base64url_to_bytes("drsqybluveECHdSPE_37iuq7wESwP7tJnbFZ7X_Ie_o"),
        )
        self.assertEqual(
            cred_data.credential_public_key,
            base64url_to_bytes(
                "pAEDAzkBACBZAQDcxA7Ehs9goWB2Hbl6e9v-aUub9rvy2M7Hkvf-iCzMGE63e3sCEW5Ru33KNy4um46s9jalcBHtZgtEnyeRoQvszis-ws5o4Da0vQfuzlpBmjWT1dV6LuP-vs9wrfObW4jlA5bKEIhv63-jAxOtdXGVzo75PxBlqxrmrr5IR9n8Fw7clwRsDkjgRHaNcQVbwq_qdNwU5H3hZKu9szTwBS5NGRq01EaDF2014YSTFjwtAmZ3PU1tcO_QD2U2zg6eB5grfWDeAJtRE8cbndDWc8aLL0aeC37Q36-TVsGe6AhBgHEw6eO3I3NW5r9v_26CqMPBDwmEundeq1iGyKfMloobIUMBAAE"
            ),
        )
Ejemplo n.º 8
0
def verify_authentication_response(
    *,
    credential: AuthenticationCredential,
    expected_challenge: bytes,
    expected_rp_id: str,
    expected_origin: Union[str, List[str]],
    credential_public_key: bytes,
    credential_current_sign_count: int,
    require_user_verification: bool = False,
) -> VerifiedAuthentication:
    """Verify a response from navigator.credentials.get()

    Args:
        `credential`: The value returned from `navigator.credentials.get()`.
        `expected_challenge`: The challenge passed to the authenticator within the preceding authentication options.
        `expected_rp_id`: The Relying Party's unique identifier as specified in the precending authentication options.
        `expected_origin`: The domain, with HTTP protocol (e.g. "https://domain.here"), on which the authentication ceremony should have occurred.
        `credential_public_key`: The public key for the credential's ID as provided in a preceding authenticator registration ceremony.
        `credential_current_sign_count`: The current known number of times the authenticator was used.
        (optional) `require_user_verification`: Whether or not to require that the authenticator verified the user.

    Returns:
        Information about the authenticator

    Raises:
        `helpers.exceptions.InvalidAuthenticationResponse` if the response cannot be verified
    """

    # FIDO-specific check
    if bytes_to_base64url(credential.raw_id) != credential.id:
        raise InvalidAuthenticationResponse(
            "id and raw_id were not equivalent")

    # FIDO-specific check
    if credential.type != PublicKeyCredentialType.PUBLIC_KEY:
        raise InvalidAuthenticationResponse(
            f'Unexpected credential type "{credential.type}", expected "public-key"'
        )

    response = credential.response

    client_data = parse_client_data_json(response.client_data_json)

    if client_data.type != ClientDataType.WEBAUTHN_GET:
        raise InvalidAuthenticationResponse(
            f'Unexpected client data type "{client_data.type}", expected "{ClientDataType.WEBAUTHN_GET}"'
        )

    if expected_challenge != client_data.challenge:
        raise InvalidAuthenticationResponse(
            "Client data challenge was not expected challenge")

    if isinstance(expected_origin, str):
        if expected_origin != client_data.origin:
            raise InvalidAuthenticationResponse(
                f'Unexpected client data origin "{client_data.origin}", expected "{expected_origin}"'
            )
    else:
        try:
            expected_origin.index(client_data.origin)
        except ValueError:
            raise InvalidAuthenticationResponse(
                f'Unexpected client data origin "{client_data.origin}", expected one of {expected_origin}'
            )

    if client_data.token_binding:
        status = client_data.token_binding.status
        if status not in expected_token_binding_statuses:
            raise InvalidAuthenticationResponse(
                f'Unexpected token_binding status of "{status}", expected one of "{",".join(expected_token_binding_statuses)}"'
            )

    auth_data = parse_authenticator_data(response.authenticator_data)

    # Generate a hash of the expected RP ID for comparison
    expected_rp_id_hash = hashlib.sha256()
    expected_rp_id_hash.update(expected_rp_id.encode("utf-8"))
    expected_rp_id_hash_bytes = expected_rp_id_hash.digest()

    if auth_data.rp_id_hash != expected_rp_id_hash_bytes:
        raise InvalidAuthenticationResponse("Unexpected RP ID hash")

    if not auth_data.flags.up:
        raise InvalidAuthenticationResponse(
            "User was not present during authentication")

    if require_user_verification and not auth_data.flags.uv:
        raise InvalidAuthenticationResponse(
            "User verification is required but user was not verified during authentication"
        )

    if (auth_data.sign_count > 0 or credential_current_sign_count > 0
        ) and auth_data.sign_count <= credential_current_sign_count:
        # Require the sign count to have been incremented over what was reported by the
        # authenticator the last time this credential was used, otherwise this might be
        # a replay attack
        raise InvalidAuthenticationResponse(
            f"Response sign count of {auth_data.sign_count} was not greater than current count of {credential_current_sign_count}"
        )

    client_data_hash = hashlib.sha256()
    client_data_hash.update(response.client_data_json)
    client_data_hash_bytes = client_data_hash.digest()

    signature_base = response.authenticator_data + client_data_hash_bytes

    try:
        decoded_public_key = decode_credential_public_key(
            credential_public_key)
        crypto_public_key = decoded_public_key_to_cryptography(
            decoded_public_key)

        verify_signature(
            public_key=crypto_public_key,
            signature_alg=decoded_public_key.alg,
            signature=response.signature,
            data=signature_base,
        )
    except InvalidSignature:
        raise InvalidAuthenticationResponse(
            "Could not verify authentication signature")

    return VerifiedAuthentication(
        credential_id=credential.raw_id,
        new_sign_count=auth_data.sign_count,
    )