s1r-Jの技術ブログ

とあるSEの技術ブログ

FIDO2サーバ実装に必要そうな仕様書「Server Requirements and Transport Binding Profile」を日本語訳する

Server Requirements and Transport Binding Profileの日本語訳をしたので残しておきます。

W3CのWebAuthnに関する仕様書(Web Authentication: An API for accessing Public Key Credentials - Level 2)と合わせて、FIDO2サーバの実装に重要な仕様書のはず。WebAuthnはリライングパーティ向けなのでFIDO2サーバの実装にはこちらが重要なのか?この仕様書でもWebAuthnと競合する場合にはこちらのドキュメントが優先される旨、FIDO2 Conformance Test(FIDO2サーバの認定を受けるために必要な適合性試験)についての言及があるので、確認が必要なはず。 FIDOアライアンスのドキュメント一覧のページからはリンクがないなど、このドキュメントの位置付けがよくわからない。

Server Requirements and Transport Binding Profile

Review Draft,July 02, 2018

概要

FIDO2は、Client-to-Authenticator Protocol (CTAP)を実装した認証器およびプアラットフォームやW3C WebAuthn仕様書を実装したブラウザの利用を通じてセキュアな認証を提供する。これらの認証器は、登録および認証要求の正当性を確認するサーバと通信することが期待されます。FIDO2サーバに対する多くの必須要件、アサーションフォーマットやアテステーションフォーマットやオプショナルな拡張機能などはW3C WebAuthn仕様書に含まれています。サーバの必須要件およびガイダンスに関する本仕様書は、サーバに関係のない認証器およびWebブラウザの詳細を残しつつ、FIDO2サーバの実装に役立てられるようにサーバに関する全ての要件を1つのドキュメントにまとめようとしている。

1 イントロダクション

この仕様書は、サーバ実装に関してW3C WebAuthnの仕様書からの情報を取り込んだ必須要件とガイダンスを提供する。サーバは、実装が連携して機能することを保証することからFIDOエコシステムにおいて非常に重要である。異なるアテステーションフォーマット(packed、AndroidTPMなど)、アテステーションモード(サロゲート、フル、ECDAAなど)、暗号化アルゴリズムRSA、ECDSAなど)というように、様々な仕様書に多くのオプション機能がある。これらの多様な機能を実装している認証器は、一般的にはメモリおよびまたはCPUに制限があり、先述のような様々な機能を実装することに制限がある消費者向けの電子機器である。したがって、サーバは可能な限り幅広い種類の認証器と互換性を持つことを保証するため、これらの機能を可能な限り多く実装することになる。

WebAuthnの仕様書の概念は非常にシンプルである:サーバに新しい認証器を登録するメソッドを提供し(navigator.credentials.create())、登録済の認証器を利用した認証をおこなう別のメソッドを提供している(navigator.credentials.get())。登録において、認証器はその製造時に認証器に組み込んだアテステーション秘密鍵を利用してアテステーションステートメントを作成し、登録プロセスでのroot-of-trustを提供する。登録処理で登録されたアカウントごとに新しいキーペアを作成され、登録で作成した秘密鍵は正当な認証であることを示すためサーバに送信するアサーションに対して署名する用途に利用される。以下のセクションでは登録とアテステーションの必須要件および認証とアサーションの必須要件について記述している。

サーバには特定の必須プロトコル(REST、SOAP伝書鳩量子テレポーテーションなど)は存在しない(ただし、セキュアなコミュニケーションチャンネルに関する要件は存在する)。サーバはブラウザ、プラットフォーム、認証器が作成するによって作成された何らかの形式のJavaScriptオブジェクトを受け取っていることが想定される。これらのオブジェクトは署名されているため、プロトコルは署名されたオブジェクトをその署名が不当となるような方法で変更してはならない(MUST NOT)が、そうではない場合はサーバにこれらのオブジェクトを送る任意の形式が利用できる。以下の必須要件とガイダンスは、これらのオブジェクトをサーバに対して送受信する方法に関する要件について定めていない。

この仕様書とWebAuthnの仕様書が競合する場合、WebAuthn仕様書が優先される。しかし、この仕様書がWebAuthn仕様書よりも優先とする説明や追記があり、WebブラウザにおいてWebAuthnを実装する方法についての多くの記述はサーバ実装には無関係である。

2 登録とアテステーション

サーバは登録をサポートするものとする(SHALL)。登録リクエストは、チャレンジを認証器に送信してレスポンスにCredentialCreationOptionsオブジェクト(または類似のもの)を受け取るという形を取る。PublicKeyCredentialresponse属性はシリアライズされたclientDataJSON属性およびシリアライズされたattestationObject属性の両方を有している。シリアライズ形式(例 base64urlエンコーディング)には、デシリアライズされたときに元のバイト構造がアテステーションにおいて署名されたものと同じであることという要件を除き、他の要件は存在しない。

サーバは登録リクエストごとにランダムなチャレンジを利用するものとする(SHALL)。チャレンジのランダム性の定義はこの仕様書の範囲外(詳細については[FIDOSecRef]を参照)ではあるが、同じチャレンジや単調増加するチャレンジ、その他単純なチャンレジを利用することは受け入れられず安全ではない。チャレンジの生成には暗号論的乱数生成器を利用することが期待される。

2.1 アテステーションの検証

サーバはアテステーションを検証するものとする(SHALL)。[WebAuthn]はアテステーションの検証方法を指定している。Relying Partyに対する要件はサーバにも標準である。AttestationResponseのフィールド名は[WebAuthn]仕様書のフィールド名および形式と一致しなくてもよい(MAY NOT)ことに注意する。アプリケーションとサーバが独自のフィールド名、形式で通信する可能性がある。[WebAuthn]に記載されている名前および形式は便宜上のものである。

サーバはアテステーション証明書チェーンを検証するものとする(SHALL)。

サーバはFIDO Metadata Service [FIDOMetadataService]を利用してアテステーションの検証をサポートしなければならない(MUST)。

サーバは、認証器のメタデータ属性に基づいて、認証器の許可・不許可、追加の認証要素の要求、リスク分析の実施に関するポリシーを持ってもよい(MAY)。

2.2 アテステーションタイプ

[WebAuthn]では複数のアテステーションタイプが定義されている。サーバはアテステーションフォーマットのうち1つをサポートしなければならない(MUST)。

  • サーバはbasicアテステーションをサポートしなければならない(MUST)
  • サーバはselfアテステーションをサポートしなければならない(MUST)
  • サーバはPrivacy CAアテステーションをサポートしてもよい(MAY)
  • サーバはElliptic Curve Direct Anonymousアテステーション(ECDAA)をサポートしてもよい(MAY)

2.3 アテステーションフォーマット

[WebAuthn]は複数のアテステーションフォーマットを定義しており、エコシステムの進歩に応じて[WebAuthn-Registries]レジストリに新しいアテステーションフォーマットが随時追加・更新される可能性がある。サーバは少なくとも1つのアテステーションフォーマットをサポートしなければならない(MUST)。

  • サーバはPackedアテステーションをサポートしなければならない(MUST):[WebAuthn]
  • サーバはTPMアテステーションをサポートしなければならない(MUST):[WebAuthn]
  • サーバはAndroid Keyアテステーションをサポートする必要がある(SHOULD):[WebAuthn]
  • サーバはU2Fアテステーションをサポートしなければならない(MUST):[WebAuthn]
  • サーバはAndroid SafteyNetアテステーションをサポートしなければならない(MUST):[WebAuthn]
  • サーバは、随時更新される[WebAuthn-Registries]に定義されている他のアテステーションフォーマットをサポートしてもよい(MAY)。認証器またはサーバが新しいアテステーションフォーマットを作成した場合、[WebAuthn-Registries]レジストリに登録する必要がある(SHOULD)。

2.3.1 Packedアテステーション

サーバは、[WebAuthn]に定義されている「Validation Procedure」に従ってPackedアテステーションを検証しなければならない(MUST)。

EXAMPLE 1

{
    "rawId": "sL39APyTmisrjh11vghaqNfuruLQmCfR0c1ryKtaQ81jkEhNa5u9xLTnkibvXC9YpzBLFwWEZ3k9CR_sxzm_pWYbBOtKxeZu9z2GT8b6QW4iQvRlyumCT3oENx_8401r",
    "id": "sL39APyTmisrjh11vghaqNfuruLQmCfR0c1ryKtaQ81jkEhNa5u9xLTnkibvXC9YpzBLFwWEZ3k9CR_sxzm_pWYbBOtKxeZu9z2GT8b6QW4iQvRlyumCT3oENx_8401r",
    "response": {
        "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJ1Vlg4OElnUmEwU1NyTUlSVF9xN2NSY2RmZ2ZSQnhDZ25fcGtwVUFuWEpLMnpPYjMwN3dkMU9MWFEwQXVOYU10QlIzYW1rNkhZenAtX1Z4SlRQcHdHdyIsIm9yaWdpbiI6Imh0dHBzOi8vd2ViYXV0aG4ub3JnIiwidG9rZW5CaW5kaW5nIjp7InN0YXR1cyI6Im5vdC1zdXBwb3J0ZWQifSwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
        "attestationObject": "o2NmbXRmcGFja2VkZ2F0dFN0bXSjY2FsZyZjc2lnWEgwRgIhAIsK0Wr9tmud-waIYoQw20UWi7DL_gDx_PNG3PB57eHLAiEAtRyd-4JI2pCVX-dDz4mbHc_AkvC3d_4qnBBa3n2I_hVjeDVjg1kCRTCCAkEwggHooAMCAQICEBWfe8LNiRjxKGuTSPqfM-IwCgYIKoZIzj0EAwIwSTELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFEZlaXRpYW4gVGVjaG5vbG9naWVzMRswGQYDVQQDDBJGZWl0aWFuIEZJRE8yIENBLTEwIBcNMTgwNDExMDAwMDAwWhgPMjAzMzA0MTAyMzU5NTlaMG8xCzAJBgNVBAYTAkNOMR0wGwYDVQQKDBRGZWl0aWFuIFRlY2hub2xvZ2llczEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEdMBsGA1UEAwwURlQgQmlvUGFzcyBGSURPMiBVU0IwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASABnVcWfvJSbAVqNIKkliXvoMKsu_oLPiP7aCQlmPlSMcfEScFM7QkRnidTP7hAUOKlOmDPeIALC8qHddvTdtdo4GJMIGGMB0GA1UdDgQWBBR6VIJCgGLYiuevhJglxK-RqTSY8jAfBgNVHSMEGDAWgBRNO9jEZxUbuxPo84TYME-daRXAgzAMBgNVHRMBAf8EAjAAMBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEEI4MkVENzNDOEZCNEU1QTIwCgYIKoZIzj0EAwIDRwAwRAIgJEtFo76I3LfgJaLGoxLP-4btvCdKIsEFLjFIUfDosIcCIDQav04cJPILGnPVPazCqfkVtBuyOmsBbx_v-ODn-JDAWQH_MIIB-zCCAaCgAwIBAgIQFZ97ws2JGPEoa5NI-p8z4TAKBggqhkjOPQQDAjBLMQswCQYDVQQGEwJDTjEdMBsGA1UECgwURmVpdGlhbiBUZWNobm9sb2dpZXMxHTAbBgNVBAMMFEZlaXRpYW4gRklETyBSb290IENBMCAXDTE4MDQxMDAwMDAwMFoYDzIwMzgwNDA5MjM1OTU5WjBJMQswCQYDVQQGEwJDTjEdMBsGA1UECgwURmVpdGlhbiBUZWNobm9sb2dpZXMxGzAZBgNVBAMMEkZlaXRpYW4gRklETzIgQ0EtMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABI5-YAnswRZlzKD6w-lv5Qg7lW1XJRHrWzL01mc5V91n2LYXNR3_S7mA5gupuTO5mjQw8xfqIRMHVr1qB3TedY-jZjBkMB0GA1UdDgQWBBRNO9jEZxUbuxPo84TYME-daRXAgzAfBgNVHSMEGDAWgBTRoZhNgX_DuWv2B2e9UBL-kEXxVDASBgNVHRMBAf8ECDAGAQH_AgEAMA4GA1UdDwEB_wQEAwIBBjAKBggqhkjOPQQDAgNJADBGAiEA-3-j0kBHoRFQwnhWbSHMkBaY7KF_TztINFN5ymDkwmUCIQDrCkPBiMHXvYg-kSRgVsKwuVtYonRvC588qRwpLStZ7FkB3DCCAdgwggF-oAMCAQICEBWfe8LNiRjxKGuTSPqfM9YwCgYIKoZIzj0EAwIwSzELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFEZlaXRpYW4gVGVjaG5vbG9naWVzMR0wGwYDVQQDDBRGZWl0aWFuIEZJRE8gUm9vdCBDQTAgFw0xODA0MDEwMDAwMDBaGA8yMDQ4MDMzMTIzNTk1OVowSzELMAkGA1UEBhMCQ04xHTAbBgNVBAoMFEZlaXRpYW4gVGVjaG5vbG9naWVzMR0wGwYDVQQDDBRGZWl0aWFuIEZJRE8gUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJ3wCm47zF9RMtW-pPlkEHTVTLfSYBlsidz7zOAUiuV6k36PvtKAI_-LZ8MiC9BxQUfUrfpLY6klw344lwLq7POjQjBAMB0GA1UdDgQWBBTRoZhNgX_DuWv2B2e9UBL-kEXxVDAPBgNVHRMBAf8EBTADAQH_MA4GA1UdDwEB_wQEAwIBBjAKBggqhkjOPQQDAgNIADBFAiEAt7E9ZQYxnhfsSk6c1dSmFNnJGoU3eJiycs2DoWh7-IoCIA9iWJH8h-UOAaaPK66DtCLe6GIxdpIMv3kmd1PRpWqsaGF1dGhEYXRhWOSVaQiPHs7jIylUA129ENfK45EwWidRtVm7j9fLsim91EEAAAABQjgyRUQ3M0M4RkI0RTVBMgBgsL39APyTmisrjh11vghaqNfuruLQmCfR0c1ryKtaQ81jkEhNa5u9xLTnkibvXC9YpzBLFwWEZ3k9CR_sxzm_pWYbBOtKxeZu9z2GT8b6QW4iQvRlyumCT3oENx_8401rpQECAyYgASFYIFkdweEE6mWiIAYPDoKz3881Aoa4sn8zkTm0aPKKYBvdIlggtlG32lxrang8M0tojYJ36CL1VMv2pZSzqR_NfvG88bA"
    }
};

2.3.2 TPMアテステーション

サーバは、[WebAuthn]に定義されている「Validation Procedure」に従ってTPMアテステーションを検証しなければならない(MUST)。

EXAMPLE 2

