1. 導入

1.1. Web Authenticationとは

Web Authenticationは、W3Cで2019年3月に勧告になった、Webアプリケーションの新しいセキュアな認証方式仕様です。 ローカル認証や公開鍵認証、Origin別の鍵管理を組み合わせることで、フィッシングなど認証プロセスに対する攻撃に対して堅固な認証を実現しています。 主要ブラウザでの実装が進みつつあり、セキュリティ、利便性を重視するユーザーに対して優れた選択肢を提供します。 当初はFIDO AllianceでFIDO2.0の一部として仕様策定が進められていましたが、現在はW3Cに移管され、仕様策定が行われています。

1.2. WebAuthn4Jとは

WebAuthn4Jは、Web Authentication仕様に基づくサーバーサイド検証を行うためのJavaライブラリです。WebAuthnではなく、Apple App Attestや、FIDO CTAP2セキュリティキーを用いた独自アプリケーションのサーバーサイド検証に用いることも可能です。 Web Authentication仕様で定められた全ての構成証明ステートメント(Attestation)をサポートしながら、外部ライブラリへの依存関係は最小限に抑えたポータブルなライブラリです。

1.3. WebAuthn4J Spring Securityとは

WebAuthn4Jの関連プロジェクトとして、Spring SecurityでWebAuthnを利用するためのラッパーライブラリを WebAuthn4J Spring Security という名前で開発しています。 Spring Securityを活用したアプリケーションでWebAuthnを導入する場合は、WebAuthn4Jを直接利用するのではなく、 WebAuthn4J Spring Securityを利用するのが良いでしょう。

1.4. 特徴

1.4.1. サポートする構成証明ステートメントフォーマット

WebAuthn4Jでは、以下の通り全ての構成証明ステートメントフォーマットをサポートしています。

  • Packed attestation

  • FIDO U2F attestation

  • Android Key attestation

  • Android SafetyNet attestation

  • TPM attestation

  • Apple Anonymous attestation

  • None attestation

  • Apple App Attest attestation

1.4.2. 準拠状況

FIDO Allianceが提供するFIDO2 Test Tools で必須のテストケースに加え、オプションのAndroid Key attestationのテストケースを全て合格しています。

FIDO2 Test ToolsはFIDO2 Transport Binding ProfileのREST API経由でテストを実行する為、WebAuthn4J Spring Securityが 提供するFIDO2 Transport Binding ProfileのREST API実装に対して実行して検証を行っています。

1.4.3. ポータビリティ

WebAuthn4JはSLF4JとJacksonにのみ依存しており、非常に高いポータビリティを持っています。 あなたのJavaアプリへの導入の障害は殆どないでしょう。

1.5. 要件

1.5.1. 言語

  • Java11以降

1.6. 動作環境

  • SecureContext(サイトがHTTPS接続か、localhost)

1.7. Maven Centralからの取得

Mavenを使用している場合、webauthn4jを依存関係として追加して使用してください:

<properties>
  ...
  <!-- Use the latest version whenever possible. -->
  <webauthn4j.version>0.27.0.RELEASE</webauthn4j.version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>com.webauthn4j</groupId>
    <artifactId>webauthn4j-core</artifactId>
    <version>${webauthn4j.version}</version>
  </dependency>
  ...
</dependencies>

1.8. ソースコード

ソースコードは Github で管理されています。

git clone git@github.com:webauthn4j/webauthn4j.git

1.9. ライセンス

WebAuthn4jは Apache 2.0 license ライセンスの オープンソースソフトウェアです。

1.10. 開発への参加

WebAuthn4Jに興味を持って頂きありがとうございます。WebAuthn4JはGitHub上で開発を進めています。 お気軽にIssueやPull Requestをお寄せ下さい。

2. クイックスタート

WebAuthn認証は、事前に認証デバイスで生成した公開鍵をサーバーに登録し、認証時に認証デバイスで生成した署名を公開鍵で検証することで認証が成立する認証方式です。 このクイックスタートでは、WebAuthn4Jを用いてWebAuthn認証における登録時にサーバーに送信される公開鍵やデバイスの構成情報を 含むデータ(構成証明、Attestation)の検証と、認証時にサーバーに送信される署名を含んだデータ(アサーション、Assertion)を検証する方法を解説します。 また、Apple App Attestの構成証明、アサーションを検証する方法も示します。

2.1. WebAuthnの検証

2.1.1. WebAuthn構成証明の検証

認証デバイスの登録時に構成証明を検証する際は、RegistrationRequest を引数に WebAuthnManager#verify メソッドを用いて登録リクエストのパース、検証を行ってください。 登録リクエストの検証でエラーが発生した場合に、元のパースされたデータにアクセスしたい場合は、 WebAuthnManager#parse メソッドを用いて登録リクエストをパースしたうえで、 得られた RegistrationData のインスタンスを WebAuthnManager#verify メソッドに渡して実行してください。

