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.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.9. ライセンス
WebAuthn4jは Apache 2.0 license ライセンスの オープンソースソフトウェアです。
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
の コンストラクタの引数に指定する、 credentialId
と userHandle
、 authenticatorData
、 clientDataJSON
、signature
は フロントエンド側で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#setProduction
で false
を設定する必要があります。
// 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
の コンストラクタの引数に指定する、 keyId
と assertion
、 clientDataHash
は 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
は登録リクエスト検証時の構成証明ステートメントの署名と信頼性の検証は、 それぞれ AttestationStatementVerifier
と CertPathTrustworthinessVerifier
インタフェースの実装に委譲します。
大多数のサイトは厳密な構成証明ステートメントの検証を必要とせず、エンタープライズ用途以外では厳密な構成証明ステートメントの検証は非推奨とされていることから(
WebAuthn仕様書関連個所参照 )、 WebAuthn4Jでは構成証明ステートメントの検証をしないように AttestationStatementVerifier
と
CertPathTrustworthinessVerifier
を構成した WebAuthnManager
のインスタンスを返却する
WebAuthnManager.createNonStrictWebAuthnManager
ファクトリメソッドを用意しています。
もし、エンタープライズなユースケースで、認証デバイスの厳密な検証が要件である場合は、
WebAuthnManager
クラスのコンストラクタを用いて AttestationStatementVerifier
と CertPathTrustworthinessVerifier
の実装をコンストラクタインジェクションして構成して下さい。
3.1. 構成証明ステートメントの検証
構成証明ステートメントの検証は、 AttestationStatementVerifier
インタフェースの実装クラスが提供します。 構成照明ステートメント毎に、対応する実装クラスが提供されていますので、必要なVerifierからなるListを
WebAuthnManager
クラスのコンストラクタに指定して下さい。 例えば、 packed
のみをサポートする場合は、 PackedAttestationStatementVerifier
を唯一の要素とするListとし、 例えば、 packed
, tpm
をサポートする場合は、 PackedAttestationStatementVerifier
と TPMAttestationStatementVerifier
からなるListを指定して下さい。
3.1.1. 構成証明ステートメントの信頼性の検証
構成証明ステートメント自体の信頼性の検証は、証明書パスの検証、自己署名のパターンがありますが、 証明書パスの検証は CertPathTrustworthinessVerifier
インタフェースの実装に検証が委譲されます。
WebAuthn4Jは CertPathTrustworthinessVerifier
インタフェースの実装として DefaultCertPathTrustworthinessVerifier
を提供しています。 DefaultCertPathTrustworthinessVerifier
は TrustAnchorRepository
インタフェースを通じて取得した TrustAnchor
をトラストアンカーとして証明書パスの検証を行うことで構成証明ステートメントの信頼性の検証を行います。
3.1.2. トラストアンカーの解決
前節で TrustAnchorRepository
インタフェースがトラストアンカーの取得に使用されると述べましたが、 TrustAnchorRepository
インタフェースは AAGUID
や attestationCertificateKeyIdentifier
に基づいて TrustAnchor
を返却するインタフェースです。
webauthn4j-core
モジュールでは、 TrustAnchorRepository
の実装として、KeyStoreTrustAnchorRepository
を提供しています。
KeyStoreTrustAnchorRepository
は、Java Key Storeファイルからトラストアンカーを取得します。なお、 KeyStoreTrustAnchorRepository
は AAGUID
や attestationCertificateKeyIdentifier
に応じて異なる TrustAnchorを返却することはせず、
Java Key Storeファイルに登録された証明書を全てトラストアンカーとして扱います。
FIDO Metadata Serviceを用いたトラストアンカーの取得
FIDO Metadata Statement関連の機能を提供する webauthn4j-metadata モジュールは実験的な提供段階です。
|
FIDO Allianceでは、FIDO Metadata Serviceという、認証デバイスのメタデータを配信するサービスを提供しています。
webauthn4j-metadata
モジュールでは、TrustAnchorRepository
の実装として MetadataBLOBBasedTrustAnchorRepository
を提供しています。
MetadataBLOBBasedTrustAnchorRepository
は FidoMDS3MetadataBLOBAsyncProvider
と組み合わせることで
FIDO Metadata Serviceの公開する情報に基づいてトラストアンカーを構築することが可能です。
4. 詳細
4.1. クレデンシャル情報の表現
クレデンシャル情報を表現するインタフェースとして、 CredentialRecord
インタフェースが存在します。 登録時に、 RegistrationData
クラスが含む値を用いて CredentialRecord
インタフェースの インスタンスを作成し、アプリケーションの作法に則り永続化してください。認証時の検証に必要となります。 なお、永続化する際は、検索する際の利便性を考え、credentialIdをキーに永続化すると良いでしょう。
CredentialRecord
インタフェースの実装クラスを設計する際は、アプリケーションの要件に合わせて拡張すると良いでしょう。 典型的には、 CredentialRecord
をユーザーが識別するための名前フィールドの追加などが考えられるでしょう。
4.2. CredentialRecordのシリアライズ、デシリアライズ
認証デバイスの登録時、CredentialRecord
のインスタンスをデータベース等に永続化し、認証時に利用できるようにするのはアプリケーションの責務ですが、
CredentialRecord
を構成する各メンバをシリアライズ、デシリアライズする際に使用できるクラスをWebAuthn4Jでは用意しています。 アプリケーションで永続化を実装する際の補助としてご利用ください。
4.2.1. attestedCredentialData
AttestedCredentialDataConverter
で AttestedCredentialData
を byte[]
に変換したり、その逆の変換を行うことが出来ます。 なお、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.3. DCAppleDeviceのシリアライズ、デシリアライズ
webauthn4j-device-checkでは、CredentialRecord
インタフェースの代わりに、DCAppleDevice
インタフェースを実装したクラスを、構成証明の検証時とアサーションの検証時の間で永続化する必要があります。 概ねCredentialRecordのシリアライズ、デシリアライズで解説した方法でシリアライズ、デシリアライズが可能ですが、一点気を付ける必要がある点として、 webauthn4j-device-check独自のクラス(例えば AppleAppAttestAttestationStatement
)のシリアライズ、デシリアライズを行う為に、
ObjectConverter
は DeviceCheckCBORModule
が登録されたものを使用する必要があります。
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.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.6. カスタムなデータ変換ロジックの実装
WebAuthn4Jでは、JSONやCBORのシリアライズ、デシリアライズ処理にJacksonライブラリを使用しています。 Client ExtensionやAuthenticator Extensionのデータ変換でカスタムな変換を行いたい場合、WebAuthn4Jが内部で使用している Jacksonの ObjectMapper
にカスタムなシリアライザ、デシリアライザを登録することで実現できます。
4.7. カスタムな検証ロジックの実装
WebAuthn4Jでは、カスタムな検証ロジックを実装し、追加することが可能です。 登録時の検証にカスタムロジックを追加する場合は、 CustomRegistrationVerifier
を実装してください。 認証時の検証にカスタムロジックを追加する場合は、 CustomAuthenticationVerifier
を実装してください。
4.8. クラス
4.8.2. Converter, WebAuthnModule
データパッケージ配下のクラスはJacksonによってシリアライズ、デシリアライズ可能なように設計されています。 一部のクラスはカスタムなシリアライザ、デシリアライザが必要であり、 converter
パッケージ配下に集約されています。 カスタムシリアライザ、デシリアライザは WebAuthnJSONModule
と WebAuthnCBORModule
というJacksonのModuleにまとめられています。 WebAuthn4Jは内部で使用するJacksonの ObjectMapper
に自動で WebAuthnModule
を適用しますが、WebAuthnManager
の外部で WebAuthn4Jのシリアライザ、デシリアライザを使用したい場合は、Jacksonの ObjectMapper
に WebAuthnModule
を登録すると 良いでしょう。
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
が提供されています。