{
    "rawId": "hWzdFiPbOMQ5KNBsMhs-Zeh8F0iTHrH63YKkrxJFgjQ",
    "id": "hWzdFiPbOMQ5KNBsMhs-Zeh8F0iTHrH63YKkrxJFgjQ",
    "response": {
        "clientDataJSON": "ew0KCSJ0eXBlIiA6ICJ3ZWJhdXRobi5jcmVhdGUiLA0KCSJjaGFsbGVuZ2UiIDogIndrNkxxRVhBTUFacHFjVFlsWTJ5b3I1RGppeUlfYjFneTluRE90Q0IxeUdZbm1fNFdHNFVrMjRGQXI3QXhUT0ZmUU1laWdrUnhPVExaTnJMeEN2Vl9RIiwNCgkib3JpZ2luIiA6ICJodHRwczovL3dlYmF1dGhuLm9yZyIsDQoJInRva2VuQmluZGluZyIgOiANCgl7DQoJCSJzdGF0dXMiIDogInN1cHBvcnRlZCINCgl9DQp9",
        "attestationObject": "o2NmbXRjdHBtaGF1dGhEYXRhWQFnlWkIjx7O4yMpVANdvRDXyuORMFonUbVZu4_Xy7IpvdRFAAAAAAiYcFjK3EuBtuEw3lDcvpYAIIVs3RYj2zjEOSjQbDIbPmXofBdIkx6x-t2CpK8SRYI0pAEDAzkBACBZAQDF2m9Nk1e94gL1xVjNCjFW0lTy4K2atXkx-YJrdH3hrE8p1gcIdNzleRDhmERJnY5CRwM5sXDQIrUBq4jpwvTtMC5HGccN6-iEJAPtm9_CJzCmGhtw9hbF8bcAys94RhN9xLLUaajhWqtPrYZXCEAi0o9E2QdTIxJrcAfJgZOf33JMr0--R1BAQxpOoGRDC8ss-tfQW9ufZLWw4JUuz4Z5Jz1sbfqBYB8UUDMWoT0HgsMaPmvd7T17xGvB-pvvDf-Dt96vFGtYLEZEgho8Yu26pr5CK_BOQ-2vX9N4MIYVPXNhogMGGmKYqybhM3yhye0GdBpZBUd5iOcgME6uGJ1_IUMBAAFnYXR0U3RtdKZjdmVyYzIuMGNhbGc5__5jc2lnWQEAcV1izWGUWIs0DEOZNQGdriNNXo6nbrGDLzEAeswCK9njYGCLmOkHVgSyafhsjCEMZkQmuPUmEOMDKosqxup_tiXQwG4yCW9TyWoINWGayQ4vcr6Ys-l6KMPkg__d2VywhfonnTJDBfE_4BIRD60GR0qBzTarthDHQFMqRtoUtuOsTF5jedU3EQPojRA5iCNC2naCCZuMSURdlPmhlW5rAaRZVF41ZZECi5iFOM2rO0UpGuQSLUvr1MqQOsDytMf7qWZMvwT_5_8BF6GNdB2l2VzmIJBbV6g8z7dj0fRkjlCXBp8UG2LvTq5SsfugrRWXOJ8BkdMplPfl0mz6ssU_n2N4NWOCWQS2MIIEsjCCA5qgAwIBAgIQEyidpWZzRxOSMNfrAvV1fzANBgkqhkiG9w0BAQsFADBBMT8wPQYDVQQDEzZOQ1UtTlRDLUtFWUlELTE1OTFENEI2RUFGOThEMDEwNDg2NEI2OTAzQTQ4REQwMDI2MDc3RDMwHhcNMTgwNTIwMTYyMDQ0WhcNMjgwNTIwMTYyMDQ0WjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvQ6XK2ujM11E7x4SL34p252ncyQTd3-4r5ALQhBbFKS95gUsuENTG-48GBQwu48i06cckm3eH20TUeJvn4-pj6i8LFOrIK14T3P3GFzbxgQLq1KVm63JWDdEXk789JgzQjHNO7DZFKWTEiktwmBUPUA88TjQcXOtrR5EXTrt1FzGzabOepFann3Ny_XtxI8lDZ3QLwPLJfmk7puGtkGNaXOsRC7GLAnoEB7UWvjiyKG6HAtvVTgxcW5OQnHFb9AHycU5QdukXrP0njdCpLCRR0Nq6VMKmVU3MaGh-DCwYEB32sPNPdDkPDWyk16ItwcmXqfSBV5ZOr8ifvcXbCWUWwIDAQABo4IB5TCCAeEwDgYDVR0PAQH_BAQDAgeAMAwGA1UdEwEB_wQCMAAwbQYDVR0gAQH_BGMwYTBfBgkrBgEEAYI3FR8wUjBQBggrBgEFBQcCAjBEHkIAVABDAFAAQQAgACAAVAByAHUAcwB0AGUAZAAgACAAUABsAGEAdABmAG8AcgBtACAAIABJAGQAZQBuAHQAaQB0AHkwEAYDVR0lBAkwBwYFZ4EFCAMwSgYDVR0RAQH_BEAwPqQ8MDoxODAOBgVngQUCAwwFaWQ6MTMwEAYFZ4EFAgIMB05QQ1Q2eHgwFAYFZ4EFAgEMC2lkOjRFNTQ0MzAwMB8GA1UdIwQYMBaAFMISqVvO-lb4wMFvsVvdAzRHs3qjMB0GA1UdDgQWBBSv4kXTSA8i3NUM0q57lrWpM8p_4TCBswYIKwYBBQUHAQEEgaYwgaMwgaAGCCsGAQUFBzAChoGTaHR0cHM6Ly9hemNzcHJvZG5jdWFpa3B1Ymxpc2guYmxvYi5jb3JlLndpbmRvd3MubmV0L25jdS1udGMta2V5aWQtMTU5MWQ0YjZlYWY5OGQwMTA0ODY0YjY5MDNhNDhkZDAwMjYwNzdkMy8zYjkxOGFlNC0wN2UxLTQwNTktOTQ5MS0wYWQyNDgxOTA4MTguY2VyMA0GCSqGSIb3DQEBCwUAA4IBAQAs-vqdkDX09fNNYqzbv3Lh0vl6RgGpPGl-MYgO8Lg1I9UKvEUaaUHm845ABS8m7r9p22RCWO6TSEPS0YUYzAsNuiKiGVna4nB9JWZaV9GDS6aMD0nJ8kNciorDsV60j0Yb592kv1VkOKlbTF7-Z10jaapx0CqhxEIUzEBb8y9Pa8oOaQf8ORhDHZp-mbn_W8rUzXSDS0rFbWKaW4tGpVoKGRH-f9vIeXxGlxVS0wqqRm_r-h1aZInta0OOiL_S4367gZyeLL3eUnzdd-eYySYn2XINPbVacK8ZifdsLMwiNtz5uM1jbqpEn2UoB3Hcdn0hc12jTLPWFfg7GiKQ0hk9WQXsMIIF6DCCA9CgAwIBAgITMwAAAQDiBsSROVGXhwAAAAABADANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE2MDQGA1UEAxMtTWljcm9zb2Z0IFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDE0MB4XDTE3MDIwMTE3NDAyNFoXDTI5MTIzMTE3NDAyNFowQTE_MD0GA1UEAxM2TkNVLU5UQy1LRVlJRC0xNTkxRDRCNkVBRjk4RDAxMDQ4NjRCNjkwM0E0OEREMDAyNjA3N0QzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9IwUMSiQUbrQR0NLkKR-9RB8zfHYdlmDB0XN_m8qrNHKRJ__lBOR-mwU_h3MFRZF6X3ZZwka1DtwBdzLFV8lVu33bc15stjSd6B22HRRKQ3sIns5AYQxg0eX2PtWCJuIhxdM_jDjP2hq9Yvx-ibt1IO9UZwj83NGxXc7Gk2UvCs9lcFSp6U8zzl5fGFCKYcxIKH0qbPrzjlyVyZTKwGGSTeoMMEdsZiq-m_xIcrehYuHg-FAVaPLLTblS1h5cu80-ruFUm5Xzl61YjVU9tAV_Y4joAsJ5QP3VPocFhr5YVsBVYBiBcQtr5JFdJXZWWEgYcFLdAFUk8nJERS7-5xLuQIDAQABo4IBizCCAYcwCwYDVR0PBAQDAgGGMBsGA1UdJQQUMBIGCSsGAQQBgjcVJAYFZ4EFCAMwFgYDVR0gBA8wDTALBgkrBgEEAYI3FR8wEgYDVR0TAQH_BAgwBgEB_wIBADAdBgNVHQ4EFgQUwhKpW876VvjAwW-xW90DNEezeqMwHwYDVR0jBBgwFoAUeowKzi9IYhfilNGuVcFS7HF0pFYwcAYDVR0fBGkwZzBloGOgYYZfaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVFBNJTIwUm9vdCUyMENlcnRpZmljYXRlJTIwQXV0aG9yaXR5JTIwMjAxNC5jcmwwfQYIKwYBBQUHAQEEcTBvMG0GCCsGAQUFBzAChmFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRQTSUyMFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMTQuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQAKc9z1UUBAaybIVnK8yL1N1iGJFFFFw_PpkxW76hgQhUcCxNFQskfahfFzkBD05odVC1DKyk2PyOle0G86FCmZiJa14MtKNsiu66nVqk2hr8iIcu-cYEsgb446yIGd1NblQKA1C_28F2KHm8YRgcFtRSkWEMuDiVMa0HDU8aI6ZHO04Naj86nXeULJSZsA0pQwNJ04-QJP3MFQzxQ7md6D-pCx-LVA-WUdGxT1ofaO5NFxq0XjubnZwRjQazy_m93dKWp19tbBzTUKImgUKLYGcdmVWXAxUrkxHN2FbZGOYWfmE2TGQXS2Z-g4YAQo1PleyOav3HNB8ti7u5HpI3t9a73xuECy2gFcZQ24DJuBaQe4mU5I_hPiAa-822nPPL6w8m1eegxhHf7ziRW_hW8s1cvAZZ5Jpev96zL_zRv34MsRWhKwLbu2oOCSEYYh8D8DbQZjmsxlUYR_q1cP8JKiIo6NNJ85g7sjTZgXxeanA9wZwqwJB-P98VdVslC17PmVu0RHOqRtxrht7OFT7Z10ecz0tj9ODXrv5nmBktmbgHRirRMl84wp7-PJhTXdHbxZv-OoL4HP6FxyDbHxLB7QmR4-VoEZN0vsybb1A8KEj2pkNY_tmxHH6k87euM99bB8FHrW9FNrXCGL1p6-PYtiky52a5YQZGT8Hz-ZnxobTmhjZXJ0SW5mb1ih_1RDR4AXACIAC7xZ9N_ZpqQtw7hmr_LfDRmCa78BS2erCtbrsXYwa4AHABSsnz8FacZi-wkUkfHu4xjG8MPfmwAAAAGxWkjHaED549jznwUBqeDEpT-7xBMAIgALcSGuv6a5r9BwMvQvCSXg7GdAjdWZpXv6D4DH8VYBCE8AIgALAVI0eQ_AAZjNvrhUEMK2q4wxuwIFOnHIDF0Qljhf47RncHViQXJlYVkBNgABAAsABgRyACCd_8vzbDg65pn7mGjcbcuJ1xU4hL4oA5IsEkFYv60irgAQABAIAAAAAAABAMXab02TV73iAvXFWM0KMVbSVPLgrZq1eTH5gmt0feGsTynWBwh03OV5EOGYREmdjkJHAzmxcNAitQGriOnC9O0wLkcZxw3r6IQkA-2b38InMKYaG3D2FsXxtwDKz3hGE33EstRpqOFaq0-thlcIQCLSj0TZB1MjEmtwB8mBk5_fckyvT75HUEBDGk6gZEMLyyz619Bb259ktbDglS7PhnknPWxt-oFgHxRQMxahPQeCwxo-a93tPXvEa8H6m-8N_4O33q8Ua1gsRkSCGjxi7bqmvkIr8E5D7a9f03gwhhU9c2GiAwYaYpirJuEzfKHJ7QZ0GlkFR3mI5yAwTq4YnX8"
    }
};

2.3.3 Android Keyアテステーション例

サーバは、[WebAuthn]に定義されている「Validation Procedure」に従ってAndroid Keyアテステーションを検証する必要がある(SHOULD)。

ISSUE 1Android Keyアテステーションの例が必要

Android Keyアテステーション例を記載する必要がある。

2.3.4 Android SafetyNetアテステーション例

サーバは、[WebAuthn]に定義されている「Validation Procedure」に従ってAndroid SafetyNetアテステーションを検証しなければならない(MUST)。

EXAMPLE 3