RegistrationRequest のメンバー はフロントエンド側でWebAuthn JS APIを実行して取得した値となります。 何らかの方法でフロントエンド側からサーバー側に伝送し、指定してください。

RegistrationParameters は、WebAuthnManager#verify メソッドのもう一つの引数であり、 サーバーの状態や検証条件をまとめたパラメータです。 サーバーの状態については、 serverProperty としてまとめています。 ServerProperty のコンストラクタを呼び出す際のパラメータには以下の値を指定して下さい。

  • origin にはWebAuthnによる認証を提供するサイトのOriginを指定して下さい。WebAuthnでは、ブラウザが認識しているOriginを ClientDataに書き込んで署名を行います。WebAuthn4Jは書き込まれたOriginが指定されたOriginと合致するかを検証することで、 フィッシング攻撃を防ぎます。

  • rpId にはWebAuthnによる認証を提供するサイトのrpIdを指定して下さい。rpIdは資格情報のスコープを指定するパラメータです。 詳しくは WebAuthnの仕様書のrpIdの項 を参照して下さい。

  • challenge には発行したChallengeを指定して下さい。challenge はリプレイ攻撃を防ぐ為のパラメータです。 サーバー側で challenge としてランダムなバイト列を生成し、フロントエンド側でWebAuthn JS APIを実行する際に パラメータとして指定して署名対象に含め、サーバー側で値の一致を検証することで、リプレイ攻撃からユーザーを防御することが出来ます。 発行したChallengeを検証時まで永続化しておくのはアプリケーション側の責務です。セッションなどに格納しておくと良いでしょう。

  • tokenBindingId はToken bindingを利用する場合のパラメータです。利用しない場合は null を指定してください。

検証に失敗した場合は、 VerificationException のサブクラスの例外が発生します。 検証に成功した場合は、返却された値から CredentialRecord インスタンスを作成し、データベース等へアプリケーション側で永続化して下さい。 認証時に必要となります。永続化方法について詳しくは、 CredentialRecordのシリアライズ、デシリアライズ を参照して下さい。

// Client properties
byte[] attestationObject = null /* set attestationObject */;
byte[] clientDataJSON = null /* set clientDataJSON */;
String clientExtensionJSON = null;  /* set clientExtensionJSON */;
Set<String> transports = null /* set transports */;

// Server properties
Origin origin = null /* set origin */;
String rpId = null /* set rpId */;
Challenge challenge = null /* set challenge */;
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, tokenBindingId);

// expectations
boolean userVerificationRequired = false;
boolean userPresenceRequired = true;

RegistrationRequest registrationRequest = new RegistrationRequest(attestationObject, clientDataJSON, clientExtensionJSON, transports);
RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, userVerificationRequired, userPresenceRequired);
RegistrationData registrationData;
try{
    registrationData = webAuthnManager.parse(registrationRequest);
}
catch (DataConversionException e){
    // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException
    throw e;
}
try{
    webAuthnManager.verify(registrationData, registrationParameters);
}
catch (ValidationException e){
    // If you would like to handle WebAuthn data validation error, please catch ValidationException
    throw e;
}

// please persist CredentialRecord object, which will be used in the authentication process.
CredentialRecord credentialRecord =
    new CredentialRecordImpl( // You may create your own CredentialRecord implementation to save friendly authenticator name
        registrationData.getAttestationObject(),
        registrationData.getCollectedClientData(),
        registrationData.getClientExtensions(),
        registrationData.getTransports()
    );
save(credentialRecord); // please persist credentialRecord in your manner

2.1.2. WebAuthnアサーションの検証

認証時にアサーションを検証する際は、AuthenticationRequest を引数に WebAuthnManager#verify メソッドを 実行してください。AuthenticationRequest の コンストラクタの引数に指定する、 credentialIduserHandleauthenticatorDataclientDataJSONsignature は フロントエンド側でWebAuthn JS APIを実行して取得した値となります。 何らかの方法でフロントエンド側からサーバー側に伝送し、指定してください。 WebAuthnManager#verify メソッドのもう一つの引数である AuthenticationParameters の コンストラクタの引数に指定する、 serverProperty はサーバー側から取得する値をまとめたパラメータです。

userVerificationRequired は認証デバイスでのユーザーの本人性確認が必要かどうかを示すパラメータです。 パスワード+認証デバイスの「所持」による多要素認証を行う場合は、パスワードで本人性の確認が出来ている為 false で良いでしょう。 パスワードレス認証として、認証デバイスによる本人性確認+「所持」による多要素認証を行う場合は true を指定する必要があります。

