1. Deep-Dive

1.1. Representation of an authenticator

WebAuthn4j provides Authenticator interface as a representation of an authenticator.

On registering the authenticator, you need to persist its representation by creating the instance implementing Authenticator 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 Authenticator interface in order to meet your application’s requirements. For example, you can add the field for your application’s user to identify the authenticator.

1.2. Authenticator serialization and deserialization

While it is application’s responsibility to serialize Authenticator instance at registration, WebAuthn4J provides an utility class to serialize or deserialize members of Authenticator class. Please use them for implementing persistence in your application.

1.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);

1.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;
    }
}

1.2.3. transports

If you would like to persist as JSON String, use ObjectConverter.

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

1.2.4. counter

This member is long. Nothing special is required.

1.2.5. authenticatorExtensions

This member can be serialized as CBOR as it is originally CBOR data.

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

1.2.6. clientExtensions

This member can be serialized as JSON as it is originally JSON data.

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

1.3. DCAppleDevice serialization and deserialization

When you use webauthn4j-device-check, you need to persist DCAppleDevice instead of Authenticator interface between attestation and assertion. In general, you can serialize and deserialize it by the method explained in Authenticator 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.

1.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.

1.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 validated to determine if the security key is acceptable for the application. WebAuthn4J can validate the attestation with CoreRegistrationValidator 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 validate the assertion with CoreAuthenticationValidator class.

1.4.2. How to validate 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 validates the attestation and the assertion are separated, in some cases, an application specific client data is needed to be validated 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 validate its specific client data by setting clientDataHash to the hash of the client data and validating the signature.

1.5. Project Modules

WebAuthn4J consists of the following four modules.

1.5.1. Core webauthn4j-core.jar

Provides core features for WebAuthn attestation and assertion verification.

1.5.2. Core webauthn4j-device-check.jar

Provides core features for Apple App Attest attestation and assertion verification.

1.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.

1.5.4. Test webauthn4j-test.jar

Internal library for WebAuthn4J testing. The included classes don’t follow semantic versioning and the design may be changed even though it is public.

1.5.5. Util webauthn4j-util.jar

Contains utility classes used in WebAuthn4J library.

1.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.

1.6.1. Custom converter registration

Since WebAuthn4J wraps ObjectMapper with ObjectConverter, inject your customized ObjectMapper through ObjectConverter constructor and specify the ObjectConverter instance to the WebAuthnManager instance creation parameter.

1.7. Custom validator implementation

WebAuthn4J can add custom validator. For registration validation, implement CustomRegistrationValidator. For authentication validation, implement CustomAuthenticationValidator.

1.7.1. Custom validator registration

CustomRegistrationValidator and CustomAuthenticationValidator implementation can be registered to WebAuthnManager via its constructor’s customRegistrationValidators and customAuthenticationValidators parameters.

1.8. Classes

1.8.1. Data Transfer Objects

Classes under com.webauthn4j.data package are designed as immutable DTO.

1.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.

1.8.3. TrustAnchorsResolver

TrustAnchorsResolver interface is used by TrustAnchorCertPathTrustworthinessValidator to explore root certificates in the verification of the authenticity of the attestation statements.

1.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.

1.8.5. Exceptions

If some verification fails, WebAuthn4J throws an exception class inheriting ValidationException.

1.9. Logging

WebAuthn4J uses SLF4J as log interface library. You can use any kind of this implementation like Logback as you want.