{
    "rawId": "qCXEfJ-dEoBlWqIl0iq2p_gj13HSg7r_MA7xOcOiO8RkCrYNmQHIjV9yhZVASr87cUsflo7DNuuvGsnrlTl1ig",
    "id": "qCXEfJ-dEoBlWqIl0iq2p_gj13HSg7r_MA7xOcOiO8RkCrYNmQHIjV9yhZVASr87cUsflo7DNuuvGsnrlTl1ig",
    "response": {
        "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJEa1hCdWRCa2wzTzBlTUV5SGZBTVgxT2tRbHV4c2hjaW9WU3dITVJMUlhtd044SXJldHg3cWJ0MWx3Y0p4d0FxWUU0SUxTZjVwd3lHMEhXSWtEekVMUT09Iiwib3JpZ2luIjoid2ViYXV0aG4ub3JnIiwiaGFzaEFsZyI6IlNIQS0yNTYifQ",
        "attestationObject": "o2hhdXRoRGF0YVjElWkIjx7O4yMpVANdvRDXyuORMFonUbVZu4_Xy7IpvdRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKglxHyfnRKAZVqiJdIqtqf4I9dx0oO6_zAO8TnDojvEZAq2DZkByI1fcoWVQEq_O3FLH5aOwzbrrxrJ65U5dYqlAQIDJiABIVggh5OJfYRDzVGIowKqU57AnoVjjdmmjGi9zlMkjAVV9DAiWCDr0iSi0viIKNPMTIdN28gWNmkcwOr6DQx66MPff3Odm2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaDEyNjg1MDIzaHJlc3BvbnNlWRSnZXlKaGJHY2lPaUpTVXpJMU5pSXNJbmcxWXlJNld5Sk5TVWxGYVdwRFEwRXpTMmRCZDBsQ1FXZEpTVmxyV1c4MVJqQm5PRFpyZDBSUldVcExiMXBKYUhaalRrRlJSVXhDVVVGM1ZrUkZURTFCYTBkQk1WVkZRbWhOUTFaV1RYaElha0ZqUW1kT1ZrSkJiMVJHVldSMllqSmtjMXBUUWxWamJsWjZaRU5DVkZwWVNqSmhWMDVzWTNwRmJFMURUVWRCTVZWRlFYaE5ZMUl5T1haYU1uaHNTVVZzZFdSSFZubGliVll3U1VWR01XUkhhSFpqYld3d1pWTkNTRTE2UVdWR2R6QjRUbnBGZVUxRVVYaE5la1UwVGtST1lVWjNNSGhQUkVWNVRVUk5kMDFFUVhkTlJFSmhUVWQzZUVONlFVcENaMDVXUWtGWlZFRnNWbFJOVWsxM1JWRlpSRlpSVVVsRVFYQkVXVmQ0Y0ZwdE9YbGliV3hvVFZKWmQwWkJXVVJXVVZGSVJFRXhUbUl6Vm5Wa1IwWndZbWxDVjJGWFZqTk5VazEzUlZGWlJGWlJVVXRFUVhCSVlqSTVibUpIVldkVFZ6VnFUVkp6ZDBkUldVUldVVkZFUkVKS2FHUklVbXhqTTFGMVdWYzFhMk50T1hCYVF6VnFZakl3ZDJkblJXbE5RVEJIUTFOeFIxTkpZak5FVVVWQ1FWRlZRVUUwU1VKRWQwRjNaMmRGUzBGdlNVSkJVVU5WYWpoM1dXOVFhWGhMWW1KV09ITm5XV2QyVFZSbVdDdGtTWE5HVkU5clowdFBiR2hVTUdrd1ltTkVSbHBMTW5KUGVFcGFNblZUVEZOV2FGbDJhWEJhVGtVelNFcFJXWFYxV1hkR2FtbDVLM2xyWm1GMFFVZFRhbEo2UmpGaU16RjFORE12TjI5SE5XcE5hRE5UTXpkaGJIZHFWV0k0UTFkcFZIaHZhWEJXVDFsM1MwdDZkVlY1YTNGRlEzUnFiR2hLTkVGclYyRkVVeXRhZUV0RmNVOWhaVGwwYmtOblpVaHNiRnBGTDA5U1oyVk5ZWGd5V0U1RGIwZzJjM0pVUlZKamEzTnFlbHBhY2tGWGVFdHpaR1oyVm5KWVRucERVamxFZUZaQlUzVkpOa3g2ZDJnNFJGTnNNa1ZQYjJ0aWMyRnVXaXNyTDBweFRXVkJRa1ptVUhkcWVYZHlZakJ3Y2tWVmVUQndZV1ZXYzNWa0t6QndaV1Y0U3k4MUswVTJhM0JaUjBzMFdrc3libXR2Vmt4MVowVTFkR0ZJY2tGcU9ETlJLMUJQWW1KMlQzcFhZMFpyY0c1V1MzbHFielpMVVVGdFdEWlhTa0ZuVFVKQlFVZHFaMmRHUjAxSlNVSlJha0ZVUW1kT1ZraFRWVVZFUkVGTFFtZG5ja0puUlVaQ1VXTkVRVlJCWkVKblRsWklVa1ZGUm1wQlZXZG9TbWhrU0ZKc1l6TlJkVmxYTld0amJUbHdXa00xYW1JeU1IZGhRVmxKUzNkWlFrSlJWVWhCVVVWRldFUkNZVTFETUVkRFEzTkhRVkZWUmtKNlFVTm9hVVp2WkVoU2QwOXBPSFpqUjNSd1RHMWtkbUl5WTNaYU0wNTVUV2s1U0ZaR1RraFRWVVpJVFhrMWFtTnVVWGRMVVZsSlMzZFpRa0pSVlVoTlFVZEhTRmRvTUdSSVFUWk1lVGwyV1ROT2QweHVRbkpoVXpWdVlqSTVia3d3WkZWVk1HUktVVlZqZWsxQ01FZEJNVlZrUkdkUlYwSkNVVWM0U1hKUmRFWlNOa05WVTJ0cGEySXpZV2x0YzIweU5tTkNWRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRVTFDT0VkQk1WVmtTWGRSV1UxQ1lVRkdTR1pEZFVaRFlWb3pXakp6VXpORGFIUkRSRzlJTm0xbWNuQk1UVU5GUjBFeFZXUkpRVkZoVFVKbmQwUkJXVXRMZDFsQ1FrRklWMlZSU1VaQmVrRkpRbWRhYm1kUmQwSkJaMGwzVFZGWlJGWlNNR1pDUTI5M1MwUkJiVzlEVTJkSmIxbG5ZVWhTTUdORWIzWk1NazU1WWtNMWQyRXlhM1ZhTWpsMlduazVTRlpHVGtoVFZVWklUWGsxYW1OdGQzZEVVVmxLUzI5YVNXaDJZMDVCVVVWTVFsRkJSR2RuUlVKQlJpOVNlazV1UXpWRWVrSlZRblJ1YURKdWRFcE1WMFZSYURsNlJXVkdXbVpRVERsUmIydHliRUZ2V0dkcVYyZE9PSEJUVWxVeGJGWkhTWEIwZWsxNFIyaDVNeTlQVWxKYVZHRTJSREpFZVRob2RrTkVja1pKTXl0c1Exa3dNVTFNTlZFMldFNUZOVkp6TW1ReFVtbGFjRTF6ZWtRMFMxRmFUa2N6YUZvd1FrWk9VUzlqYW5KRGJVeENUMGRMYTBWVk1XUnRRVmh6UmtwWVNtbFBjakpEVGxSQ1QxUjFPVVZpVEZkb1VXWmtRMFl4WW5kNmVYVXJWelppVVZOMk9GRkVialZQWkUxVEwxQnhSVEZrUldkbGRDODJSVWxTUWpjMk1VdG1XbEVyTDBSRk5reHdNMVJ5V2xSd1QwWkVSR2RZYUN0TVowZFBjM2RvUld4cU9XTXpkbHBJUjBwdWFHcHdkRGh5YTJKcGNpOHlkVXhIWm5oc1ZsbzBTekY0TlVSU1RqQlFWVXhrT1hsUVUyMXFaeXRoYWpFcmRFaDNTVEZ0VVcxYVZsazNjWFpQTlVSbmFFOTRhRXBOUjJ4Nk5teE1hVnB0ZW05blBTSXNJazFKU1VWWVJFTkRRVEJUWjBGM1NVSkJaMGxPUVdWUGNFMUNlamhqWjFrMFVEVndWRWhVUVU1Q1oydHhhR3RwUnpsM01FSkJVWE5HUVVSQ1RVMVRRWGRJWjFsRVZsRlJURVY0WkVoaVJ6bHBXVmQ0VkdGWFpIVkpSa3AyWWpOUloxRXdSV2RNVTBKVFRXcEZWRTFDUlVkQk1WVkZRMmhOUzFJeWVIWlpiVVp6VlRKc2JtSnFSVlJOUWtWSFFURlZSVUY0VFV0U01uaDJXVzFHYzFVeWJHNWlha0ZsUm5jd2VFNTZRVEpOVkZWM1RVUkJkMDVFU21GR2R6QjVUVlJGZVUxVVZYZE5SRUYzVGtSS1lVMUdVWGhEZWtGS1FtZE9Wa0pCV1ZSQmJGWlVUVkkwZDBoQldVUldVVkZMUlhoV1NHSXlPVzVpUjFWblZraEtNV016VVdkVk1sWjVaRzFzYWxwWVRYaEtWRUZxUW1kT1ZrSkJUVlJJUldSMllqSmtjMXBUUWtwaWJsSnNZMjAxYkdSRFFrSmtXRkp2WWpOS2NHUklhMmRTZWsxM1oyZEZhVTFCTUVkRFUzRkhVMGxpTTBSUlJVSkJVVlZCUVRSSlFrUjNRWGRuWjBWTFFXOUpRa0ZSUkV0VmEzWnhTSFl2VDBwSGRXOHlia2xaWVU1V1YxaFJOVWxYYVRBeFExaGFZWG8yVkVsSVRFZHdMMnhQU2lzMk1EQXZOR2hpYmpkMmJqWkJRVUl6UkZaNlpGRlBkSE0zUnpWd1NEQnlTbTV1VDBaVlFVczNNVWMwYm5wTFRXWklRMGRWYTNOWEwyMXZibUVyV1RKbGJVcFJNazRyWVdsamQwcExaWFJRUzFKVFNXZEJkVkJQUWpaQllXaG9PRWhpTWxoUE0yZzVVbFZyTWxRd1NFNXZkVUl5Vm5wNGIwMVliR3Q1VnpkWVZWSTFiWGMyU210TVNHNUJOVEpZUkZadlVsUlhhMDUwZVRWdlEwbE9USFpIYlc1U2Mwb3hlbTkxUVhGWlIxWlJUV012TjNONUt5OUZXV2hCVEhKV1NrVkJPRXRpZEhsWUszSTRjMjUzVlRWRE1XaFZjbmRoVnpaTlYwOUJVbUU0Y1VKd1RsRmpWMVJyWVVsbGIxbDJlUzl6UjBsS1JXMXFVakIyUmtWM1NHUndNV05UWVZkSmNqWXZOR2MzTW00M1QzRllkMlpwYm5VM1dsbFhPVGRGWm05UFUxRktaVUY2UVdkTlFrRkJSMnBuWjBWNlRVbEpRa3g2UVU5Q1owNVdTRkU0UWtGbU9FVkNRVTFEUVZsWmQwaFJXVVJXVWpCc1FrSlpkMFpCV1VsTGQxbENRbEZWU0VGM1JVZERRM05IUVZGVlJrSjNUVU5OUWtsSFFURlZaRVYzUlVJdmQxRkpUVUZaUWtGbU9FTkJVVUYzU0ZGWlJGWlNNRTlDUWxsRlJraG1RM1ZHUTJGYU0xb3ljMU16UTJoMFEwUnZTRFp0Wm5Kd1RFMUNPRWRCTVZWa1NYZFJXVTFDWVVGR1NuWnBRakZrYmtoQ04wRmhaMkpsVjJKVFlVeGtMMk5IV1ZsMVRVUlZSME5EYzBkQlVWVkdRbmRGUWtKRGEzZEtla0ZzUW1kbmNrSm5SVVpDVVdOM1FWbFpXbUZJVWpCalJHOTJUREk1YW1NelFYVmpSM1J3VEcxa2RtSXlZM1phTTA1NVRXcEJlVUpuVGxaSVVqaEZTM3BCY0UxRFpXZEtZVUZxYUdsR2IyUklVbmRQYVRoMldUTktjMHh1UW5KaFV6VnVZakk1Ymt3eVpIcGpha2wyV2pOT2VVMXBOV3BqYlhkM1VIZFpSRlpTTUdkQ1JHZDNUbXBCTUVKbldtNW5VWGRDUVdkSmQwdHFRVzlDWjJkeVFtZEZSa0pSWTBOQlVsbGpZVWhTTUdOSVRUWk1lVGwzWVRKcmRWb3lPWFphZVRsNVdsaENkbU15YkRCaU0wbzFUSHBCVGtKbmEzRm9hMmxIT1hjd1FrRlJjMFpCUVU5RFFWRkZRVWhNWlVwc2RWSlVOMkoyY3pJMlozbEJXamh6YnpneGRISlZTVk5rTjA4ME5YTnJSRlZ0UVdkbE1XTnVlR2hITVZBeVkwNXRVM2hpVjNOdmFVTjBNbVYxZURsTVUwUXJVRUZxTWt4SldWSkdTRmN6TVM4MmVHOXBZekZyTkhSaVYxaHJSRU5xYVhJek4zaFVWRTV4VWtGTlVGVjVSbEpYVTJSMmRDdHViRkJ4ZDI1aU9FOWhNa2t2YldGVFNuVnJZM2hFYWs1VFpuQkVhQzlDWkRGc1drNW5aR1F2T0dOTVpITkZNeXQzZVhCMVprbzVkVmhQTVdsUmNHNW9PWHBpZFVaSmQzTkpUMDVIYkRGd00wRTRRMmQ0YTNGSkwxVkJhV2d6U21GSFQzRmpjR05rWVVOSmVtdENZVkk1ZFZsUk1WZzBhekpXWnpWQlVGSk1iM1Y2Vm5rM1lUaEpWbXMyZDNWNU5uQnRLMVEzU0ZRMFRGazRhV0pUTlVaRldteG1RVVpNVTFjNFRuZHpWbm81VTBKTE1sWnhiakZPTUZCSlRXNDFlRUUyVGxwV1l6ZHZPRE0xUkV4QlJuTm9SVmRtUXpkVVNXVXpaejA5SWwxOS5leUp1YjI1alpTSTZJbXhYYTBscWVEZFBOSGxOY0ZaQlRtUjJVa1JZZVhWUFVrMUdiMjVWWWxaYWRUUXZXSGszU1hCMlpGSkJRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVUZCUVVGQlFVRkJRVkZMWjJ4NFNIbG1ibEpMUVZwV2NXbEtaRWx4ZEhGbU5FazVaSGd3YjA4MkwzcEJUemhVYmtSdmFuWkZXa0Z4TWtSYWEwSjVTVEZtWTI5WFZsRkZjUzlQTTBaTVNEVmhUM2Q2WW5KeWVISktOalZWTldSWmNXeEJVVWxFU21sQlFrbFdaMmRvTlU5S1psbFNSSHBXUjBsdmQwdHhWVFUzUVc1dlZtcHFaRzF0YWtkcE9YcHNUV3RxUVZaV09VUkJhVmREUkhJd2FWTnBNSFpwU1V0T1VFMVVTV1JPTWpoblYwNXRhMk4zVDNJMlJGRjROalpOVUdabU0wOWtiU3QxTm1WS2NVeENiREZJTWxNeWRISkJRa2hNYVc1cmJuTjVWazFRYlM5Q1RsVldXakpLUm14eU9EQWlMQ0owYVcxbGMzUmhiWEJOY3lJNk1UVXlPRGt4TVRZek5ETTROU3dpWVhCclVHRmphMkZuWlU1aGJXVWlPaUpqYjIwdVoyOXZaMnhsTG1GdVpISnZhV1F1WjIxeklpd2lZWEJyUkdsblpYTjBVMmhoTWpVMklqb2lTazlETTFWcmMyeHpkVlo2TVRObFQzQnVSa2s1UW5CTWIzRkNaemxyTVVZMlQyWmhVSFJDTDBkcVRUMGlMQ0pqZEhOUWNtOW1hV3hsVFdGMFkyZ2lPbVpoYkhObExDSmhjR3REWlhKMGFXWnBZMkYwWlVScFoyVnpkRk5vWVRJMU5pSTZXeUpIV0ZkNU9GaEdNM1pKYld3ekwwMW1ibTFUYlhsMVMwSndWRE5DTUdSWFlraFNVaTgwWTJkeEsyZEJQU0pkTENKaVlYTnBZMGx1ZEdWbmNtbDBlU0k2Wm1Gc2MyVXNJbUZrZG1salpTSTZJbEpGVTFSUFVrVmZWRTlmUmtGRFZFOVNXVjlTVDAwc1RFOURTMTlDVDA5VVRFOUJSRVZTSW4wLmlDRjZEMm9zOERZdURWT250M3pESkIybVNYblpqdFdKdGxfanpTRHg1TXJSQzlBMmZtRkJaNno1a3BRWjJNaVE3b290ajlXa0hNZ3hxSWhyWDNkbGgyUE9IQXdrSVMzNHlTakxWTnNTUHByRTg0ZVpncVNGTE1FWVQwR1IyZVZMSEFNUE44bjVSOEs2YnVET0dGM25TaTZHS3pHNTdabGw4Q1NvYjJ5aUFTOXI3c3BkQTZIMFRESC1OR3pTZGJNSUlkOGZaRDFkekZLTlFyNzdiNmxiSUFGZ1FiUlpCcm5wLWUtSDRpSDZkMjFvTjJOQVlSblI1WVVSYWNQNmtHR2oyY0Z4c3dFMjkwOHd4djloaVlOS05vamVldThYYzRJdDdQYmhsQXVPN3l3aFFGQTgxaVBDQ0ZtMTFCOGNmVVhiV0E4bF8ydHROUEJFTUdNNi1aNlZ5UQ"
    }
};

2.3.5 U2Fアテステーション

サーバは、[WebAuthn]に定義されている「Validation Procedure」に従ってU2Fアテステーションを検証しなければならない(MUST)。

EXAMPLE 4

{
    "rawId": "Bo-VjHOkJZy8DjnCJnIc0Oxt9QAz5upMdSJxNbd-GyAo6MNIvPBb9YsUlE0ZJaaWXtWH5FQyPS6bT_e698IirQ==",
    "id": "Bo-VjHOkJZy8DjnCJnIc0Oxt9QAz5upMdSJxNbd-GyAo6MNIvPBb9YsUlE0ZJaaWXtWH5FQyPS6bT_e698IirQ==",
    "response": {
        "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAO-683ISJhKdmUPmVbQuYZsp8lkD7YJcInHS3QOfbrioAiEAzgMJ499cBczBw826r1m55Jmd9mT4d1iEXYS8FbIn8MpjeDVjgVkCSDCCAkQwggEuoAMCAQICBFVivqAwCwYJKoZIhvcNAQELMC4xLDAqBgNVBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgwMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAqMSgwJgYDVQQDDB9ZdWJpY28gVTJGIEVFIFNlcmlhbCAxNDMyNTM0Njg4MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESzMfdz2BRLmZXL5FhVF-F1g6pHYjaVy-haxILIAZ8sm5RnrgRbDmbxMbLqMkPJH9pgLjGPP8XY0qerrnK9FDCaM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwCwYJKoZIhvcNAQELA4IBAQCsFtmzbrazqbdtdZSzT1n09z7byf3rKTXra0Ucq_QdJdPnFhTXRyYEynKleOMj7bdgBGhfBefRub4F226UQPrFz8kypsr66FKZdy7bAnggIDzUFB0-629qLOmeOVeAMmOrq41uxICn3whK0sunt9bXfJTD68CxZvlgV8r1_jpjHqJqQzdio2--z0z0RQliX9WvEEmqfIvHaJpmWemvXejw1ywoglF0xQ4Gq39qB5CDe22zKr_cvKg1y7sJDvHw2Z4Iab_p5WdkxCMObAV3KbAQ3g7F-czkyRwoJiGOqAgau5aRUewWclryqNled5W8qiJ6m5RDIMQnYZyq-FTZgpjXaGF1dGhEYXRhWMRJlg3liA6MaHQ0Fw9kdmBbj-SuuaKGMseZXPO6gx2XY0EAAAAAAAAAAAAAAAAAAAAAAAAAAABABo-VjHOkJZy8DjnCJnIc0Oxt9QAz5upMdSJxNbd-GyAo6MNIvPBb9YsUlE0ZJaaWXtWH5FQyPS6bT_e698IiraUBAgMmIAEhWCA1c9AIeH5sN6x1Q-2qR7v255tkeGbWs0ECCDw35kJGBCJYIBjTUxruadjFFMnWlR5rPJr23sBJT9qexY9PCc9o8hmT",
        "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJWdTh1RHFua3dPamQ4M0tMajZTY24yQmdGTkxGYkdSN0txX1hKSndRbm5hdHp0VVI3WElCTDdLOHVNUENJYVFtS3cxTUNWUTVhYXpOSkZrN05ha2dxQSIsImNsaWVudEV4dGVuc2lvbnMiOnt9LCJoYXNoQWxnb3JpdGhtIjoiU0hBLTI1NiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0Ojg0NDMiLCJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0="
    }
};

3 認証とアサーション

サーバは認証をサポートするものとする(SHALL)。