authenticator には、登録時に永続化した CredentialRecord を指定してください。

検証に失敗した場合は、 VerificationException のサブクラスの例外が発生します。 検証後は、 CredentialRecord に紐づけたcounterおよび、uvInitialized、backedUpの値を更新してください。カウンタは万が一認証デバイスのクローンが 作成された場合を検知するために用意されています。カウンタについて詳しくは WebAuthnの仕様書のカウンタの項 を参照して下さい。

// Client properties
byte[] credentialId = null /* set credentialId */;
byte[] userHandle = null /* set userHandle */;
byte[] authenticatorData = null /* set authenticatorData */;
byte[] clientDataJSON = null /* set clientDataJSON */;
String clientExtensionJSON = null /* set clientExtensionJSON */;
byte[] signature = null /* set signature */;

// Server properties
Origin origin = null /* set origin */;
String rpId = null /* set rpId */;
Challenge challenge = null /* set challenge */;
byte[] tokenBindingId = null /* set tokenBindingId */;
ServerProperty serverProperty = new ServerProperty(origin, rpId, challenge, tokenBindingId);

// expectations
List<byte[]> allowCredentials = null;
boolean userVerificationRequired = true;
boolean userPresenceRequired = true;

CredentialRecord credentialRecord = load(credentialId); // please load authenticator object persisted in the registration process in your manner

AuthenticationRequest authenticationRequest =
        new AuthenticationRequest(
                credentialId,
                userHandle,
                authenticatorData,
                clientDataJSON,
                clientExtensionJSON,
                signature
        );
AuthenticationParameters authenticationParameters =
        new AuthenticationParameters(
                serverProperty,
                credentialRecord,
                allowCredentials,
                userVerificationRequired,
                userPresenceRequired
        );

AuthenticationData authenticationData;
try {
    authenticationData = webAuthnManager.parse(authenticationRequest);
} catch (DataConversionException e) {
    // If you would like to handle WebAuthn data structure parse error, please catch DataConversionException
    throw e;
}
try {
    webAuthnManager.verify(authenticationData, authenticationParameters);
} catch (ValidationException e) {
    // If you would like to handle WebAuthn data validation error, please catch ValidationException
    throw e;
}
// please update the counter of the authenticator record
updateCounter(
        authenticationData.getCredentialId(),
        authenticationData.getAuthenticatorData().getSignCount()
);

2.2. Apple App Attestの検証

続いて、Apple App Attestの検証方法について解説します。 Apple App Attestは、WebAuthnに類似したデータ構造を持つため、Verifierの設計も、WebAuthn用のVerifierを踏襲しています。 なお、リスクメトリックの評価には対応していません。

2.2.1. Maven Centralからの取得

Apple App Attestの検証用クラスは、WebAuthn4J本体(webauthn4j-core)とは別の、webauthn4j-appattestというモジュールとして配布されています。 Mavenを使用している場合、以下のようにwebauthn4j-appattestを依存関係として追加してください。

<properties>
  ...
  <!-- Use the latest version whenever possible. -->
  <webauthn4j.version>0.24.0.RELEASE</webauthn4j.version>
  ...
</properties>

<dependencies>
  ...
  <dependency>
    <groupId>com.webauthn4j</groupId>
    <artifactId>webauthn4j-appattest</artifactId>
    <version>${webauthn4j.version}</version>
  </dependency>
  ...
</dependencies>

2.2.2. Apple App Attest構成証明の検証

認証デバイスの登録時に構成証明を検証する際は、DCAttestationRequest を引数に DeviceCheckManager#verify メソッドを用いて登録リクエストのパース、検証を行ってください。 登録リクエストの検証でエラーが発生した場合に、元のパースされたデータにアクセスしたい場合は、 DeviceCheckManager#parse メソッドを用いて登録リクエストをパースしたうえで、 得られた DCAttestationData のインスタンスを DeviceCheckManager#verify メソッドに渡して実行してください。

DCAttestationRequest のメンバー はiOS上でDevice Check App Attest APIを実行して取得した値となります。 何らかの方法でiOSデバイス側からサーバー側に伝送し、指定してください。

