1. Introduction
1.1. Web Authentication
Web Authentication is a new, secure web application authentication specification standardized under W3C. By combining local authentication, public-key authentication, per-origin key management, it provides strong authentication to web sites against authentication process attacks like phishing. Implementation is in progress in major browsers, and the specification offers excellent choices for users who place importance on security and convenience. Initially, the specification was developed as FIDO 2.0 by the FIDO Alliance, but it has been transferred to W3C.
1.2. WebAuthn4J
WebAuthn4J is a portable Java library for WebAuthn and Apple App Attest server side verification.
1.3. WebAuthn4J Spring Security
As a related project of WebAuthn4J, we are developing a wrapper library named WebAuthn4J Spring Security to support WebAuthn with Spring Security. To introduce WebAuthn in an application built with Spring Security, it is better to use WebAuthn4J Spring Security rather than directly using WebAuthn4J.
1.4. Feature
1.4.1. Supported Attestation Statement Formats
All attestation statement formats are supported:
-
Packed attestation
-
FIDO U2F attestation
-
Android Key attestation
-
Android SafetyNet attestation
-
TPM attestation
-
Apple Anonymous attestation
-
Apple App Attest attestation
-
None attestation
1.4.2. Conformance
All mandatory test cases and optional Android Key attestation test cases of FIDO2 Test Tools provided by FIDO Alliance are passed.
Since FIDO2 Test Tools runs the test via the REST API of FIDO2 Transport Binding Profile, it is executed through the REST API implementation provided by WebAuthn4j Spring Security. |
1.6. Getting from Maven Central
If you are using Maven, just add the webauthn4j as a dependency:
<properties>
...
<!-- Use the latest version whenever possible. -->
<webauthn4j.version>0.26.0.RELEASE</webauthn4j.version>
...
</properties>
<dependencies>
...
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>${webauthn4j.version}</version>
</dependency>
...
</dependencies>
1.7. Source code
Source code for this project is hosted on Github.
git clone git@github.com:webauthn4j/webauthn4j.git
1.8. License
WebAuthn4J is an open source software licensed under Apache 2.0 license.
2. Quick Start
WebAuthn is an authentication method that a user registers a public key generated by an authenticator to the server in advance and the server verifies the signature generated by the authenticator with the public key at the time of authentication.
This quickstart will focus on how to verify the WebAuthn "attestation" data, which is sent at registration and contains public key and authenticator configuration, and how to verify the WebAuthn "assertion" data, which is sent at authentication and contains signature. Apple App Attest attestation and assertion validation is also explained later.
2.1. WebAuthn verification
2.1.1. WebAuthn Attestation verification
To verify an attestation on authenticator registration, call WebAuthnManager#verify
with a
RegistrationRequest
instance as an argument.
If you would like to access the parsed data when an validation error occurred, please use WebAuthnManager#parse
to parse the registration request and pass the returned RegistrationData
instance to WebAuthnManager#verify
method.
The members of RegistrationRequest
are the values obtained by the WebAuthn JS API in the front end side.
Transmit from the front end side to the server side in some way.
RegistrationParameters
is an another argument for WebAuthnManager#parse
method, and contains server property and validation conditions.
serverProperty
has following members.
-
For
origin
, please specify Origin of the site that provides WebAuthn authentication. WebAuthn specification tells that the authenticator signs the data including Origin recognized by the browser. WebAuthn4J verifies whether the written Origin (namely, recognized by the browser) matches the specified Origin inserverProperty
to prevent phishing attacks, which is a behavior required in WebAuthn specification. -
For
rpId
, please set the rpId of the site that provides WebAuthn authentication. rpId is a parameter that specifies the scope of credentials. For more details, please refer to WebAuthn specification rpId definition. -
For
challenge
, please specify the Challenge issued on WebAuthn JS API call.challenge
is a parameter to prevent replay attacks. By issuing the random byte sequencechallenge
on server side, signing it with WebAuthn JS API, and verifying the signature on server side, users are protected from the replay attack. It is the application’s responsibility for retaining the issued Challenge. -
tokenBindingId
is a parameter for Token binding. If you do not want to use it please specifynull
.
If validation fails, an exception inheriting VerificationException
is thrown.
If validation succeeds, please create an CredentialRecord
instance from the returned value and persist it to the database or something in your application manner.
The instance is required at the time of authentication.
For more details about CredentialRecord
serialization, please refer CredentialRecord serialization and deserialization.
// 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.2. WebAuthn Assertion verification
To parse and verify an assertion on authentication, call WebAuthnManager#verify
with a AuthenticationRequest
instance as an argument.
If you would like to access the parsed data when an validation error occurred, please use
WebAuthnManager#parse
to parse the authentication request and pass the returned AuthenticationData
instance to WebAuthnManager#verify
method.
The members of AuthenticationRequest
are the values obtained by the WebAuthn JS API in the front end side.
Transmit from the front end side to the server side in some way.
AuthenticationParameters is an another argument for WebAuthnManager#parse method, and contains server property, persisted authenticator and validation conditions.
// 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.3. Apple App Attest verification
Next, how to verify Apple App Attest is explained. Since Apple App Attest has a data structure similar to WebAuthn, the validator design follows that of WebAuthn. Risk metric evaluation is not supported for now.
2.3.1. Getting from Maven Central
Apple App Attest validators are contained in the dedicated webauthn4j-device-check
module.
If you are using maven, add the webauthn4j-device-check
as a dependency in this way:
<properties>
...
<!-- Use the latest version whenever possible. -->
<webauthn4j.version>0.24.0.RELEASE</webauthn4j.version>
...
</properties>
<dependencies>
...
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-device-check</artifactId>
<version>${webauthn4j.version}</version>
</dependency>
...
</dependencies>
2.3.2. Apple App Attest attestation verification
To verify an attestation on authenticator registration, call DeviceCheckManager#verify
with a
DCAttestationRequest
instance as an argument.
If you would like to access the parsed data when an validation error occurred, please use DeviceCheckManager#parse
to parse the attestation request and pass the returned DCAttestationData
instance to DeviceCheckManager#verify
method.
The members of DCAttestationRequest
are the values obtained by the Apple App Attest API in the iOS device Transmit from the iOS device to the server side in some way.
DCAttestationParameters
is an another argument for DeviceCheckManager#parse
method, and contains server property and validation conditions.
DCServerProperty
has following members.
-
For
teamIdentifier
, please set the teamIdentifier used for your iOS App development. For more details, please refer to Validating Apps that connect to your server. -
For
cfBundleIdentifier
, please set the cfBundleIdentifier used for your iOS App development. For more details, please refer to Validating Apps that connect to your server. -
For
challenge
, please specify the Challenge issued on App Attest API call.challenge
is a parameter to prevent replay attacks. By issuing the random byte sequencechallenge
on server side, signing it with App Attest API, and verifying the signature on server side, users are protected from the replay attack. It is the application’s responsibility for retaining the issued Challenge.
If validation fails, an exception inheriting VerificationException
is thrown.
If validation succeeds, please create an DCAppleDevice
instance from the returned value and persist it to the database or something in your application manner.
The instance is required at the time of authentication.
Production environment? Development environment?
Apple App Attest can return a development attestation for development.
By default, webAuthn4j-device-check is set to accept a production attestation.
If you want to accept a development attestation, you need to 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.3.3. Apple App Attest assertion verification
To parse and verify an assertion on authentication, call DeviceCheckManager#verify
with a DCAssertionRequest
instance as an argument.
If you would like to access the parsed data when an validation error occurred, please use
DeviceCheckManager#parse
to parse the authentication request and pass the returned DCAssertionData
instance to DeviceCheckManager#verify
method.
The members of DCAssertionRequest
are the values obtained by the App Attest API in the iOS device.
Transmit from the iOS device to the server side in some way.
DCAssertionParameters is an another argument for DeviceCheckManager#parse method, and contains server property, persisted authenticator and validation conditions.
// 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. Configuration
WebAuthn4J has a one main entry point class, WebAuthnManager
.
It delegates attestation statements validation to an implementation of
AttestationStatementVerifier
and attestation statements trustworthiness validation to an implementation of
CertPathTrustworthinessVerifier
.
Since most sites don’t require strict attestation statement validation (WebAuthn Spec related topic ), WebAuthn4J provides WebAuthnManager.createNonStrictWebAuthnManager
factory method that returns an WebAuthnManager
instance configured AttestationStatementVerifier
and
CertPathTrustworthinessVerifier
not to verify attestation statements.
If you are engaging an enterprise use case and strict authenticator verification is a requirement, Use the constructor of the WebAuthnManager
class and inject validators.
3.1. Attestation statement validation
Attestation statement validation is provided by the implementation of AttestationStatementVerifier
interface.
For each attestation statement format, corresponding validator classes are provided.
Please specify its list at the first argument of the constructor of WebAuthnManager
class.
For example, if you would like to limit the supported format to packed
only, add only
PackedAttestationStatementVerifier
to the List
, and if you would like to support packed
and tpm
format, make the List
with PackedAttestationStatementVerifier
and TPMAttestationStatementVerifier
.
3.1.1. Attestation statement trustworthiness validation
Attestation statement trustworthiness validation has two patterns: certificate path validation, and self attestation.
Certificate path validation is delegated via CertPathTrustworthinessVerifier
interface.
WebAuthn4J provides DefaultCertPathTrustworthinessVerifier
as CertPathTrustworthinessVerifier
implementation.
DefaultCertPathTrustworthinessVerifier
verifies trustworthiness by checking the attestation certificate chains to the root certificate provided as TrustAnchor
via TrustAnchorRepository
interface.
3.1.2. Trust anchor resolution
TrustAnchorRepository
is an interface that resolves TrustAnchor
from AAGUID
or attestationCertificateKeyIdentifier
.
webauthn4j-core
module provides a KeyStoreTrustAnchorRepository
as a TrustAnchorRepository
.
KeyStoreTrustAnchorRepository
fetches TrustAnchor
from a Java Key Store. Please note that
KeyStoreTrustAnchorRepository
does not return a different TrustAnchor
depending on AAGUID
or attestationCertificateKeyIdentifier
.
All certificates registered in the Java Key Store file are treated as trust anchors.
Trust anchor resolution using FIDO Metadata Service
webauthn4j-metadata module, which provides FIDO Metadata Statement handling, is under experimental status.
|
FIDO Alliance offers FIDO Metadata Service, which provides metadata of authenticators.
webauthn4j-metadata
module provides a MetadataBLOBBasedTrustAnchorRepository
as a TrustAnchorRepository
implementation.
MetadataBLOBBasedTrustAnchorRepository
can provide trust anchors based on the information published by FIDO Metadata Service when it is used in combination with FidoMDS3MetadataBLOBProvider
.
3.2. What WebAuthn4J doesn’t offer
In order to realize framework independence, WebAuthn4J intentionally scopes functions to WebAuthn Assertion / Attestation verification. Fetching parameters from HTTP request, issuing and saving Challenge in session, counter Value validation are not provided. Please implement in your caller code. If you are using Spring Security, consider using WebAuthn$J Spring Security as it provides these implementations of authentication framework specific parts.
4. Deep-Dive
4.1. Representation of a credential record
WebAuthn4j provides CredentialRecord
interface as a representation of a credential record.
On registering the credential, you need to persist its representation by creating the instance implementing
CredentialRecord
interface in your application manner because it is used afterwards on authentication verification.
It might be better to use credentialId as a search key for this persisted instance.
You can freely enhance the class implementing CredentialRecord
interface in order to meet your application’s requirements.
For example, you can add a field like name
to identify the credential.
4.2. CredentialRecord serialization and deserialization
While it is application’s responsibility to serialize CredentialRecord
instance at registration, WebAuthn4J provides an utility class to serialize or deserialize fields of CredentialRecord
class.
Please use them for implementing persistence in your application.
4.2.1. attestedCredentialData
AttestedCredentialDataConverter
converts from AttestedCredentialData
to byte[]
and vice versa.
If you would like to persist as String
, use Base64UrlUtil
to convert from byte[]
to base64url String
.
AttestedCredentialDataConverter attestedCredentialDataConverter = new AttestedCredentialDataConverter(objectConverter);
// serialize
byte[] serialized = attestedCredentialDataConverter.convert(attestedCredentialData);
// deserialize
AttestedCredentialData deserialized = attestedCredentialDataConverter.convert(serialized);
4.2.2. attestationStatement
Since AttestationStatement
is an interface, there are some implementation classes like PackedAttestationStatement
or AndroidKeyAttestationStatement
per format.
As AttestationStatement
is not self-descriptive for its format, the format need to be persisted in an another field.
Because of that, an envelope class which has attestationStatement field and format field is required, and the envelope class need to be serialized for persisting 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
If you would like to persist as JSON String
, use ObjectConverter
.
String serializedTransports = objectConverter.getJsonConverter().writeValueAsString(transports);
4.3. DCAppleDevice serialization and deserialization
When you use webauthn4j-device-check
, you need to persist DCAppleDevice
instead of CredentialRecord
interface between attestation and assertion.
In general, you can serialize and deserialize it by the method explained in CredentialRecord serialization and deserialization, but ObjectConverter
must be the one with DeviceCheckCBORModule
registered.
A ObjectConverter
with a DeviceCheckCBORModule
can be obtained with DeviceCheckManager.createObjectConverter
static method.
4.4. Using FIDO CTAP2 Security key in your own application other than WebAuthn
For FIDO CTAP2 Security key, WebAuthn is just an application. An original application can use a a security key too. This section describes how to use WebAuthn4J for attestation and assertion validation in your own application using the FIDO CTAP2 security key.
4.4.1. Registration & Authentication flow of your own application using FIDO CTAP2 security key
If you use FIDO CTAP2 security key for authentication in your own application, you need to register the security key first.
Call the authenticatorMakeCredential method of the security key to retrieve the "Attestation" data, which contains public key and device configuration and save it.
The obtained attestation data need to be verified to determine if the security key is acceptable for the application.
WebAuthn4J can verify the attestation with CoreRegistrationVerifier
class.
For authentication, the application need to call the authenticatorGetAssertion method of the security key to retrieve the "assertion" data, which contains signature.
By validating the retrieved assertion, the application can determine whether the security key used for authentication is the same as the one used for registration, and can determine whether the access is legitimate.
WebAuthn4J can verify the assertion with CoreAuthenticationVerifier
class.
4.4.2. How to verify application specific client data
Implementing the above flow will provide authentication feature, but if the entity that calls the FIDO CTAP2 security key (client) and the entity that verifies the attestation and the assertion are separated, in some cases, an application specific client data is needed to be verified at the server at registration and authentication.
The client data itself can be sent together with the attestation and assertion, but in order to protect the client data from MITM attacks, it need to be signed and protected.
In FIDO CTAP2 specification, there is a parameter named clientDataHash
that is common to authenticatorMakeCredential method used at registration and authenticatorGetAssertion method used at authentication.
Since the security key generates a signature from data that contains clientDataHash
, an application can verify its specific client data by setting clientDataHash
to the hash of the client data and validating the signature.
4.5. Project Modules
WebAuthn4J consists of the following four modules.
4.5.1. Core webauthn4j-core.jar
Provides core features for WebAuthn attestation and assertion verification.
4.5.2. Core webauthn4j-device-check.jar
Provides core features for Apple App Attest attestation and assertion verification.
4.5.3. Metadata webauthn4j-metadata.jar
Provides additional features regarding FIDO Metadata Service. As FIDO Metadata Statement specification is still draft, it is in experimental status. The included classes don’t follow semantic versioning and the design may be changed even though it is public.
4.6. Custom converter implementation
WebAuthn4J uses Jackson library for JSON and CBOR serialization and deserialization.
If you would like to custom serialization or deserialization, register custom serializer or deserializer to the underlying Jackson ObjectMapper
.
4.7. Custom validator implementation
WebAuthn4J can add custom validator.
For registration validation, implement CustomRegistrationVerifier
.
For authentication validation, implement CustomAuthenticationVerifier
.
4.8. Classes
4.8.1. Data Transfer Objects
Classes under com.webauthn4j.data
package are designed as immutable DTO.
4.8.2. Converter, Jackson Modules for WebAuthn
Classes under com.webauthn4j.data
package are designed as being serializable and deserializable.
Some Classes under converter
package needs custom serializer and deserializer.
Jackson’s module named
WebAuthnJSONModule
and WebAuthnCBORModule
consolidate these custom serializer and deserializer.
WebAuthn4J’s validators register these modules onto Jackson’s ObjectMapper
automatically.
If you want to use WebAuthn4J’s serializer and deserializer outside of WebAuthnManager, you can register these modules onto Jackson’s ObjectMapper
.
4.8.3. TrustAnchorsResolver
TrustAnchorsResolver
interface is used by TrustAnchorCertPathTrustworthinessVerifier
to explore root certificates in the verification of the authenticity of the attestation statements.
4.8.4. TrustAnchorsProvider
TrustAnchorsProvider
is an interface that TrustAnchorsResolverImpl
delegates TrustAnchor load operation to.
KeyStoreFileTrustAnchorsProvider is provided as an implementation for loading TrustAnchor from Java Key Store file.
WebAuthn$J Spring Security also provides CertFileResourcesTrustAnchorProvider
to load TrustAnchor from Spring Resource.