サーバは認証リクエストごとにランダムなチャレンジを利用するものとする(SHALL)。チャレンジのランダム性の定義はこの仕様書の範囲外(詳細については[FIDOSecRef]を参照)ではあるが、同じチャレンジや単調増加するチャレンジ、その他単純なチャンレジを利用することは受け入れられず安全ではない。チャレンジの生成には暗号論的乱数生成器を利用することが期待される。

サーバはアサーション署名を検証するものとする(SHALL)。

アサーションレスポンスを受け取った際に、サーバは[WebAuthn]で定義されている手順でアサーションレスポンスを検証するものとする(SHALL)。

サーバはTUPおよびまたはその他ユーザ認証を検証するものとする(SHALL)。

4 コミュニケーションチャンネルの必須要件

サーバがTLSおよびトークバインディングを利用できるように実装している場合、[TokenBindingOverHttp]を利用して[TokenBindingProtocol]を実装する必要がある(SHOULD).

5 拡張機能

サーバは、拡張機能が存在しなくても登録および認証を実行できる動作モードを用意しなければならない(MUST)。ただし、本番環境にデプロイするときにそのように構築する必要があるという要件はない。

サーバは拡張機能をサポートしてもよい(MAY)。

サーバは、FIDO U2Fとの後方互換性のため、[WebAuthn]をサポートする必要がある(SHOULD)。ブラウザ、プラットフォームおよびその他クライアントが拡張機能をサポートしている可能性、サポートしていない可能性があることに注意する。

サーバが新しい拡張機能を実装した場合、[WebAuthn-Registries]レジストリに登録する必要がある(SHOULD)。

6 その他

[WebAuthn] セクション5.3.5のセキュリティ要件を遵守する必要がある。

signaturerawData属性から算出される。

サーバは、以下にRequiredと記載されたアルゴリズムを実装しなければならず(MUST)、RecommendedおよびOptionalと記載されたアルゴリズムを実装してもよい(MAY)。また、サーバはその他アルゴリズムを実装しても良い(MAY)。

名称:RS1

  • :未定(-65535の割当を要求)
  • 説明:RSASSA-PKCS1-v1_5 w/ SHA-1
  • 参照:[RFC8017] セクション8.2
  • ステータス:Required

名称:RS256

  • :未定(-257の割当を要求)
  • 説明:RSASSA-PKCS1-v1_5 w/ SHA-256
  • 参照:[RFC8017] セクション8.2
  • ステータス:Required

名称:RS384

  • :未定(-258割当を要求)
  • 説明:RSASSA-PKCS1-v1_5 w/ SHA-384
  • 参照:[RFC8017] セクション8.2
  • ステータス:Optional

名称:RS512

  • :未定(-259の割当を要求)
  • 説明:RSASSA-PKCS1-v1_5 w/ SHA-512
  • 参照:[RFC8017] セクション8.2
  • ステータス:Optional

名称:PS256

  • :-37
  • 説明:RSASSA-PSS w/ SHA-256
  • 参照:[RFC8230]
  • ステータス:Optional

名称:PS384

  • :-38
  • 説明:RSASSA-PSS w/ SHA-384
  • 参照:[RFC8230]
  • ステータス:Optional

名称:PS512

  • :-39
  • 説明:RSASSA-PSS w/ SHA-512
  • 参照:[RFC8230]
  • ステータス:Optional

名称:ES256

  • :-7
  • 説明:ECDSA using P-256 and SHA-256
  • 参照:[RFC8152]
  • ステータス:Required

名称:ES384

  • :-35
  • 説明:ECDSA using P-384 and SHA-384
  • 参照:[RFC8152]
  • ステータス:Recommended

名称:ES512

  • :-36
  • 説明:ECDSA using P-512 and SHA-512
  • 参照:[RFC8152]
  • ステータス:Optional

名称:EdDSA

  • :-8
  • 説明:EdDSA signature algorithms
  • 参照:[RFC8037]
  • ステータス:Recommended

名称:ES256K

  • :未定(-43の割当を要求)
  • 説明:ECDSA using P-256K and SHA-256
  • 参照:[SEC2V2]
  • ステータス:Optional

サーバは、以下にRequiredと記載された曲線を実装しなければならず(MUST)、RecommendedおよびOptionalと記載された曲線を実装してもよい(MAY)。また、サーバはその他の曲線を実装しても良い(MAY)。

名称:P-256

  • :1
  • 説明:EC2 NIST P-256 also known as secp256r1
  • 参照:[RFC8152]
  • ステータス:Required

名称:P-384

  • :2
  • 説明:EC2 NIST P-384 also known as secp384r1
  • 参照:[RFC8152]
  • ステータス:Recommended

名称:P-512

  • :3
  • 説明:EC2 NIST P-512 also known as secp512r1
  • 参照:[RFC8152]
  • ステータス:Optional

名称:Ed25519

  • :6
  • 説明:Edwards-curve Digital Signature Algorithm on curve 25519
  • 参照: [RFC8032]
  • ステータス:Recommended

名称:Ed448

  • :7
  • 説明:Edwards-curve Digital Signature Algorithm on curve 448
  • 参照:[RFC8032]
  • ステータス:Optional

名称:P-256K

  • :未定(8の割当を要求)
  • 説明:SECG secp256k1 curve
  • 参照:[SEC2V2]
  • ステータス:Optional

書面の都合上、この仕様書を記述している時点で実際に認証器によって利用されているアルゴリズムおよび曲線だけが、Requiredのリストに含まれている。将来の暗号論開発の可能性に先行して備えたいサーバは、必須のアルゴリズムと曲線に加え、Recommendedとなっているアルゴリズムおよび曲線を実装することを検討するべきです。

サーバはFIDOプライバシー指針 [FIDOPrivacyPrinciples]を遵守しなければなりません(MUST)。

7 トランスポートバインディングプロフィール

このセクションは非標準です。

7.1 目次

  • イントロダクション
  • 登録
    • 概要
      • Credential Creation Options
      • Authenticator Attestation Response
    • Primary IDL
      • ServerPublicKeyCredentialCreationOptionsRequest
      • ServerPublicKeyCredentialCreationOptionsResponse
      • ServerAuthenticatorAttestationResponse
    • Supporting IDL
      • ServerPublicKeyCredential
      • ServerPublicKeyCredentialUserEntity
      • ServerPublicKeyCredentialDescriptor
  • 認証
    • 概要
      • Credential Get Options
      • Authenticator Assertion Response
    • IDL
      • ServerPublicKeyCredentialGetOptionsRequest
      • ServerPublicKeyCredentialGetOptionsResponse
      • ServerAuthenticatorAssertionResponse
  • 共通
    • IDL
      • ServerResponse

7.2 イントロダクション

このドキュメントは、FIDO2サーバ向けの非標準的なREST APIを含む。このインタフェースは必須ではないが、FIDO2適合性テストツールには利用されているインタフェースであり、サーバは標準的な方法でメッセージを送受信してそれらメッセージが適合性テストツールによって検証される。

FIDO2仕様と同様に、ここで記述しているインタフェースは[WebAuthn]仕様に大きく依存している。このドキュメントの命名方法はWebAuthnに従っており、サーバと送受信するメッセージを定義するためにインタフェース定義言語(IDL)を再度利用している。

このドキュメントは、登録、認証そして共通という3つのセクションに分割されている。登録および認証のセクションではこれらの処理に関するメッセージが含まれており、共通セクションでは登録と認証の両方に共通するメッセージとデータフォーマットを含んでいる。

7.3 登録

このセクションは、クライアントとサーバ間で交換する登録メッセージの簡単な概要、それらメッセージの例とメッセージのIDL定義を記載している。WebAuthnの命名方法にしたがって「credential creation」と参照されることもあるに注意する。

7.3.1 登録の概要

登録の手順は2つのステップになっており、4種類のメッセージが送信される。最初のステップでは、クライアントが「Credential Creation Options」を受け取る。これにはクライアントがサーバに送ったServerPublicKeyCredentialCreationOptionsRequestとサーバが受け取ったServerPublicKeyCredentialCreationOptionsResponseを関連する。このはWebAuthnのnavigator.credentials.create()に利用されることを意図しており、特にサーバによって必ず生成されるチャレンジは中間者攻撃(MITM)対策のために重要である。navigator.credentials.create()の完了時に、呼び出し関数から生成されたディクショナリをServerAuthenticatorAttestationResponseresponseフィールドに設定したServerPublicKeyCredentialとしてサーバに送り返す。ServerAuthenticatorAttestationResponseは以下の共通セクションに記述している一般的なServerAuthenticatorResponseを拡張していることに注意する。サーバはチャレンジ、オリジン、署名そして[WebAuthn]仕様書のセクション7.1に記載されているアルゴリズムに従ってServerAuthenticatorAttestationResponseのその他の属性を検証し、適切なServerResponseメッセージを返却する。

7.3.2 例

7.3.2.1 例:Credential Creation Options

リクエス

  • URL:/attestation/options
  • メソッドPOST
  • URLパラメータ:なし
  • ボディapplication/jsonフォーマットのServerPublicKeyCredentialCreationOptionsRequest
    {
        "username": "johndoe@example.com",
        "displayName": "John Doe",
        "authenticatorSelection": {
            "residentKey": false,
            "authenticatorAttachment": "cross-platform",
            "userVerification": "preferred"
        },
        "attestation": "direct"
    }

成功レスポンス

    {
        "status": "ok",
        "errorMessage": "",
        "rp": {
            "name": "Example Corporation"
        },
        "user": {
            "id": "S3932ee31vKEC0JtJMIQ",
            "name": "johndoe@example.com",
            "displayName": "John Doe"
        },

        "challenge": "uhUjPNlZfvn7onwuhNdsLPkkE5Fv-lUN",
        "pubKeyCredParams": [
            {
                "type": "public-key",
                "alg": -7
            }
        ],
        "timeout": 10000,
        "excludeCredentials": [
            {
                "type": "public-key",
                "id": "opQf1WmYAa5aupUKJIQp"
            }
        ],
        "authenticatorSelection": {
            "residentKey": false,
            "authenticatorAttachment": "cross-platform",
            "userVerification": "preferred"
        },
        "attestation": "direct"
    }

エラーレスポンス

    {
        "status": "failed",
        "errorMessage": "Missing challenge field!"
    }

JavaScriptサンプル

    fetch('/attestation/options', {
        method  : 'POST',
        credentials : 'same-origin',
        headers : {
            'Content-Type' : 'application/json'
        },
        body: JSON.stringify({
            "username": "johndoe@example.com",
            "displayName": "John Doe",
            "authenticatorSelection": {
                "residentKey": false,
                "authenticatorAttachment": "cross-platform",
                "userVerification": "preferred"
            },
            "attestation": "direct"
        })
    }).then(function (response) {
        return response.json();
    }).then(function (json) {
        console.log(json);
    }).catch(function (err) {
        console.log({ 'status': 'failed', 'error': err });
    })
7.3.2.2 例:Authenticator Attestaion Response

リクエス

  • URL:/attestation/result
  • メソッドPOST
  • URLパラメータ:なし
  • ボディapplication/jsonフォーマットのServerAuthenticatorAttestationResponseresponseフィールドに設定したServerPublicKeyCredential
    {
        "id": "LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
        "rawId": "LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
        "response": {
            "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
            "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"
        },
        "type": "public-key"
    }

成功レスポンス

    {
        "status": "ok",
        "errorMessage": "",
    }

エラーレスポンス

    {
        "status": "failed",
        "errorMessage": "Can not validate response signature!"
    }

呼び出しサンプル

    fetch('/attestation/result', {
        method  : 'POST',
        credentials : 'same-origin',
        headers : {
            'Content-Type' : 'application/json'
        },
        body: JSON.stringify({
            "id": "LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
            "rawId": "LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
            "response": {
                "clientDataJSON": "eyJjaGFsbGVuZ2UiOiJOeHlab3B3VktiRmw3RW5uTWFlXzVGbmlyN1FKN1FXcDFVRlVLakZIbGZrIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmNyZWF0ZSJ9",
                "attestationObject": "o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEcwRQIgVzzvX3Nyp_g9j9f2B-tPWy6puW01aZHI8RXjwqfDjtQCIQDLsdniGPO9iKr7tdgVV-FnBYhvzlZLG3u28rVt10YXfGN4NWOBWQJOMIICSjCCATKgAwIBAgIEVxb3wDANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLDEqMCgGA1UEAwwhWXViaWNvIFUyRiBFRSBTZXJpYWwgMjUwNTY5MjI2MTc2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZNkcVNbZV43TsGB4TEY21UijmDqvNSfO6y3G4ytnnjP86ehjFK28-FdSGy9MSZ-Ur3BVZb4iGVsptk5NrQ3QYqM7MDkwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjUwEwYLKwYBBAGC5RwCAQEEBAMCBSAwDQYJKoZIhvcNAQELBQADggEBAHibGMqbpNt2IOL4i4z96VEmbSoid9Xj--m2jJqg6RpqSOp1TO8L3lmEA22uf4uj_eZLUXYEw6EbLm11TUo3Ge-odpMPoODzBj9aTKC8oDFPfwWj6l1O3ZHTSma1XVyPqG4A579f3YAjfrPbgj404xJns0mqx5wkpxKlnoBKqo1rqSUmonencd4xanO_PHEfxU0iZif615Xk9E4bcANPCfz-OLfeKXiT-1msixwzz8XGvl2OTMJ_Sh9G9vhE-HjAcovcHfumcdoQh_WM445Za6Pyn9BZQV3FCqMviRR809sIATfU5lu86wu_5UGIGI7MFDEYeVGSqzpzh6mlcn8QSIZoYXV0aERhdGFYxEmWDeWIDoxodDQXD2R2YFuP5K65ooYyx5lc87qDHZdjQQAAAAAAAAAAAAAAAAAAAAAAAAAAAEAsV2gIUlPIHzZnNIlQdz5zvbKtpFz_WY-8ZfxOgTyy7f3Ffbolyp3fUtSQo5LfoUgBaBaXqK0wqqYO-u6FrrLApQECAyYgASFYIPr9-YH8DuBsOnaI3KJa0a39hyxh9LDtHErNvfQSyxQsIlgg4rAuQQ5uy4VXGFbkiAt0uwgJJodp-DymkoBcrGsLtkI"
            },
            "type": "public-key"
        })
    }).then(function (response) {
        return response.json();
    }).then(function (json) {
        console.log(json);
    }).catch(function (err) {
        console.log({ 'status': 'failed', 'error': err });
    })

7.3.3 登録Primary IDL

7.3.3.1 ServerPublicKeyCredentialCreationOptionsRequest
    dictionary ServerPublicKeyCredentialCreationOptionsRequest {
        required DOMString              username;
        required DOMString              displayName;
        AuthenticatorSelectionCriteria  authenticatorSelection;
        AttestationConveyancePreference attestation = "none";
    };
  • 必須 username - エンティティについての人間が読むことができる名称。例えば「alexm」、「alex.p.mueller@example.com」や「+14255551234」
  • 必須 displayName - ユーザアカウントに対する人間にとって読みやすい名称、表示のためだけに利用されることを想定。例えば「Alex P. Müller」、「田中 倫」
  • authenticatorSelection - WebAuthn仕様書に記載されているAuthenticatorSelectionCriteriaを含むディクショナリ
  • attestation - 「none」、「indirect」「direct」を設定することができる。更なる情報はWebAuthn仕様に記載されている。デフォルトは「none」が設定される。
7.3.3.2 ServerPublicKeyCredentialCreationOptionsResponse
    dictionary ServerPublicKeyCredentialCreationOptionsResponse : ServerResponse {
        required PublicKeyCredentialRpEntity         rp;
        required ServerPublicKeyCredentialUserEntity user;

        required DOMString                                challenge;
        required sequence<PublicKeyCredentialParameters>  pubKeyCredParams;

        unsigned long                                 timeout;
        sequence<ServerPublicKeyCredentialDescriptor> excludeCredentials = [];
        AuthenticatorSelectionCriteria               authenticatorSelection;
        AttestationConveyancePreference              attestation = "none";
        AuthenticationExtensionsClientInputs         extensions;
    };
  • 必須 rp - WebAuthn仕様書に記述されているPublicKeyCredentialRpEntityとして定義されたディクショナリ
  • 必須 user - 本ドキュメントに記述したServerPublicKeyCredentialUserEntityとして定義されたディクショナリ
  • 必須 challenge - 最低16バイト長、最大64バイト長のランダムなチャレンジをbase64urlエンコードした値
  • 必須 pubKeyCredParams - WebAuthn仕様書に記述されたPublicKeyCredentialParametersの配列
  • timeout - タイムアウト(ms)
  • excludeCredentials - 本ドキュメントに記述されたServerPublicKeyCredentialDescriptorの配列
  • authenticatorSelection - WebAuthn仕様書に記述されたAuthenticatorSelectionCriteriaのセット
  • attestation - 「none」、「indirect」「direct」を設定することができる。更なる情報はWebAuthn仕様に記載されている。デフォルトは「none」が設定される。
  • extensions - WebAuthn仕様書に記述された AuthenticationExtensionsClientInputsのセット
  • このドキュメントに記載したServerResponseを拡張している