DCAttestationParameters は、DeviceCheckManager#verify メソッドのもう一つの引数であり、 サーバーの状態や検証条件をまとめたパラメータです。 サーバーの状態については、 DCServerProperty としてまとめています。 DCServerProperty のコンストラクタを呼び出す際のパラメータには以下の値を指定して下さい。

  • teamIdentifier にはiOSアプリ開発時のteam identifierを指定してください。 詳しくは Apple Apple Attestのサーバサイド検証手順 を参照して下さい。

  • cfBundleIdentifier にはiOSアプリ開発時のbundle identifierを指定してください。 詳しくは Apple Apple Attestのサーバサイド検証手順 を参照して下さい。

  • challenge には発行したChallengeを指定して下さい。challenge はリプレイ攻撃を防ぐ為のパラメータです。 サーバー側で challenge としてランダムなバイト列を生成し、iOS側でApp Attest APIを実行する際に パラメータとして指定して署名対象に含め、サーバー側で値の一致を検証することで、リプレイ攻撃からユーザーを防御することが出来ます。 発行したChallengeを検証時まで永続化しておくのはアプリケーション側の責務です。セッションなどに格納しておくと良いでしょう。

検証に失敗した場合は、 VerificationException のサブクラスの例外が発生します。 検証に成功した場合は、返却された値から DCAppleDevice インスタンスを作成し、データベース等へアプリケーション側で永続化して下さい。 認証時に必要となります。

商用環境か?開発環境か?

Apple App Attestは、開発中用に開発用の構成証明を返却することが可能です。 WebAuthn4Jはデフォルトでは商用の構成証明を受け入れる設定となっており、 開発用の構成証明を利用する場合は、 DCAttestationDataVerifier#setProductionfalse を設定する必要があります。

// Client properties
byte[] keyId = null; /* set keyId */
byte[] attestationObject = null; /* set attestationObject */
byte[] challenge = null; /* set challenge */
byte[] clientDataHash = MessageDigestUtil.createSHA256().digest(challenge);

// Server properties
String teamIdentifier = null /* set teamIdentifier */;
String cfBundleIdentifier = null /* set cfBundleIdentifier */;
DCServerProperty dcServerProperty = new DCServerProperty(teamIdentifier, cfBundleIdentifier, new DefaultChallenge(challenge));

DCAttestationRequest dcAttestationRequest = new DCAttestationRequest(keyId, attestationObject, clientDataHash);
DCAttestationParameters dcAttestationParameters = new DCAttestationParameters(dcServerProperty);
DCAttestationData dcAttestationData;
try {
    dcAttestationData = deviceCheckManager.parse(dcAttestationRequest);
} catch (DataConversionException e) {
    // If you would like to handle Apple App Attest data structure parse error, please catch DataConversionException
    throw e;
}
try {
    deviceCheckManager.verify(dcAttestationData, dcAttestationParameters);
} catch (ValidationException e) {
    // If you would like to handle Apple App Attest data validation error, please catch ValidationException
    throw e;
}

// please persist Authenticator object, which will be used in the authentication process.
DCAppleDevice dcAppleDevice =
        new DCAppleDeviceImpl( // You may create your own Authenticator implementation to save friendly authenticator name
                dcAttestationData.getAttestationObject().getAuthenticatorData().getAttestedCredentialData(),
                dcAttestationData.getAttestationObject().getAttestationStatement(),
                dcAttestationData.getAttestationObject().getAuthenticatorData().getSignCount(),
                dcAttestationData.getAttestationObject().getAuthenticatorData().getExtensions()
        );
save(dcAppleDevice); // please persist authenticator in your manner

2.2.3. Apple App Attestアサーションの検証

認証時にアサーションを検証する際は、DCAssertionRequest を引数に DeviceCheckManager#verify メソッドを 実行してください。DCAssertionRequest の コンストラクタの引数に指定する、 keyIdassertionclientDataHash は iOS側でApple App Attest APIを実行して取得した値となります。 何らかの方法でフロントエンド側からサーバー側に伝送し、指定してください。 DeviceCheckManager#verify メソッドのもう一つの引数である DCAssertionParameters の コンストラクタの引数に指定する、 serverProperty はサーバー側から取得する値をまとめたパラメータです。

DCAppleDevice には、登録時に永続化した DCAppleDevice を指定してください。

検証に失敗した場合は、 VerificationException のサブクラスの例外が発生します。 検証後は、 DCAppleDevice に紐づけたカウンタの値を更新してください。カウンタは万が一認証デバイスのクローンが 作成された場合を検知するために用意されています。カウンタについて詳しくは WebAuthnの仕様書のカウンタの項 を参照して下さい。

// Client properties
byte[] keyId = null /* set keyId */;
byte[] assertion = null /* set assertion */;
byte[] clientDataHash = null /* set clientDataHash */;

// Server properties
String teamIdentifier = null /* set teamIdentifier */;
String cfBundleIdentifier = null /* set cfBundleIdentifier */;
byte[] challenge = null;
DCServerProperty dcServerProperty = new DCServerProperty(teamIdentifier, cfBundleIdentifier, new DefaultChallenge(challenge));

