Библиотека py_webauthn (пакет webauthn в PyPI) построена поверх pyca/cryptography: она формирует параметры регистрации и аутентификации и проверяет ответы, включая все форматы аттестации. В ходе проверки ей передавали ту же регистрацию и те же assertion программного аутентификатора, что и SimpleWebAuthn, go-webauthn и WebAuthn4J, поэтому различия между библиотеками хорошо видны.
Политика по умолчанию здесь мягче, чем у остальных трёх библиотек. generate_registration_options() вообще не отправляет поле authenticatorSelection, поэтому браузеры применяют значения по умолчанию: проверка пользователя preferred и резидентный ключ discouraged (то есть ключи доступа создаются необнаруживаемыми), а verify_registration_response() и verify_authentication_response() принимали ответы со сброшенным флагом UV, если не передать require_user_verification=True. Поэтому вход, построенный на настройках по умолчанию, принимает касание ключа безопасности без ввода PIN. Задайте политику явно с обеих сторон — в параметрах и при проверке, как в примере.
Подписи assertion должны быть в ASN.1 DER: подпись в формате raw r‖s длиной 64 байта отклонена с ошибкой Could not verify authentication signature, в которой кодировка не упоминается; конвертер DER ⇄ raw покажет, не выдал ли тестовый клиент подпись не в той форме. Подпись DER с high-S прошла проверку. По умолчанию предлагаются Ed25519 (-8), ES256 (-7) и RS256 (-257); записи для ML-DSA нет, в отличие от SimpleWebAuthn 14.
Счётчики проверяются строго: Response sign count of 3 was not greater than current count of 5, и то же самое для 0 после 5, а значение 0 после 0 — именно его сообщают синхронизируемые ключи доступа — прошло проверку. Сохраняйте возвращённый new_sign_count после каждого успешного входа.
Декодируйте необработанные данные от браузера в инспекторе WebAuthn и безопасно экспериментируйте в песочнице ключей доступа.