7.3.3.3 ServerAuthenticatorAttestationResponse

一般的にWebAuthnに記述されているAuthenticatorAttestationResponseと同等だが、BufferSource型のフィールドはbase64urlエンコーディングされて利用される。

dictionary ServerAuthenticatorAttestationResponse : ServerAuthenticatorResponse {
    required DOMString      clientDataJSON;
    required DOMString      attestationObject;
};
  • 必須 clientDataJSON - base64urlエンコードされたclientDataJSONバッファ
  • 必須 attestationObject - base64urlエンコードされたattestationObjectバッファ

7.3.4 登録 Supporting IDL

7.3.4.1 ServerPublicKeyCredential

一般的にWebAuthnに記述されているPublicKeyCredentialと同等だが、WebAuthnでBufferSource型のフィールドはbase64urlエンコーディングされて利用される。

dictionary ServerPublicKeyCredential : Credential {
    required DOMString                    rawId;
    required ServerAuthenticatorResponse  response;
    AuthenticationExtensionsClientOutputs getClientExtensionResults;
};
  • 必須 id - この属性はCredentialから継承しているが、ServerPublicKeyCredentialでは認証器のcredIdのbase64urlエンコードされた値にオーバーライドされている
  • 必須 rawId - idと同等
  • 必須 response - 本ドキュメントに記述したServerAuthenticatorAttestationResponseまたはServerAuthenticatorAssertionResponseによって定義されるディクショナリ
  • 必須 type - この属性はCredentialから継承しているが、ServerPublicKeyCredentialでは「public-key」にオーバーライドされている
  • getClientExtensionResult - 拡張機能の識別子を含むマップ、拡張機能のクライアント拡張処理によって生成されたクライアント拡張出力エントリが含まれる
  • Credential Management API仕様書に記述されているCredentialを拡張している
7.3.4.2 ServerPublicKeyCredentialUserEntity

一般的にWebAuthnに記述されているPublicKeyCredentialUserEntityと同等だが、idBufferSourcebase64urlエンコーディングが代わりに利用される。

    dictionary ServerPublicKeyCredentialUserEntity : PublicKeyCredentialEntity {
        required DOMString   id;
        required DOMString   displayName;
    };
  • 必須 id - base64urlエンコードされたidバッファ
  • 必須 displayName - ユーザアカウントに対する人間にとって読みやすい名称、表示のためだけに利用されることを想定。例えば「Alex P. Müller」、「田中 倫」。ServerPublicKeyCredentialCreationOptionsRequest.displayNameに対応する。
  • WebAuthn仕様書に記述されているPublicKeyCredentialEntityを拡張している
7.3.4.3 ServerPublicKeyCredentialDescription

一般的にWebAuthnに記述されているPublicKeyCredentialDescriptorと同等だが、idBufferSourcebase64urlエンコーディングが代わりに利用される。

    dictionary ServerPublicKeyCredentialDescriptor {
        required PublicKeyCredentialType      type;
        required DOMString                    id;
        sequence<AuthenticatorTransport>      transports;
    };
  • 必須 type - WebAuthn仕様書に記述されたPublicKeyCredentialTypeとして定義されたディクショナリ
  • 必須 id - 呼び出し関数に参照される公開鍵クレデンシャルのクレデンシャルIDのbase64urlエンコードされた値
  • transports - WebAuthn仕様書に記述されたAuthenticatorTransportの配列

7.4 認証

このセクションはこの認証でサーバとやり取りするメッセージの概要から始め、続いてこれらのメッセージ例について示し、メッセージの特定のIDL定義を示す。「認証」とはWebAuthnで使われている用語集に従って「クレデンシャルの取得」、「クレデンシャルリクエスト」または「認証アサーションの取得」を指すことがあることに注意する。

7.4.1 認証の概要

登録について書かれた通信フローと同様に、認証フローはサーバと交換する4つのメッセージが必要である。メッセージの最初のペアは、クライアントからサーバへのServerPublicKeyCredentialGetOptionsRequestのフォーマットのリクエストと、サーバが対応した結果としてクライアントに返却するServerPublicKeyCredentialGetOptionsResponseである。このServerPublicKeyCredentialGetOptionsResponseはWebAuthnのnavigator.credentials.get()関数のパラメータとして利用されることを想定している。navigator.credentials.get()の結果はクライアントによってresponseフィールドにServerAuthenticatorAssertionResponseを設定したServerPublicKeyCredentialに整形され、サーバに送信する。サーバは[WebAuthn]仕様書のセクション7.2に従ってアサーションを検証し、結果に応じたServerResponseを返却する。

7.4.2 認証例

7.4.2.1 認証例:Credential Get Options

リクエス

  • URL:/attestation/options
  • メソッドPOST
  • URLパラメータ:なし
  • ボティapplication/jsonエンコードされたServerPublicKeyCredentialGetOptionsRequest
    {
        "username": "johndoe@example.com",
        "userVerification": "required"
    }

成功レスポンス

{
    "status": "ok",
    "errorMessage": "",
    "challenge": "6283u0svT-YIF3pSolzkQHStwkJCaLKx",
    "timeout": 20000,
    "rpId": "https://example.com",
    "allowCredentials": [
        {
            "id": "m7xl_TkTcCe0WcXI2M-4ro9vJAuwcj4m",
            "type": "public-key"
        }
    ],
    "userVerification": "required"
}

エラーレスポンス

    {
        "status": "failed",
        "errorMessage": "User does not exists!"
    }

呼び出しサンプル

    fetch('/attestation/options', {
        method  : 'POST',
        credentials : 'same-origin',
        headers : {
            'Content-Type' : 'application/json'
        },
        body: JSON.stringify({
            "username": "johndoe@example.com",
            "userVerification": "required"
        })
    }).then(function (response) {
        return response.json();
    }).then(function (json) {
        console.log(json);
    }).catch(function (err) {
        console.log({ 'status': 'failed', 'error': err });
    })
7.4.2.2 認証例:Authenticator Assertion Response

リクエス

  • URL:/assertion/result
  • メソッドPOST
  • URLパラメータ:なし
  • ボディresponseフィールドにServerAuthenticatorAssertionResponseを設定されているapplication/jsonエンコードされたServerPublicKeyCredential
    {
        "id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
        "rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
        "response":{
            "authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAAA",
            "signature":"MEYCIQCv7EqsBRtf2E4o_BjzZfBwNpP8fLjd5y6TUOLWt5l9DQIhANiYig9newAJZYTzG1i5lwP-YQk9uXFnnDaHnr2yCKXL",
            "userHandle":"",
            "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4ZGowQ0JmWDY5MnFzQVRweTBrTmM4NTMzSmR2ZExVcHFZUDh3RFRYX1pFIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9"
        },
        "type":"public-key"
    }

成功レスポンス

{
    "status": "ok",
    "errorMessage": ""
}

エラーレスポンス

    {
        "status": "failed",
        "errorMessage": "Can not validate response signature!"
    }

呼び出しサンプル

    fetch('/assertion/result', {
        method  : 'POST',
        credentials : 'same-origin',
        headers : {
            'Content-Type' : 'application/json'
        },
        body: JSON.stringify({
            "id":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
            "rawId":"LFdoCFJTyB82ZzSJUHc-c72yraRc_1mPvGX8ToE8su39xX26Jcqd31LUkKOS36FIAWgWl6itMKqmDvruha6ywA",
            "response":{
                "authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAAA",
                "signature":"MEYCIQCv7EqsBRtf2E4o_BjzZfBwNpP8fLjd5y6TUOLWt5l9DQIhANiYig9newAJZYTzG1i5lwP-YQk9uXFnnDaHnr2yCKXL",
                "userHandle":"",
                "clientDataJSON":"eyJjaGFsbGVuZ2UiOiJ4ZGowQ0JmWDY5MnFzQVRweTBrTmM4NTMzSmR2ZExVcHFZUDh3RFRYX1pFIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9"
            },
            "type":"public-key"
        })
    }).then(function (response) {
        return response.json();
    }).then(function (json) {
        console.log(json);
    }).catch(function (err) {
        console.log({ 'status': 'failed', 'error': err });
    })

7.4.3 Authenticator IDL

7.4.3.1 ServerPublicKeyCredentialGetOptionsRequest
    dictionary ServerPublicKeyCredentialGetOptionsRequest {
        required DOMString              username;
        UserVerificationRequirement     userVerification = "preferred";
    };
  • 必須 username - エンティティについての人間が読むことができる名称。例えば「alexm」、「alex.p.mueller@example.com」や「+14255551234」
  • userVerfication - 「required」、「preferred」、「discouraged」を設定する。詳細はWebAuthn仕様書を参照する。デフォルトは「preferred」。
7.4.3.2 ServerPublicKeyCredentialGetOptionsResponse
    dictionary ServerPublicKeyCredentialGetOptionsResponse : ServerResponse {
        required DOMString                   challenge;
        unsigned long                        timeout;
        USVString                            rpId;
        sequence<ServerPublicKeyCredentialDescriptor> allowCredentials = [];
        UserVerificationRequirement          userVerification = "preferred";
        AuthenticationExtensionsClientInputs extensions;
    };
  • 必須 challenge - base64urlエンコードされたランダムなチャレンジ、チャレンジは最小でも16バイト長とし、最大は64バイト長とする
  • timeout - タイムアウト(ms)
  • rpId - このオプショナルな属性は呼び出し元が要求するリライングパーティの識別子を設定する。省略された場合、その値はCredentialsContainerオブジェクトに関連した設定オブジェクトののオリジンの有効ドメインになる
  • excludeCredentials - 本ドキュメントに記述されたServerPublicKeyCredentialDescriptorの配列
  • userVerification - 「required」、「preferred」、「discouraged」を設定する。詳細はWebAuthn仕様書を参照する。デフォルトは「preferred」。ServerPublicKeyCredentialGetOptionsRequest.userVerificationに対応した値。
  • extensions - WebAuthn仕様書に記述されているAuthenticationExtensionsClientInputsが設定されたディクショナリ
  • 本ドキュメントに記述されたServerResponseを拡張している
7.4.3.3 ServerPublicKeyCredentialDescriptor
dictionary ServerAuthenticatorAssertionResponse : ServerAuthenticatorResponse {
    required DOMString      clientDataJSON;
    required DOMString      authenticatorData;
    required DOMString      signature;
    required DOMString      userHandle;
};
  • 必須 clientDataJSON - base64urlエンコードされたclientDataJSONバッファ
  • 必須 authenticatorData- base64urlエンコードされたauthenticatorDataバッファ
  • 必須 signature- base64urlエンコードされたsignatureバッファ
  • 必須 userHandle- base64urlエンコードされたuserHandleバッファ。登録済ユーザのServerPublicKeyCredentialUserEntity.idに対応している。

7.5 共通

7.5.1 Common IDL

7.5.1.1 ServerResponse
    dictionary ServerResponse {
        required Status     status;
        required DOMString  errorMessage = "";
    }
  • 必須 status - レスポンスのステータスを示す。「ok」または「failed」のいずれかを設定できる。
  • 必須 errorMessage - statusが「failed」だった場合、このフィールは空にしてはならない(MUST NOT)。

リファレンスなどは省略した。

WebAuthnと競合する部分は少なかったと思う。バイナリ型の部分はbase64urlエンコードして送信することくらい。 追加して定義している部分としては、

  • レスポンスの基本的な形をServerResponseで定めていること、
  • サーバが実装すべき暗号アルゴリズムが指定されていること、
  • 登録・認証とどちらでもクライアントからサーバに送るリクエストが定められていること、
  • 登録でのクライアントからのリクエストに含まれるattestationフィールドに応じてサーバが値を返却すること、
  • 認証でのクライアントからのリクエストに含まれるuserVerificationフィールドに応じてサーバが値を返却すること

のはず。 この仕様書ではレスポンスの形がWebAuthnよりも定義されているので、適合性テストのためには対応しておく必要がある(【 FIDO Conformance Toolsメモ 】イントロダクション - 生き恥)。

Windows HelloのPIN入力によるFIDO2認証はEdgeのInPrivateブラウザ(シークレットモード)では使えないかもしれない

Windows HelloのPIN入力によるFIDO2認証はEdgeのInPrivateブラウザ(シークレットモード)では使えないかもしれない。 「かもしれない」というあやふやな書き方をしたのは、現状Microsoftなどの公式情報を探せていないから。また、顔認証や指紋認証については設定していないので不明である。

Edgeの検証結果と他のブラウザの対応状況も確認したのでメモしておく。 検証ではWebAuthn.ioというサイトを利用した。

事前にWindows Helloの設定をしておく必要があるが、ここでは説明しない。 下記にMS公式情報を載せておくので、実施するならば参考に設定してほしい。

Windows Hello の概要とセットアップ

検証

環境

  • OS: Windows10 バージョン20H2
  • Edge: 96.0.1054.43
  • GoogleChrome: 96.0.4664.45
  • FireFox: 94.0.2

Edgeの通常モード

最初にEdgeの通常モードでWindows Helloが利用できる様子をスクショで紹介しておく。

  1. WebAuthn.ioをEdgeで開く。

  1. ユーザ名をテキトーに入力し、Registerボタンを押す。

  1. Window HelloからPIN入力を求めるポップアップが表示される。PINを入力するとポップアップが閉じる。

  1. 少し待つと登録完了を伝える吹き出しが表示される。Loginボタンを押せば、ログインのために認証を求めてくる(未紹介)。

Edgeの通常モードではWindows Helloが呼び出され、PINを入力することでユーザ登録とログインができる。

EdgeのInPrivateブラウザ(シークレットモード)

  1. WebAuthn.ioをEdgeのInPrivateブラウザで開く。

  1. ユーザ名をテキトーに入力し、Registerボタンを押す。

  1. Window Helloからセキュリティキーを求めるポップアップが表示される。PIN入力を求める画面は表示されない。

上述のように、EdgeのInPrivateブラウザではWindows Helloは呼び出されるもののセキュリティキーの接続を求める画面が表示される。 ちなみにこのセキュリティキー画面は、Edge通常モードでPIN入力画面でキャンセルボタンを押すことでも表示される。

Chromeのシークレットモード

スクショを撮っていないが、GoogleChromeのシークレットモードでも試した。 結果としては、Edgeと同様だった。つまり、通常モードでは最初にPIN入力を求められ、シークレットモードではセキュリティキーの接続を求める画面が表示された。

Firefoxのプライベートモード

FirefoxはEdge、Chromeとは異なる挙動をし、プラベートモードでもPIN入力を求める画面が表示された。

  1. WebAuthn.ioFirefoxのプライベートモードで開く。

  1. ユーザ名をテキトーに入力し、Registerボタンを押す。

  1. Window HelloからPIN入力を求めるポップアップが表示される。PINを入力するとポップアップが閉じる。

  1. 少し待つと登録完了を伝える吹き出しが表示される。。

結果

今回の検証結果をまとめておく(まとめる意味がある表だろうか??)。

ブラウザ モード Windows Hello PIN入力
Microsoft Edge 通常
Microsoft Edge シークレット X
Google Chrome 通常
Google Chrome シークレット X
Mozilla Firefox 通常
Mozilla Firefox シークレット
Vivaldi 通常
Vivaldi シークレット X

検証までで紹介したブラウザ以外にVivaldiを使ってみたので、追記しておく。おそらくWindowsでは上3つのブラウザが主要なはずだ。今回の検証はデスクトップPCで実行したが、例えばサーフェスになったら挙動が異なるようなことはあるかもしれない。

考察(らしきもの)

PIN入力が利用できない理由

今回の一部ブラウザのシークレットモードでの動作だが、Windows Hello自体はブラウザから呼び出されて利用できている。セキュリティキー(=認証器)であればそのままユーザ登録・ログイン認証ができるようなので、PIN入力だけが利用できない。どういった理由だろうか。