DCAppleDevice dcAppleDevice = load(keyId); // please load authenticator object persisted in the attestation process in your manner

DCAssertionRequest dcAssertionRequest =
        new DCAssertionRequest(
                keyId,
                assertion,
                clientDataHash
        );
DCAssertionParameters dcAssertionParameters =
        new DCAssertionParameters(
                dcServerProperty,
                dcAppleDevice
        );

DCAssertionData dcAssertionData;
try {
    dcAssertionData = deviceCheckManager.parse(dcAssertionRequest);
} catch (DataConversionException e) {
    // If you would like to handle Apple App Attest data structure parse error, please catch DataConversionException
    throw e;
}
try {
    deviceCheckManager.verify(dcAssertionData, dcAssertionParameters);
} catch (ValidationException e) {
    // If you would like to handle Apple App Attest data validation error, please catch ValidationException
    throw e;
}
// please update the counter of the authenticator record
updateCounter(
        dcAssertionData.getCredentialId(),
        dcAssertionData.getAuthenticatorData().getSignCount()
);

3. 設定

WebAuthn4Jを利用する上で中心となるクラスは WebAuthnManager クラスです。 WebAuthnManager は登録リクエスト検証時の構成証明ステートメントの署名と信頼性の検証は、 それぞれ AttestationStatementVerifierCertPathTrustworthinessVerifier インタフェースの実装に委譲します。

大多数のサイトは厳密な構成証明ステートメントの検証を必要とせず、エンタープライズ用途以外では厳密な構成証明ステートメントの検証は非推奨とされていることから( WebAuthn仕様書関連個所参照 )、 WebAuthn4Jでは構成証明ステートメントの検証をしないように AttestationStatementVerifierCertPathTrustworthinessVerifier を構成した WebAuthnManager のインスタンスを返却する WebAuthnManager.createNonStrictWebAuthnManager ファクトリメソッドを用意しています。

もし、エンタープライズなユースケースで、認証デバイスの厳密な検証が要件である場合は、 WebAuthnManager クラスのコンストラクタを用いて AttestationStatementVerifierCertPathTrustworthinessVerifier の実装をコンストラクタインジェクションして構成して下さい。

3.1. 構成証明ステートメントの検証

構成証明ステートメントの検証は、 AttestationStatementVerifier インタフェースの実装クラスが提供します。 構成照明ステートメント毎に、対応する実装クラスが提供されていますので、必要なVerifierからなるListを WebAuthnManager クラスのコンストラクタに指定して下さい。 例えば、 packed のみをサポートする場合は、 PackedAttestationStatementVerifier を唯一の要素とするListとし、 例えば、 packed, tpm をサポートする場合は、 PackedAttestationStatementVerifierTPMAttestationStatementVerifier からなるListを指定して下さい。

3.1.1. 構成証明ステートメントの信頼性の検証

構成証明ステートメント自体の信頼性の検証は、証明書パスの検証、自己署名のパターンがありますが、 証明書パスの検証は CertPathTrustworthinessVerifier インタフェースの実装に検証が委譲されます。 WebAuthn4Jは CertPathTrustworthinessVerifier インタフェースの実装として DefaultCertPathTrustworthinessVerifier を提供しています。 DefaultCertPathTrustworthinessVerifierTrustAnchorRepository インタフェースを通じて取得した TrustAnchor をトラストアンカーとして証明書パスの検証を行うことで構成証明ステートメントの信頼性の検証を行います。

3.1.2. トラストアンカーの解決

前節で TrustAnchorRepository インタフェースがトラストアンカーの取得に使用されると述べましたが、 TrustAnchorRepository インタフェースは AAGUIDattestationCertificateKeyIdentifier に基づいて TrustAnchor を返却するインタフェースです。 webauthn4j-core モジュールでは、 TrustAnchorRepository の実装として、KeyStoreTrustAnchorRepository を提供しています。 KeyStoreTrustAnchorRepository は、Java Key Storeファイルからトラストアンカーを取得します。なお、 KeyStoreTrustAnchorRepositoryAAGUIDattestationCertificateKeyIdentifier に応じて異なる TrustAnchorを返却することはせず、 Java Key Storeファイルに登録された証明書を全てトラストアンカーとして扱います。

FIDO Metadata Serviceを用いたトラストアンカーの取得
FIDO Metadata Statement関連の機能を提供する webauthn4j-metadata モジュールは実験的な提供段階です。

FIDO Allianceでは、FIDO Metadata Serviceという、認証デバイスのメタデータを配信するサービスを提供しています。 webauthn4j-metadata モジュールでは、TrustAnchorRepository の実装として MetadataBLOBBasedTrustAnchorRepository を提供しています。 MetadataBLOBBasedTrustAnchorRepositoryFidoMDS3MetadataBLOBAsyncProvider と組み合わせることで FIDO Metadata Serviceの公開する情報に基づいてトラストアンカーを構築することが可能です。

