py_webauthn(PyPI の webauthn パッケージ)は、pyca/cryptography の上で、登録用と認証用のオプションを生成し、すべてのアテステーション形式に対応してレスポンスを検証します。チェックでは、SimpleWebAuthn、go-webauthn、WebAuthn4J で使ったものと同じソフトウェア認証器の登録とアサーションを渡しました。そのため、ライブラリ間の違いがはっきりわかります。
ポリシーのデフォルトは 4 つのライブラリの中で最も緩くなっています。generate_registration_options() は authenticatorSelection メンバーをまったく送信しないため、ブラウザーはユーザー検証を preferred、レジデントキーを discouraged(検出可能ではないパスキー)とするデフォルトにフォールバックします。また、require_user_verification=True を渡さない限り、verify_registration_response() と verify_authentication_response() は UV フラグが立っていないレスポンスも受け入れました。そのため、デフォルトのまま構築したログインは、PIN なしでセキュリティキーをタップしただけで通ってしまいます。サンプルのように、両側でポリシーを明示的に設定してください。
アサーション署名は ASN.1 DER でなければなりません。64 バイトの生の r‖s 署名は Could not verify authentication signature で失敗しましたが、このメッセージはエンコーディングに触れていません。テスト用クライアントが誤った形式を生成していないかは、DER ⇄ 生形式の変換ツールで確認できます。high-S の DER 署名は検証を通りました。デフォルトでは Ed25519(-8)、ES256(-7)、RS256(-257)が提示され、SimpleWebAuthn 14 と異なり ML-DSA の項目はありません。
カウンターのチェックは厳格です。保存済みの値が 5 のときにカウンター 3 を送ると Response sign count of 3 was not greater than current count of 5 で失敗し、5 のあとの 0 も同様に失敗しましたが、0 のあとの 0(同期パスキーが報告する値)は検証を通りました。ログインに成功するたびに、返された new_sign_count を保存してください。
ブラウザーの生の出力は WebAuthn インスペクターでデコードし、パスキーサンドボックスで安全に試してください。