シークレットモードでは、セキュリティを高めるためにPIN入力という記憶要素だけではなく所持という要素が加わることでセキュリティが高められそうなセキュリティキーが必要なのだろうか。その場合、指紋や顔認証の扱いが気になる。所持要素を重視しているのであれば、シークレットモードでは指紋や顔認証もPIN同様に使えないことになる。

もう一つとしては、シークレットモードの目的である。Edgeのシークレットモードの説明では、

このデバイスを使用している他のユーザーは閲覧アクティビティを見ることができません

さらにChromeでも

あなたのアクティビティは、このデバイスを利用する他のユーザーには表示されません。

と説明されている。シークレットモードのブラウザを使う理由はデバイスを共有する他のユーザから情報を秘匿するためにある。ということは、デバイスにクレデンシャル情報が残ってしまうWindows Hello PIN入力はこの場合に不適切なのかもしれない。また、Windows Hello PIN入力はユーザサインインにも利用でき、わざわざシークレットモードで他のユーザから隠しているのにサインインしているユーザが知っている情報による認証は不適切ということかもしれない。この場合、顔認証や指紋認証も利用できないはずだ。

同じ動作するブラウザ、異なる動作をするブラウザ

EdgeとChromeが同じ動作をしてFirefoxが異なる動作という点も気になる。なにかMicrosoftWindows Helloに関する仕様を提示していて、Chromeはそれに従ったがFirefoxは従っていないのだろうか。 もしくは、EdgeとChromeのベースになっているChromiumにシークレットモードではPINを使えなくする実装が入っているのかもしれない。VivaldiChromiumをベースとしていること、Chromium開発にはMicrosoftも関わっていることから可能性はある。

今回のシークレットモードブラウザでのWindows Helloについてきちんとした情報ソースは得られていない。 後日わかったことがあれば、追記したいと思っているし、有識者からの情報提供もお願いしたい。

FIDO Metadata ServiceのEntryのプロパティを勉強する

2021年5月にFIDO Metadata Service(MDS)のバージョンが3になった。

v2と比べると、データ自体がJSON(正確にはJWT)になって扱いやすくなり、さらに各プロパティの値も直感的になったらしい。詳しくはFIDO AllianceのAckermann Yuriy氏の記事を見てほしい。データの取得方法も紹介されている。

WebAuthn/FIDO2: What’s new in MDS3? Migrating from MDS2 to MDS3. | by Ackermann Yuriy | WebAuthn Works | Medium

この記事では実際のデータの中身を確認してみたいと思う。長いので(やる気が続けば)何回かに分けてまとめる。

データの取得

FIDO Allianceのサイトにアクセスし、データをダウンロードする。データはJWT形式なので、ダウンロードしてメモ帳などで開いたら全文コピーして jwt.ioに貼り付けるなどしてデコードすればよい。

データには画像をBase64エンコードしている項目(icon等)が存在する。こちらはBase64→画像デコード:Base64を画像に変換 | ラッコツールズ🔧のようなWebアプリを使えばブラウザだけで内容を確認できる。

おまけとして、MDSのデータをWebサイトにしているサイトを見つけた:FIDO MDS Explorer

データの例

2021年11月時点では、全部で98の認証器のメタデータが提供されていた。ここではFIDO2認証器としてYubico Bio Seriesを参考として貼っておく。(FIDO2認証器のJSONデータ

Yubico Bio Seriesの情報は以下の通り:

Metadataについて

MDSで取得できるデータはJWT形式であり、そのペイロード部分のentriesプロパティに認証器の情報が配列で入っている。配列の要素がEntryとなっている。

Entryの定義 - FIDO Metadata Service

本記事のAppendixはこのEntryを抜き出したものである。

Entryの各プロパティ

Entry内の各プロパティについて確認していく。よくわからない部分はとりあえず翻訳しただけになっている。

最も基本的な項目は、aaguid, aaid, attestationCertificateKeyIdentifiersといった認証器モデルを特定するための識別子、statusReportsという認証器の現在のステータスを示す必須項目だろう。

  • aaguid, aaid, attestationCertificateKeyIdentifiers:認証器のモデルを特定するための識別子。
    • FIDO2の認証器は aaguidを持つ、FIDO UAFの認証器はaaidを持つ、FIDO U2Fの認証器はattestationCertificateKeyIdentifiersを持つ。なので、上記のいずれかのプロパティに
    • aaguidは文字列でaaidもおそらく文字列。「おそらく」というのはaaidの型の記載を見つけられなかったが、実際のデータでは文字列になっていたからだ。attestationCertificateKeyIdentifiersだけは文字列の配列。
  • metadataStatement:非必須。認証器に関する情報。
    • 認証器に関する細かい情報が入っているようだ。後述のstatusReportsよりも細かい情報が欲しければこちらを使うのだろうか。非必須であるが、現時点で提供されている認証器データにはすべて含まれていた。
    • この項目は長いので、次回以降まとめる。
    • 参考:Metadata Statement - FIDO Metadata Statement
  • biometricStatusReports:認証器が持つ生体認証の認定情報。
    • このプロパティを持つデータは存在しなかった。まだ運用されていない?
  • statusReports:必須。認証器の情報の配列。最新の要素は現在の認証器のステータスを反映しなれければならない。
    • 必須項目とされており、これが最も基本的な認証器情報だと思われる。また、ステータスが更新されると配列の要素が増えるらしい。例えば、認証器がある日付ではFIDO未認定だったが、その後認定されており、要素が追加れされていた。最新の要素はeffectiveDateで判定する。また、Yubico Bio Seriesのように、要素のeffectiveDateが同一の要素が存在することがある。この2つの要素は、statusの値がFIDO_CERTIFIEDFIDO_CERTIFIED_L1と異なっている。FIDO_CERTIFIEDFIDO_CERTIFIED_L1は同義だが、現在はFIDO_CERTIFIED_L1を使うことになっており、互換性をもたせたということだと考えられる。statusFIDO_CERTIFIED_L1のほうが他のプロパティも存在しているのでこちらの要素を利用したほうがよい。
    • status:認証器の認定状況を示す文字列。ただし、列挙型なので特定の値のどれかが入る。(AuthenticatorStatus - FIDO Metadata Service
      • リライングパーティで、ある程度以上の信頼がおける認証器だけに利用を制限した場合などに使えると思う。
      • 認定状況を示す値については後述する。
    • effectiveDatestatusを設定した日付。 statusReportsの配列要素が複数存在したとき、最新の要素はeffectiveDateで判定する。ISO-8601形式で日付が表示される。例)"2021-08-06"
    • certificatestatusに関連したBase64エンコードされたDER形式のPKIX(Public-Key Infrastructure using X.509)証明書。
    • urlstatusに関連した追加情報が存在するURL。
    • certificationDescriptor:FIDO Allianceによる認証器の認定での外見的特徴。外見と記載されていたが、Yubico Bio Seriesの場合は商品名のようだ。
    • certificateNumber:FIDO Allianceによる認定に合格したら発行されるユニークな識別子。
    • certificationPolicyVersion:認定に合格した認証器認定ポリシー(Authenticator Certification Policy)のバージョン。
    • certificationRequirementsVersion:認定に合格した認証器セキュリティ要件(Authenticator Security Requirements)のドキュメントのバージョン。
  • timeOfLastStatusChange:必須。statusReportsの更新日付。
    • ISO-8601形式で日付が表示される。例)"2021-08-10"
  • rogueListURL:信用できない(ローグ)認証器の一覧が記載されているURL。
    • 今回のメタデータには含まれていなかった。
    • URL先ではJSON(RogueListEntry型の配列)が取得できるらしい。RogueListEntryはskプロパティとdateプロパティを持つ。sk秘密鍵(Secret Key)のbase64url円
  • rogueListHash:信用できない(ローグ)認証器の情報。

認証器の認定状況について

statusReportsstatusの値は認証器の認定状況を示す文字列となる。 これらの文字列が何を意味するのかを調べた。

AuthenticatorStatusは大きく分けて3つに分けられる

  • 認定に関するステータス
  • セキュリティ通知ステータス
  • 報ステータス

認定に関するステータス

FIDO認定を受けているのか受けていないのか、取り消されているのかを示すステータスコード。また、認定を受けている場合、どのレベルの認定を受けているのかを示す。

まずステータスコードについて示し、あとから認定レベルについて詳細に記載する。

  • NOT_FIDO_CERTIFIED:FIDO認定を受けていない
    • 使わないほうがよさそう。
  • SELF_ASSERTION_SUBMITTED:認証器ベンダーが自己診断チェックリストを完了し、FIDOアライアンスに提出している
    • 認証機ベンダーは、FIDO認定を受ける前に認証器が必要な要件を満たしていることを確認する自己診断チェックをおこなう必要があるようだ。
    • 自己診断チェックリストが公開されている場合、urlにアクセスするためのURLを入れることができる。
    • 使わないほうがよさそう。
  • FIDO_CERTIFIED:FIDO認定を受けている
    • 現在このステータスの認定制度は終了しており、FIDO_CERTIFIED_L1に置換される予定。
    • YubiKey Bio Seriesでもこの値の入っている要素とFIDO_CERTIFIED_L1の入っている要素がある。
  • FIDO_CERTIFIED_L1:FIDO Authenticator cerificationレベル1認定
  • FIDO_CERTIFIED_L1plus:FIDO Authenticator cerificationレベル1+認定
  • FIDO_CERTIFIED_L2:FIDO Authenticator cerificationレベル2認定
  • FIDO_CERTIFIED_L2plus:FIDO Authenticator cerificationレベル2+認定
  • FIDO_CERTIFIED_L3:FIDO Authenticator cerificationレベル3認定
  • FIDO_CERTIFIED_L3plus:FIDO Authenticator cerificationレベル3+認定
  • REVOKED:何らかの理由で認証器が信頼できないとFIDOアライアンスが判断
    • リライングパーティはこの認証器モデルの将来的な登録を拒否しなければならない。
    • 信頼できないと判断した理由を示すニュースや記事のURLがurlに入る。

認定レベル

参考: - Certified Authenticator Levels - FIDO Alliance - FIDO認証の技術と応用展開の最新状況

まず認定レベルの厳しさは以下の通り。数値が大きいほうがセキュリティが高くなっている。:

FIDO_CERTIFIED = FIDO_CERTIFIED_L1 < FIDO_CERTIFIED_L1plus <  FIDO_CERTIFIED_L2 < FIDO_CERTIFIED_L2plus < FIDO_CERTIFIED_L3 < FIDO_CERTIFIED_L3plus

認定レベルに対する要件の概要

とりあえず、ざっくりした翻訳