3.2. WebAuthn4Jが提供しない機能

WebAuthn4Jは特定のフレームワークに依存しないポータビリティを実現する為、 WebAuthnのAssertion/Attestationの検証に意図的に機能を絞り込んでおり、HTTPリクエストからのパラメータの取出、 Challengeの発行・セッションへの保存、カウンタ値の検証は具備していません。 使用する認証フレームワークに合わせて独自に実装して下さい。Spring Securityをご利用の場合は、 WebAuthn4J Spring Securityがそれらの認証フレームワーク特化の部分の実装を提供しますので活用をご検討下さい。

4. 詳細

4.1. クレデンシャル情報の表現

クレデンシャル情報を表現するインタフェースとして、 CredentialRecord インタフェースが存在します。 登録時に、 RegistrationData クラスが含む値を用いて CredentialRecord インタフェースの インスタンスを作成し、アプリケーションの作法に則り永続化してください。認証時の検証に必要となります。 なお、永続化する際は、検索する際の利便性を考え、credentialIdをキーに永続化すると良いでしょう。 CredentialRecord インタフェースの実装クラスを設計する際は、アプリケーションの要件に合わせて拡張すると良いでしょう。 典型的には、 CredentialRecord をユーザーが識別するための名前フィールドの追加などが考えられるでしょう。

4.2. CredentialRecordのシリアライズ、デシリアライズ

認証デバイスの登録時、CredentialRecord のインスタンスをデータベース等に永続化し、認証時に利用できるようにするのはアプリケーションの責務ですが、 CredentialRecord を構成する各メンバをシリアライズ、デシリアライズする際に使用できるクラスをWebAuthn4Jでは用意しています。 アプリケーションで永続化を実装する際の補助としてご利用ください。

4.2.1. attestedCredentialData

AttestedCredentialDataConverterAttestedCredentialDatabyte[] に変換したり、その逆の変換を行うことが出来ます。 なお、String として保存したい場合、さらに Base64UrlUtil を用いることで byte[] から Base64Url String に変換することが出来ます。

AttestedCredentialDataConverter attestedCredentialDataConverter = new AttestedCredentialDataConverter(objectConverter);

// serialize
byte[] serialized = attestedCredentialDataConverter.convert(attestedCredentialData);
// deserialize
AttestedCredentialData deserialized = attestedCredentialDataConverter.convert(serialized);

4.2.2. attestationStatement

AttestationStatement はインタフェースであり、PackedAttestationStatement や、AndroidKeyAttestationStatement など、フォーマットにあわせて複数の実装があります。 AttestationStatement のCBOR表現は具象型が何であるかについて自己記述性を持たない為、フォーマットは別途、別のフィールドとして永続化する必要があります。 そのため、CBORとしてシリアライズする際は、実際のattestationStatementのフィールドと、フォーマットのフィールドを持つエンベロープクラスを用意し、エンベロープクラスをシリアライズしなければなりません。

//serialize
AttestationStatementEnvelope envelope = new AttestationStatementEnvelope(attestationStatement);
byte[] serializedEnvelope = objectConverter.getCborConverter().writeValueAsBytes(envelope);

//deserialize
AttestationStatementEnvelope deserializedEnvelope = objectConverter.getCborConverter().readValue(serializedEnvelope, AttestationStatementEnvelope.class);
AttestationStatement deserializedAttestationStatement = deserializedEnvelope.getAttestationStatement();
class AttestationStatementEnvelope{

    @JsonProperty("attStmt")
    @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
            property = "fmt"
    )
    private AttestationStatement attestationStatement;

    @JsonCreator
    public AttestationStatementEnvelope(@JsonProperty("attStmt") AttestationStatement attestationStatement) {
        this.attestationStatement = attestationStatement;
    }

    @JsonProperty("fmt")
    public String getFormat() {
        return attestationStatement.getFormat();
    }

    public AttestationStatement getAttestationStatement() {
        return attestationStatement;
    }
}

4.2.3. transports

JSON文字列として保存したい場合は ObjectConverter が使用できます。

String serializedTransports = objectConverter.getJsonConverter().writeValueAsString(transports);

4.2.4. counter

このメンバは元々long型の為、特に変換は不要です。

4.2.5. authenticatorExtensions

authenticatorExtensionsは元来CBORデータの為、CBORに変換できます。

byte[] serializedAuthenticatorExtensions = objectConverter.getCborConverter().writeValueAsBytes(authenticatorExtensions);

4.2.6. clientExtensions