レベル ハードウェアおよびソフトウェア要件 防御対象 実装例
レベル1 ハードウェア、ソフトウェアともに制限なし ソフトウェアおよびベストプラクティスによってフィッシング攻撃および大部分のスケーラブル攻撃を防ぐ レベル1は全ての認定に必須となるセキュリティレベル
レベル2 バイスは機密動作環境(TEE、セキュリティエレメントなど)のサポートが必要 機密動作環境によってリモートソフトウェア攻撃を防ぐ 機密動作環境(セキュリティキーやTEE等)の実装
レベル3 機密動作環境をサポートし、認証器への物理的な攻撃に対するセキュリティ耐性を持つデバイス 機密動作環境によってリモートソフトウェア攻撃およびローカルハードウェア攻撃を防ぐ GlobalPlatformに認定されたTEE、Common Criteriaに認定されたセキュアなエレメント
  • 機密動作環境(Restricted Operating Environments, ROE):認証器アプリケーションのようなセキュリティを高くしたいアプリを実行できるような環境(FIDO Authenticator Allowed Restricted Operating Environments List
  • 許可された機密動作環境(Allowed Restricted Operating Environments, AROE):FIDOアライアンスに認められた機密動作環境
レベル1

Authenticator Level 1 - FIDO Alliance

以下の4つのどれかに属する認証器はレベル1に該当する。

  1. 同じ環境で実行されている他の多くのアプリケーションに対し、認証器セキュリティパラメータを効果的に防御できないハイレベル・オペレーティング・システム(HLOS)上で実行されている認証器アプリケーション
  2. 同じ環境で実行されている他の多くのアプリケーションに対し、HLOSを攻撃することを除いて認証器セキュリティパラメータを効果的に防御できるHLOS上で実行されている認証器アプリケーション
  3. 項2と同様だが、許可された機密動作環境(AROE)に保持したシークレット認証器セキュリティパラメータを保持する
  4. AROE内で全て実装されている認証器(この認証器は、通常レベル2プラスを満たす)

ここだけを読むと「公開鍵・秘密鍵の作成ができれば何でもよい」と書かれているように見えるが、もっと詳しく仕様には書かれているのだろうか。

また、ハイレベル・オペレーティング・システム(HLOS)は何なのかよくわからなかった。AndroidはHLOSと書かれているような記事もあったので一般的なOSはHLOSなのだろうか。

レベル1プラス

Authenticator Level 1+ - FIDO Alliance

レベル1プラスはレベル1とほぼ同じだが、ホワイトボックス暗号方式を実装している。

ホワイトボックス暗号方式:内部アルゴリズムが公開されている暗号方式。従来のブラックボックス方式の暗号方式では、攻撃者に内部アルゴリズムを推測されたり、信頼できない環境で実行されたりすると脆弱になる。(ホワイトボックス暗号方式とは何か

レベル1プラスには、大規模なソフトウェア攻撃に対する防御が必要。また、デバイスのOSが攻撃されても認証器アプリケーションを保護する。保護方法はAROEではなく、ホワイトボックス暗号方式およびその他のソフトウェア保護技術が用いられる。

暗号方式がホワイトボックス暗号方式となることで、デバイス自体を攻撃されてもある程度防御できるということだろう。

レベル1プラスの時点で認証器の実装が難しいそう。一般的な利用にはレベル1でも充分かもしれない。

レベル2

Authenticator Level 2 - FIDO Alliance

レベル2は一般的なスケーラブルな攻撃に対抗できる認証器であることが求められる。 AROEおよび認証器セキュリティ要件にリスト化されている認可された暗号方式を実装している必要がある。ROEを持たない認証器やアテステーションをサポートしていない認証器はレベル2とは認定されない。

スケーラブルな攻撃:FIDOでは攻撃対象を追加するときにかかる労力・コストがほぼゼロ(=拡張性が高い(スケーラブル)?)となるような攻撃を指す。攻撃者が攻撃準備に手間をかけずに住むようなレベルとしては低い攻撃。

ROEの実装というのがレベル1との違いのようだ。

レベル2プラス

レベル2プラスは現在定義されていないらしく、要件を見つけることができなかった。

レベル3

Authenticator Level 3 - FIDO Alliance

レベル3は強化された一般的なレベルでのソフトウェア攻撃およびハードウェア攻撃に対する防御を持つ認証器であることが求められる。 AROEおよび認証器セキュリティ要件にリスト化されている認可された暗号方式を実装している必要がある。CPUやメモリ、認証器自体の物理的なケースも攻撃に対抗できるものであることが必要らしい。

レベル3になると認証器を物理的にも守る必要があるようだ。

レベル3プラス

Authenticator Level 3+ - FIDO Alliance

レベル3プラスは(レベル3では一般的レベルだったのに対して)中程度または高いレベルでのソフトウェア攻撃およびハードウェア攻撃に対する防御を持つ認証器であることが求められる。

中程度または高いレベルでの攻撃:数週間から数ヶ月かけて高度に専門的な電子実験機器を用いたチップレベルへの攻撃

AROEおよび認証器セキュリティ要件にリスト化されている認可された暗号方式を実装している必要がある。

セキュリティ通知ステータス

認証器にセキュリティに関する脆弱性が存在する場合、これらのステータスコードが利用される。 基本的にはリライングパーティでは公開鍵の登録を拒否するべきと思われる。

  • USER_VERIFICATION_BYPASSマルウェアがユーザ認証をバイパスできる
    • ユーザの同意なしに、潜在的にはユーザの知識(パスワードやPINコード)なしに認証器が利用されうる。
  • ATTESTATION_KEY_COMPROMISE:認証器のアテステーション・キーの信頼が損なわれている
    • リライングパーティはcertificateフィールドを確認して侵害されたアテステーション・キーを特定するべきである。certificateがセットされていない場合、この認証器による登録は全て拒否するべきである。
    • urlにはインシデントに関するニュースや記事のURLが入る。
  • USER_KEY_REMOTE_COMPROMISE:登録されたキーの信頼性が損なわれるような脆弱性が認証器に見つかっており、この認証器は信頼すべきではない
    • キーが推測できたり、偽造できたり、盗み出せたりする脆弱性も含む。
    • urlにはインシデントに関するニュースや記事のURLが入る。
  • USER_KEY_PHYSICAL_COMPROMISE:認証器のキー防御機構に脆弱性が見つかり、デバイスを物理的に所有している攻撃者によってユーザのキーが盗み出せる
    • urlにはインシデントに関するニュースや記事のURLが入る。

報ステータス

上記以外の何らかの情報を示すステータスコード。現状は1種類しか定義されていない。 一応、認証器を利用できるが、何らかの情報を提供したいときに使うものらしい。

  • UPDATE_AVAILABLE:認証器のソフトウェアまたはファームウェアのアップデートが利用可能
    • urlにはアップデートを入手できるURLをセットする。
    • authenticatorVersionには認証器の新しいバージョンを設定する。また、metadata statement内のauthenticatorVersionと一致しなければならない。
    • 重大なセキュリティ脆弱性に対する修正アップデートの場合、このステータスコードではなく、セキュリティ通知ステータスのステータスまたはREVOKEDを利用しなければならない。

Appendix

FIDO2認証器のMetadata Entry

Yubico Bio Series

       {
            "aaguid": "d8522d9f-575b-4866-88a9-ba99fa02f35b",
            "metadataStatement": {
                "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/",
                "aaguid": "d8522d9f-575b-4866-88a9-ba99fa02f35b",
                "description": "YubiKey Bio Series",
                "authenticatorVersion": 328965,
                "protocolFamily": "fido2",
                "schema": 3,
                "upv": [
                    {
                        "major": 1,
                        "minor": 0
                    },
                    {
                        "major": 1,
                        "minor": 1
                    }
                ],
                "authenticationAlgorithms": [
                    "secp256r1_ecdsa_sha256_raw",
                    "ed25519_eddsa_sha512_raw"
                ],
                "publicKeyAlgAndEncodings": [
                    "cose"
                ],
                "attestationTypes": [
                    "basic_full"
                ],
                "userVerificationDetails": [
                    [
                        {
                            "userVerificationMethod": "none"
                        }
                    ],
                    [
                        {
                            "userVerificationMethod": "presence_internal"
                        },
                        {
                            "userVerificationMethod": "fingerprint_internal",
                            "baDesc": {
                                "selfAttestedFRR": 0,
                                "selfAttestedFAR": 0,
                                "maxTemplates": 5,
                                "maxRetries": 5,
                                "blockSlowdown": 0
                            }
                        }
                    ],
                    [
                        {
                            "userVerificationMethod": "passcode_external",
                            "caDesc": {
                                "base": 64,
                                "minLength": 4,
                                "maxRetries": 8,
                                "blockSlowdown": 0
                            }
                        },
                        {
                            "userVerificationMethod": "presence_internal"
                        }
                    ],
                    [
                        {
                            "userVerificationMethod": "presence_internal"
                        }
                    ],
                    [
                        {
                            "userVerificationMethod": "passcode_external",
                            "caDesc": {
                                "base": 64,
                                "minLength": 4,
                                "maxRetries": 8,
                                "blockSlowdown": 0
                            }
                        }
                    ],
                    [
                        {
                            "userVerificationMethod": "fingerprint_internal",
                            "baDesc": {
                                "selfAttestedFRR": 0,
                                "selfAttestedFAR": 0,
                                "maxTemplates": 5,
                                "maxRetries": 5,
                                "blockSlowdown": 0
                            }
                        }
                    ]
                ],
                "keyProtection": [
                    "hardware",
                    "secure_element"
                ],
                "matcherProtection": [
                    "on_chip"
                ],
                "cryptoStrength": 128,
                "attachmentHint": [
                    "external",
                    "wired"
                ],
                "tcDisplay": [],
                "attestationRootCertificates": [
                    "MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbwnebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXwLvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJhjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kthX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2kLVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1UsG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqcU9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw=="
                ],
                "icon": "data:image/png;base64,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",
                "authenticatorGetInfo": {
                    "versions": [
                        "FIDO_2_0",
                        "FIDO_2_1_PRE",
                        "FIDO_2_1"
                    ],
                    "extensions": [
                        "credProtect",
                        "hmac-secret",
                        "largeBlobKey",
                        "credBlob",
                        "minPinLength"
                    ],
                    "aaguid": "d8522d9f575b486688a9ba99fa02f35b",
                    "options": {
                        "plat": false,
                        "rk": true,
                        "clientPin": false,
                        "up": true,
                        "uv": false,
                        "pinUvAuthToken": true,
                        "largeBlobs": true,
                        "bioEnroll": false,
                        "userVerificationMgmtPreview": false,
                        "authnrCfg": true,
                        "credMgmt": true,
                        "credentialMgmtPreview": true,
                        "setMinPINLength": true,
                        "makeCredUvNotRqd": false,
                        "alwaysUv": true
                    },
                    "maxMsgSize": 1200,
                    "pinUvAuthProtocols": [
                        2,
                        1
                    ],
                    "maxCredentialCountInList": 8,
                    "maxCredentialIdLength": 128,
                    "transports": [
                        "usb"
                    ],
                    "algorithms": [
                        {
                            "type": "public-key",
                            "alg": -7
                        },
                        {
                            "type": "public-key",
                            "alg": -8
                        }
                    ],
                    "maxSerializedLargeBlobArray": 1024,
                    "minPINLength": 4,
                    "firmwareVersion": 328965,
                    "maxCredBlobLength": 32,
                    "maxRPIDsForSetMinPINLength": 1,
                    "preferredPlatformUvAttempts": 3,
                    "uvModality": 2,
                    "remainingDiscoverableCredentials": 25
                }
            },
            "statusReports": [
                {
                    "status": "FIDO_CERTIFIED",
                    "effectiveDate": "2021-08-06"
                },
                {
                    "status": "FIDO_CERTIFIED_L1",
                    "effectiveDate": "2021-08-06",
                    "url": "www.yubico.com",
                    "certificationDescriptor": "YubiKey Bio",
                    "certificateNumber": "FIDO20020210806001",
                    "certificationPolicyVersion": "1.3",
                    "certificationRequirementsVersion": "1.4"
                }
            ],
            "timeOfLastStatusChange": "2021-08-10"
        },

FIDO UAF認証器のMetadata Entry

       {
            "aaid": "4e4e#4005",
            "metadataStatement": {
                "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/",
                "aaid": "4e4e#4005",
                "description": "Touch ID, Face ID, or Passcode",
                "authenticatorVersion": 256,
                "protocolFamily": "uaf",
                "schema": 3,
                "upv": [
                    {
                        "major": 1,
                        "minor": 0
                    },
                    {
                        "major": 1,
                        "minor": 1
                    }
                ],
                "authenticationAlgorithms": [
                    "rsa_emsa_pkcs1_sha256_raw"
                ],
                "publicKeyAlgAndEncodings": [
                    "rsa_2048_raw"
                ],
                "attestationTypes": [
                    "basic_surrogate"
                ],
                "userVerificationDetails": [
                    [
                        {
                            "userVerificationMethod": "passcode_internal",
                            "caDesc": {
                                "base": 10,
                                "minLength": 4,
                                "maxRetries": 5,
                                "blockSlowdown": 60
                            }
                        }
                    ],
                    [
                        {
                            "userVerificationMethod": "fingerprint_internal",
                            "baDesc": {
                                "selfAttestedFRR": 0,
                                "selfAttestedFAR": 0,
                                "maxTemplates": 0,
                                "maxRetries": 5,
                                "blockSlowdown": 0
                            }
                        }
                    ]
                ],
                "keyProtection": [
                    "hardware",
                    "tee"
                ],
                "matcherProtection": [
                    "tee"
                ],
                "attachmentHint": [
                    "internal"
                ],
                "tcDisplay": [
                    "any"
                ],
                "tcDisplayContentType": "text/plain",
                "attestationRootCertificates": [],
                "icon": "data:image/png;base64,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"
            },
            "statusReports": [
                {
                    "status": "NOT_FIDO_CERTIFIED",
                    "effectiveDate": "2018-05-19"
                }
            ],
            "timeOfLastStatusChange": "2018-05-19"
        },

FIDO U2F認証器のMetadata Entry

       {
            "attestationCertificateKeyIdentifiers": [
                "1434d2f277fe479c35ddf6aa4d08a07cbce99dd7"
            ],
            "metadataStatement": {
                "legalHeader": "https://fidoalliance.org/metadata/metadata-statement-legal-header/",
                "attestationCertificateKeyIdentifiers": [
                    "1434d2f277fe479c35ddf6aa4d08a07cbce99dd7"
                ],
                "description": "NEOWAVE Winkeo FIDO2",
                "authenticatorVersion": 2,
                "protocolFamily": "u2f",
                "schema": 3,
                "upv": [
                    {
                        "major": 1,
                        "minor": 1
                    }
                ],
                "authenticationAlgorithms": [
                    "secp256r1_ecdsa_sha256_raw"
                ],
                "publicKeyAlgAndEncodings": [
                    "ecc_x962_raw"
                ],
                "attestationTypes": [
                    "basic_full"
                ],
                "userVerificationDetails": [
                    [
                        {
                            "userVerificationMethod": "presence_internal"
                        }
                    ]
                ],
                "keyProtection": [
                    "hardware",
                    "secure_element"
                ],
                "matcherProtection": [
                    "on_chip"
                ],
                "cryptoStrength": 128,
                "attachmentHint": [
                    "external",
                    "wired"
                ],
                "tcDisplay": [],
                "attestationRootCertificates": [
                    "\n\nMIICHTCCAcKgAwIBAgICddUwCgYIKoZIzj0EAwIwezELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRFdXJvcGUxFzAVBgNVBAsTDjAwMDIgNDM0MjAyMTgwMSQwIgYDVQQDExtDZXJ0RXVyb3BlIEVsbGlwdGljIFJvb3QgQ0ExGDAWBgNVBGETD05UUkZSLTQzNDIwMjE4MDAeFw0xODAxMjIyMzAwMDBaFw0yODAxMjIyMzAwMDBaMHsxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0RXVyb3BlMRcwFQYDVQQLEw4wMDAyIDQzNDIwMjE4MDEkMCIGA1UEAxMbQ2VydEV1cm9wZSBFbGxpcHRpYyBSb290IENBMRgwFgYDVQRhEw9OVFJGUi00MzQyMDIxODAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATz2jNaKOK/MKdW2fme1tq6GREuPuuKW9HgWYgMRrjvZUTOqLANJ3Md5Hqv1EN1zMd4lWtyfzRla7rv5ARBoOoTozYwNDAPBgNVHRMBAf8EBTADAQH/MBEGA1UdDgQKBAhNnTW0a4E8ujAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSQAwRgIhAMrhb8SmfNLeLNgaAVmQ6AOMiLNLVHX0kFUO80CnT38EAiEAzNAgv4dH+HDhZSgZWJiaPu/nfZTeuGy4MydPMq5urs4=",
                    "\nMIIEODCCA92gAwIBAgIDAInBMAoGCCqGSM49BAMCMHsxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0RXVyb3BlMRcwFQYDVQQLEw4wMDAyIDQzNDIwMjE4MDEkMCIGA1UEAxMbQ2VydEV1cm9wZSBFbGxpcHRpYyBSb290IENBMRgwFgYDVQRhEw9OVFJGUi00MzQyMDIxODAwHhcNMTgwMjIyMjMwMDAwWhcNMjgwMTIxMjMwMDAwWjB0MQswCQYDVQQGEwJGUjETMBEGA1UEChMKQ2VydEV1cm9wZTEXMBUGA1UECxMOMDAwMiA0MzQyMDIxODAxHTAbBgNVBAMTFENlcnRFdXJvcGUgSWRlY3lzIENBMRgwFgYDVQRhEw9OVFJGUi00MzQyMDIxODAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASLVL+1STJvaERO5WCR+jGcAxLvmPBDiZY1NgFFIhpX6OAZApQYmt6xSh74SwM+mjgnsSEcc4A2Uf139FgZ4rpYo4ICVTCCAlEwEwYDVR0jBAwwCoAITZ01tGuBPLowSgYIKwYBBQUHAQEEPjA8MDoGCCsGAQUFBzAChi5odHRwOi8vd3d3LmNlcnRldXJvcGUuZnIvcmVmZXJlbmNlL2VjX3Jvb3QuY3J0MFMGA1UdIARMMEowSAYJKoF6AWkpAQEAMDswOQYIKwYBBQUHAgEWLWh0dHBzOi8vd3d3LmNlcnRldXJvcGUuZnIvY2hhaW5lLWRlLWNvbmZpYW5jZTCCAWAGA1UdHwSCAVcwggFTMD+gPaA7hjlodHRwOi8vd3d3LmNlcnRldXJvcGUuZnIvcmVmZXJlbmNlL2NlcnRldXJvcGVfZWNfcm9vdC5jcmwwgYaggYOggYCGfmxkYXA6Ly9sY3IxLmNlcnRldXJvcGUuZnIvY249Q2VydEV1cm9wZSUyMEVsbGlwdGljJTIwUm9vdCUyMENBLG91PTAwMDIlMjA0MzQyMDIxODAsbz1DZXJ0RXVyb3BlLGM9RlI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDCBhqCBg6CBgIZ+bGRhcDovL2xjcjIuY2VydGV1cm9wZS5mci9jbj1DZXJ0RXVyb3BlJTIwRWxsaXB0aWMlMjBSb290JTIwQ0Esb3U9MDAwMiUyMDQzNDIwMjE4MCxvPUNlcnRFdXJvcGUsYz1GUj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MBEGA1UdDgQKBAhDaQbhTFtjcjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQDAgNJADBGAiEAoEepHMC5X9jBKaGphcKjidhiN+Znz7v3S3hc31/AunsCIQDKqogK2SZOXZcvvHCB6UQSaA0nLn4RUwy1guDivbZbwg==",
                    "MIIEODCCA92gAwIBAgIDAInBMAoGCCqGSM49BAMCMHsxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0RXVyb3BlMRcwFQYDVQQLEw4wMDAyIDQzNDIwMjE4MDEkMCIGA1UEAxMbQ2VydEV1cm9wZSBFbGxpcHRpYyBSb290IENBMRgwFgYDVQRhEw9OVFJGUi00MzQyMDIxODAwHhcNMTgwMjIyMjMwMDAwWhcNMjgwMTIxMjMwMDAwWjB0MQswCQYDVQQGEwJGUjETMBEGA1UEChMKQ2VydEV1cm9wZTEXMBUGA1UECxMOMDAwMiA0MzQyMDIxODAxHTAbBgNVBAMTFENlcnRFdXJvcGUgSWRlY3lzIENBMRgwFgYDVQRhEw9OVFJGUi00MzQyMDIxODAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASLVL+1STJvaERO5WCR+jGcAxLvmPBDiZY1NgFFIhpX6OAZApQYmt6xSh74SwM+mjgnsSEcc4A2Uf139FgZ4rpYo4ICVTCCAlEwEwYDVR0jBAwwCoAITZ01tGuBPLowSgYIKwYBBQUHAQEEPjA8MDoGCCsGAQUFBzAChi5odHRwOi8vd3d3LmNlcnRldXJvcGUuZnIvcmVmZXJlbmNlL2VjX3Jvb3QuY3J0MFMGA1UdIARMMEowSAYJKoF6AWkpAQEAMDswOQYIKwYBBQUHAgEWLWh0dHBzOi8vd3d3LmNlcnRldXJvcGUuZnIvY2hhaW5lLWRlLWNvbmZpYW5jZTCCAWAGA1UdHwSCAVcwggFTMD+gPaA7hjlodHRwOi8vd3d3LmNlcnRldXJvcGUuZnIvcmVmZXJlbmNlL2NlcnRldXJvcGVfZWNfcm9vdC5jcmwwgYaggYOggYCGfmxkYXA6Ly9sY3IxLmNlcnRldXJvcGUuZnIvY249Q2VydEV1cm9wZSUyMEVsbGlwdGljJTIwUm9vdCUyMENBLG91PTAwMDIlMjA0MzQyMDIxODAsbz1DZXJ0RXVyb3BlLGM9RlI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDCBhqCBg6CBgIZ+bGRhcDovL2xjcjIuY2VydGV1cm9wZS5mci9jbj1DZXJ0RXVyb3BlJTIwRWxsaXB0aWMlMjBSb290JTIwQ0Esb3U9MDAwMiUyMDQzNDIwMjE4MCxvPUNlcnRFdXJvcGUsYz1GUj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MBEGA1UdDgQKBAhDaQbhTFtjcjAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQDAgNJADBGAiEAoEepHMC5X9jBKaGphcKjidhiN+Znz7v3S3hc31/AunsCIQDKqogK2SZOXZcvvHCB6UQSaA0nLn4RUwy1guDivbZbwg==",
                    "MIICHTCCAcKgAwIBAgICddUwCgYIKoZIzj0EAwIwezELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRFdXJvcGUxFzAVBgNVBAsTDjAwMDIgNDM0MjAyMTgwMSQwIgYDVQQDExtDZXJ0RXVyb3BlIEVsbGlwdGljIFJvb3QgQ0ExGDAWBgNVBGETD05UUkZSLTQzNDIwMjE4MDAeFw0xODAxMjIyMzAwMDBaFw0yODAxMjIyMzAwMDBaMHsxCzAJBgNVBAYTAkZSMRMwEQYDVQQKEwpDZXJ0RXVyb3BlMRcwFQYDVQQLEw4wMDAyIDQzNDIwMjE4MDEkMCIGA1UEAxMbQ2VydEV1cm9wZSBFbGxpcHRpYyBSb290IENBMRgwFgYDVQRhEw9OVFJGUi00MzQyMDIxODAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATz2jNaKOK/MKdW2fme1tq6GREuPuuKW9HgWYgMRrjvZUTOqLANJ3Md5Hqv1EN1zMd4lWtyfzRla7rv5ARBoOoTozYwNDAPBgNVHRMBAf8EBTADAQH/MBEGA1UdDgQKBAhNnTW0a4E8ujAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwIDSQAwRgIhAMrhb8SmfNLeLNgaAVmQ6AOMiLNLVHX0kFUO80CnT38EAiEAzNAgv4dH+HDhZSgZWJiaPu/nfZTeuGy4MydPMq5urs4="
                ],
                "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAACqUlEQVRIx2P8//8/Ay0BEwONwagFpFlw8cKFirIyR3t7S1Oz0KDgBfPm//z5k3izvn39lp+Ta2tltWTRIoTofxhYtXKllpq6srwCAikoRIVHvH379j9x4NSpU0AtQI1W5hZwQagPzp87V11ZiXAvIxj9Zzh54kRNZRWRPvj96xcDOM0zMTKiB9G8uXP//fsHNFRASLC+sXHm7Nlubu4Qm3bt3Llu7VpiLGCEmcuIacGZU6fB4cWQX1AQGx/n7OIyaeoUbV0diIvamluePXtGUST/+g32HSODhoYGRISFhaWppYWVlRUo+OHjh6b6BoosgHvqz58/cDl9ff3M7CwIe8+e3atXrqQgmeIokDKzs/X19EGy/xk6OzofP3pEWUbDsAYYRC3tbRwcHED2h/fv62pqCReOjCTmZE0trZy8XAj78KFDy5YuJd50VAsYcepKTU83NjWBqOnu7Hxw/wE+O/7jsgC315mZmRubm9nZ2YFqvnz+0lBfhzOg/qO7lQm/B+EAmHwLioogCo4cOrxk0WIiPUEgkpFBUnKymZk5hN3T1XX3zh1iYoKJcDTBA4qFubmtlYubC8j++vVrTVU1qHQhzQeMBHyhrKxcWFwMUXn61Kn5c+dSv8JJSEy0trGGsCf099+6dQsuxcLCCrH7P5IrSYgDeKFS39TEx8sHZH//9r2uGhFQN65fh2VPNoqqTCUlpeKyUmgxfPpMSWERMAMuX7asv7cXIqilrYXwFrxeg/qOuGZSdEzM3t17Dh06CPT0pk0bN23cCI9FYKZJz8hE98Hff38hDDY2diL90dHdpaurixawrCysre3tunq6iLTX0NAAToIsTx4/tndwiIyOAtYExFjAzc3t4+sLJL99/QosE0VFRe3s7RtbmoGVFUqcjTYdh78FAIhBLlNd7ju1AAAAAElFTkSuQmCC"
            },
            "statusReports": [
                {
                    "status": "NOT_FIDO_CERTIFIED",
                    "effectiveDate": "2021-09-21"
                }
            ],
            "timeOfLastStatusChange": "2021-09-21"
        },

雰囲気OAuth本を読んだ

先月10月はブログ記事を書けなかったので、今月はその分を入れて2本書きたい。


さて、OAuthに関する書籍を読んだので、その感想を書いておこうと思う。 今回読んだのはAuth屋の本を2冊読んだ。

最初の雰囲気~の方は商業版があり、Amazon楽天で購入することができるが、BOOTHでPDF版を購入した。 感想と書籍内のチュートリアルが現在(2021年11月14、15日あたり)と若干変わっていたのでその部分だけ軽く書いておく。

上記の本については、全員がOAuth 2.0を理解しているチームの作り方 #devio2021 | DevelopersIO などでも紹介されている。

感想

とりあえず、読んだ感想から。

一言でまとめるとOAuth、OIDCの流れがわかりやすかった。シーケンス図が繰り返し出てくるのと、チュートリアルではブラウザとcurlコマンド(とGoogleアカウント)だけを使うので直感的につかめたのが大きいと思う。書籍にも書かれていたが、curlコマンドはWindows10ならコマンドプロンプトcurlコマンドが使える。また、「OAuth、OAuth認証OpenID Connectの違いを整理して理解できる本」の最後にMacでコマンドを使ってIDトークンの中身を見ていたが、コマンドが使えなくてもJWTのデコードはJSON Web Tokens - jwt.ioというサイトを使えばブラウザ上で実行できるので、この点も問題ない。

下の画像のようにEncodedに取得したIDトークンを入れると右側のDecodedに結果が表示される。

jwt.ioでIDトークンをデコード

OAuthについては、OAuth徹底入門 セキュアな認可システムを適用するための原則と実践を以前読んだが、こちらは400ページ以上あり、説明以外にOAuthアプリのNode.jsのソースコードが記載されているなどさくっと欲しい情報を見るのは少し手間だった。「OAuth、OAuth認証OpenID Connectの違いを整理して理解できる本」の方はシーケンス図だけを抜き出した別PDFファイルもあるので探すのが楽だと思う。

OAuth・OIDCのクライアント・RP側としてアプリを作成するなら、上記のAuth屋の書籍をまず読むのは非常におすすめ。詳しく知ろうと思ったら結局RFCを読むことになるのはどの書籍でも同じだと思う。 OAuth徹底入門の方は、クライアント側、認可サーバ側の両方に対応しているようだった。署名検証の処理やBase64処理など基本的な処理をNode.jsで実装するチュートリアルがあるため、この辺も知りたいならばOAuth徹底入門が良い。(個人的な話だが、Node.jsの勉強もしたかったので徹底入門は都合がよかった。)セキュリティ関連は徹底入門のほうが詳しいが上記2冊の次であるOAuth・OIDCへの攻撃と対策を整理して理解できる本(リダイレクトへの攻撃編 - Auth屋 - BOOTHにこのあたりは書いてあると期待している。

チュートリアルの差異

コマンド内のシングルクォーテーション

トークン取得でcurlコマンドを実行するがWindows10のコマンドプロンプトではダブルクォーテーションでないと実行できなかった。下のようなかんじにすると動いた。

curl -H "Authorization: Bearer ya29.a0AR...rdP" https://openidconnect.googleapis.com/v1/userinfo

ユーザタイプの選択画面、テストユーザの追加

チュートリアルではGoogle Cloud Platform(GCP)にアクセスしてOAuth同意画面の作成をおこなうとき、書籍には記載されていないユーザタイプを選択させる画面が表示された。

今回、「外部」しか選択できなかったのでそのまま進めた。どうやらテストユーザしか使用できなくなるので、次のようにテストユーザを追加した。

ユーザタイプ

テストユーザとしてGoogleアカウントを追加すればよい。 まずはOAuth同意画面の「+ ADD USERS」というボタンをクリックする。

テストユーザ

先のボタンをクリックするとダイアログが表示される。 入力欄に、Googleアカウントのメールアドレスを入力して保存ボタンを押す。テストユーザは、GCPにアクセスしているアカウントで問題なかった。

追加

localhost(127.0.0.1)をHTTPS化して開発環境を作る

普段、開発環境ではlocalhostをHTTPで利用してサーバを立てて遊んでいる。 しかし、WebアプリによってはHTTPSでの挙動の確認やWebAuthnAPI等のようにHTTPSでないとそもそも動作しないものもあり、面倒なことになっている。

会社であれば開発用のドメイン、証明書、EC2のようなサーバを使って好き勝手できるが、個人では億劫なのとお金がかかるのでやれていない(バリバリやれていれば気にもならないがブログすら月1ぎりぎりである)。

localhostHTTPS化

そんな折、この記事を見つけた。 内容としては、持っているドメインと証明書を使ってlocalhost(127.0.0.1)をHTTPS化するというものである。

ドメインと証明書があれば、自身のPCに立てたlocalhostサーバをHTTPSにできる。 あとはドメインと証明書の確保であるが、証明書は非常に有名なLet's Encryptを利用することで無料で入手できる。 さらに、ドメインは最近知ったがFrreenomを利用すれば、無料で入手できる。

これなら無料でHTTPSの開発環境を構築できると思い、実際におこなった。

手順

ざっくりと手順は以下の通りになる。

  1. Freenomでドメインを取得
  2. 127.0.0.1に対してAレコードを追加
  3. Let's EncryptでDNS認証によるSSL証明書を取得
  4. express等でlocalhost:3000にサーバ構築

1. Freenomでドメインを取得

以下のサイトにアクセスする。

https://www.freenom.com/ja/index.html

サインアップし、利用できる無料ドメイン名を取得する。 この辺はやり方を調べればいくらでも出てくるので割愛したい。

先程検索して上位に出てきたサイトを一応紹介しておく。

2. 127.0.0.1に対してAレコードを追加

Feenomにサインインし、取得したドメイン名のDNSを管理する。 「Manage DNS」のようなリンクを押すことでレコードの追加画面に移動するはずだ。

適当なホスト名(例えばわかりやすく、localhost)に対してIPアドレス127.0.0.1を紐付けたAレコードを追加する。

自分の場合は先のFreenomでs1r-j.tkというドメインを取得しているのであとの操作が完了すれば、ローカル環境に構築したサーバに対してhttps://localhost.s1r-j.tkというURLでアクセスが可能になる。

3. Let's EncryptでDNS認証によるSSL証明書を取得

Let's Encryptを初めて使ったので躓いた。 証明書を取得するために、Linux環境でのコマンド操作が必要だがWSLで代替可能だった。

以下の記事の手順4以降を実施していけばよい。 注意点として、手順6でDNSサーバにTXTレコードを追加して手順7に進むがこのときDNSが反映されるまで若干(5分程度)待つ必要がある。

手順に戸惑ったり、詰まったら同じ内容が違った言葉で書いてあるので以下の記事も参考にしてほしい。

ここまででローカル環境のHTTPS化は完了している。 以降はおまけとして、実際にサーバを立ててHTTPS化の確認をおこなう。

4. express等でlocalhost:3000にサーバ構築

サーバ構築は何でも良い。以前だったらJavaだったが最近はNode.jsを頑張っているのでexpressを使って構築した。 適当なフォルダを作成し、npm initをおこない、必要なモジュールをインストールする。

フォルダは概ね以下のような形になるはず。

index.js   サーバの実装
package.json   
node_modules/
ssl/
 ├ privatekey.pem   Let's Encryptが作成した秘密鍵
 └ fullchain.pem    Let's Encryptが作成した証明書

index.jsの実装は以下のようにする。 sslフォルダ以下においた秘密鍵と証明書を読み込ませる。

あとはnode index.jsを実行すればサーバが立ち上がる。 HTTPS化するまえと同じく、http://localhost:3000でアクセスできることを確認。 さらにhttps://localhost.s1r-j.tkもしくはhttps://localhost.s1r-j.tk/helloのようにHTTPS化したURLでアクセスできることを確認する。

おわり

以上、無料でHTTPS化したローカル開発環境の構築である。

自身で思いついて何かをしたわけでも、特段わかりやすい記事なわけでもない。 先人たちの知恵に感謝するばかりである。

Node.jsで証明書チェーンの検証をおこなう

下記のQiitaの記事と同じです。Gistがうまく貼れなかったのでこちらにも転載。

Node.jsで証明書チェーンの検証をおこなう - Qiita


Node.jsで証明書チェーンの検証をおこなうソースコードを実装したので、メモとして残しておく。

調べ方がわるかったのか、実装サンプルを見つけるのに苦労したのでここにまとめておく。
見つけた実装サンプルはStack Overflowにありました。

ssl - Using node.js to verify a X509 certificate with CA cert - Stack Overflow

証明書チェーンとは

そもそも証明書チェーンとは、ある証明書はより上位の証明書によって署名され、その上位の証明書はさらにその上位の証明書によって署名されているという証明書がチェーンのように紐付いている関係を指します。 最も上位の証明書のことをルート証明書と呼び、それ以外の証明書は中間証明書またはサーバ証明書と呼びます。

イメージ図(証明書チェーンの働き - IBM Documentationより)

そして、証明書チェーンの検証とは正しく署名によるつながりができていることを検証する、ということです。

参考サイト

実装

実装には最初に記載したStack Overflowの記事を参考にして、node-forgeモジュールを利用します。

実装は以下のとおりです。

証明書A -> 証明書B -> ルート証明書という順に上位の証明書になっていきます。 証明書の中身は実際に検証した証明書に変えてください。サンプルでは開発のときに試したFIDO Allianceの証明書を使っています。

おわり

ほとんどモジュールのおかげなので、深い理解はできていません。 しかし、実際に動くものがあると調べるポイントや理解しやすさがあがると思います。

最後に注意点ですが、実装例の証明書は有効期限や何らかの要因で使えなくなる可能性があります。 その際は、何かしら別の証明書チェーンの証明書に差し替えてお試しください。

Markdownエディタの探索

今月書けるネタがなかったので、調査中のことでお茶を濁します。

以前、PC環境構築の記事にMarkdownエディタとしてTyporaを紹介しました。 同時にTyporaは正式版になったときに有償でライセンス購入が必要になるかもしれない(公式サイトでの文言)、ということも書きました。 2021/07/31時点でもこれは変わっていないようです。

なので、先にTyporaの代替となるエディタを探したいと思います。(この記事では詳細な比較や結論は出ません。こんな候補があったよ、というだけです。)

本題

困ったらVS Codeで良いのですが、Markdownに特化していてもう少し軽量なエディタがほしいと思っています。機能としては、アウトライン機能とリアルタイムなプレビュー機能がほしいと思っています。 少し探して見つかった候補は以下のとおりです。

MarkText

とにかくUIがきれいで、Typoraにあった機能は網羅されているかんじです。 こちらのブログで機能が紹介されていました。:オープンソースのMarkdownエディタならばMarkTextが良い感じ - FPGA開発日記

自分でも使ってみましたが、Windows10のせいなのかきちんと動作しなかった部分があります。 issueで報告されているかとかは調べていないですが、以下のようなことが起きました。

  • アウトライン(Table of content)が微妙。ソースコードモードだとリアルタイム更新されない
    • source-code modeとwyswygを入れ替えれば更新される(Alt + Ctrl + S)
  • Ctrl + Zがきかない
  • 検索で引っかかった位置にジャンプできない
  • アウトラインをクリックしてもジャンプできない
  • 勝手に改行を入れられる

上記のようなうまく動作してくれない部分があってストレスに感じたため、使うのをやめました。

Zettlr

今はこのエディタを使っています。

他のエディタと比べると少し独特でソースコードモードがなく、最初からwysiwygになっている。 少し重めのエディタになっていて、ちょっとファイルを開いてささっと直すような使い方は少し面倒かもしれない。

ghostwriter

「ghostwriter」だけで検索してもエディタの公式サイトにいけない面倒くさいやつ。 あとUIがちょっとださい。

公式サイトに載っている機能の紹介をみると自分が欲しい機能は揃っていそうですが、まだ試せていません。 Zettlrで書いたファイルを開いて、編集してみて動作を確認したいと思います。

おわりに

現状はZettlrですが、今後はghostwriterを試し、それがだめなら再び探索の旅に出たいと思います。 来月はもっと勉強して満足できる記事を書きたいと思います、、、