clientExtensionsは元来JSONデータの為、JSONに変換できます。

String serializedClientExtensions = objectConverter.getJsonConverter().writeValueAsString(clientExtensions);

4.3. DCAppleDeviceのシリアライズ、デシリアライズ

webauthn4j-device-checkでは、CredentialRecord インタフェースの代わりに、DCAppleDevice インタフェースを実装したクラスを、構成証明の検証時とアサーションの検証時の間で永続化する必要があります。 概ねCredentialRecordのシリアライズ、デシリアライズで解説した方法でシリアライズ、デシリアライズが可能ですが、一点気を付ける必要がある点として、 webauthn4j-device-check独自のクラス(例えば AppleAppAttestAttestationStatement )のシリアライズ、デシリアライズを行う為に、 ObjectConverterDeviceCheckCBORModule が登録されたものを使用する必要があります。 DeviceCheckCBORModule が登録された`ObjectConverter` は DeviceCheckManager#createObjectConverter で得ることが出来ます。

4.4. WebAuthn以外のFIDO CTAP2セキュリティキーを用いた独自アプリケーションでの利用

FIDO CTAP2セキュリティキーにとって、WebAuthnは一つの応用例でしかなく、セキュアな認証を必要とする独自アプリケーションで セキュリティキーを利用することも可能です。本節では、FIDO CTAP2セキュリティキーを用いた独自アプリケーションにおけるAttestation、Assertion検証でWebAuthn4Jを利用する方法を説明します。

4.4.1. FIDO CTAP2セキュリティキーを用いた独自アプリケーションでの登録、認証のフロー

FIDO CTAP2セキュリティキーを独自アプリケーションで認証に使用する場合、セキュリティキーを登録するために、 アプリからFIDO CTAP2セキュリティキーの authenticatorMakeCredential メソッドを呼び出し、公開鍵やデバイスの構成情報を 含むデータ(構成証明、Attestation)を取得し保存します。 取得されたAttestationは、セキュリティキーがアプリとして受け入れ可能なキーか判定するために検証が必要です。 WebAuthn4Jでは、 CoreRegistrationVerifier クラスを用いることで、取得されたAttestationを検証可能です。

認証時には、同様にアプリからFIDO CTAP2セキュリティキーの authenticatorGetAssertion メソッドを呼び出し、認証時にサーバーに送信される署名を含んだデータ(アサーション、Assertion)を取得します。 取得されたAssertionを検証することで、アプリは認証に用いられたセキュリティキーが、登録時に用いられたセキュリティキーと同一であることを確認し、正当なアクセスか判定することが可能となります。WebAuthn4Jでは、 CoreAuthenticationVerifier クラスを用いることで、取得されたAssertionを検証可能です。

4.4.2. アプリケーション固有のクライアントデータの真正性の担保、検証

上記のフローに従って実装することで、FIDO CTAP2セキュリティキーを用いた安全な認証が実現可能ですが、 FIDO CTAP2セキュリティキーを呼び出す主体(クライアント)と、Attestation、Assertionを検証する主体(サーバー)が分離している場合、クライアントが登録、認証時にアプリケーション固有のクライアントデータを生成し、クライアントデータを追加でサーバーで検証したい場合もあります。クライアントデータ自体はAttestation、Assertionと一緒に送信すれば良いですが、 クライアントデータを中間者攻撃から防御するために、クライアントデータに対して署名を行い、保護する必要があります。

さて、FIDO CTAP2では、登録時に利用する authenticatorMakeCredential メソッドと認証時に利用する authenticatorGetAssertion メソッド 、どちらにも共通するパラメータとして、clientDataHash というパラメータが存在します。セキュリティキーは、受け取った clientDataHash パラメータを署名対象のデータの一部として署名を生成するため、アプリケーションとして署名で保護したいクライアントデータのハッシュを取得し、 clientDataHash にセットすることで、アプリケーション固有のクライアントデータが改竄されていない真正なデータか、サーバー側で検証することが出来ます。

4.5. Project Modules

WebAuthn4Jは、以下の4つのModuleから構成されます。

4.5.1. Core: webauthn4j-core.jar

WebAuthn Attestation/Assertionの検証機能およびコア機能を提供します。

4.5.2. Metadata: webauthn4j-metadata.jar

FIDO Metadata Serviceを用いたTrustAnchorの解決など、追加的な機能を提供します。

4.5.3. Core-Async: webauthn4j-core-async.jar

WebAuthn Attestation/Assertionの検証機能およびコア機能の非同期版を提供します。現時点では、実験的な提供です。 含まれているクラスは、Publicであっても、セマンティックバージョニングに従わずに破壊的変更が入る場合があります。

4.5.4. Metadata-Async: webauthn4j-metadata-async.jar

FIDO Metadata Serviceを用いたTrustAnchorの解決など、追加的な機能の非同期バージョンを提供します。 現時点では、実験的な提供です。 含まれているクラスは、Publicであっても、セマンティックバージョニングに従わずに破壊的変更が入る場合があります。

4.5.5. App Attest: webauthn4j-appattest.jar

Apple App Attest Attestation/Assertionの検証機能を提供します。

4.5.6. Test: webauthn4j-test.jar

WebAuthn4Jのテストを行うための内部ライブラリです。含まれているクラスは、Publicであっても、セマンティックバージョニングに従わずに 破壊的変更が入る場合があります。

4.5.7. Util: webauthn4j-util.jar

WebAuthn4Jライブラリで使用されるユーティリティクラスをまとめたライブラリです。

4.6. カスタムなデータ変換ロジックの実装

WebAuthn4Jでは、JSONやCBORのシリアライズ、デシリアライズ処理にJacksonライブラリを使用しています。 Client ExtensionやAuthenticator Extensionのデータ変換でカスタムな変換を行いたい場合、WebAuthn4Jが内部で使用している Jacksonの ObjectMapper にカスタムなシリアライザ、デシリアライザを登録することで実現できます。

4.6.1. カスタムなデータ変換ロジックの登録

WebAuthn4Jは、Jacksonの ObjectMapperObjectConverter というクラスでラップして使用しており、 カスタムなシリアライザ、デシリアライザを登録した ObjectMapperObjectConverter インスタンス作成時にコンストラクタから インジェクトし、その ObjectConverterWebAuthnManager のインスタンス作成時にパラメータとして指定してください。

4.7. カスタムな検証ロジックの実装

WebAuthn4Jでは、カスタムな検証ロジックを実装し、追加することが可能です。 登録時の検証にカスタムロジックを追加する場合は、 CustomRegistrationVerifier を実装してください。 認証時の検証にカスタムロジックを追加する場合は、 CustomAuthenticationVerifier を実装してください。

4.7.1. カスタム検証ロジックの登録

CustomRegistrationVerifierCustomAuthenticationVerifier の実装は WebAuthnManager のコンストラクタの customRegistrationVerifiers パラメータおよび customAuthenticationVerifiers パラメータを通じて登録することが出来ます。

4.8. クラス

4.8.1. Data transfer Objects

com.webauthn4j.data パッケージ配下のクラスはイミュータブルなDTOとして設計されています。

4.8.2. Converter, WebAuthnModule

データパッケージ配下のクラスはJacksonによってシリアライズ、デシリアライズ可能なように設計されています。 一部のクラスはカスタムなシリアライザ、デシリアライザが必要であり、 converter パッケージ配下に集約されています。 カスタムシリアライザ、デシリアライザは WebAuthnJSONModuleWebAuthnCBORModule というJacksonのModuleにまとめられています。 WebAuthn4Jは内部で使用するJacksonの ObjectMapper に自動で WebAuthnModule を適用しますが、WebAuthnManager の外部で WebAuthn4Jのシリアライザ、デシリアライザを使用したい場合は、Jacksonの ObjectMapperWebAuthnModule を登録すると 良いでしょう。

4.8.3. TrustAnchorsResolver

TrustAnchorsResolver インタフェースは TrustAnchorCertPathTrustworthinessVerifier で構成証明ステートメントの信頼性の 検証を行う際に信頼するルート証明書のセットを探索するために使用されます。

4.8.4. TrustAnchorsProvider

TrustAnchorsProvider インタフェースは前述の TrustAnchorsResolver インタフェースの実装である TrustAnchorsResolverImpl がTrustAnchorの読込処理を委譲する先のインタフェースです。実装としてJava Key StoreファイルからTrustAnchorを読み込む KeyStoreFileTrustAnchorsProvider クラスが提供されている他、WebAuthn4J Spring Securityでは、SpringのResourceから TrustAnchorを読み込む CertFileResourcesTrustAnchorProvider が提供されています。

4.8.5. 例外クラス

データの変換に失敗した場合、 DataConversionException のサブクラスがスローされます。 データの検証に失敗した場合、 VerificationException のサブクラスがスローされます。

4.9. ログ

WebAuthn4JはSLF4Jをログインタフェースライブラリとして使用します。 Logbackなどログ実装ライブラリを構成し、ログをお好みのスタイルで出力してください。

5. 既知の問題

5.1. PS256、PS384、PS512のサポート

WebAuthn4JはPS256、PS384、PS512アルゴリズムをサポートしていません。 PS256、PS384、PS512アルゴリズムはWebAuthn仕様でオプションとされており、当面サポートする予定はありません。