yubico/java-webauthn-server

repository·main·Indexed 20 days ago

https://github.com/yubico/java-webauthn-server

A Java library for WebAuthn server implementation. It includes the webauthn-server-attestation module for verifying attestation statements via the FIDO Metadata Service (MDS) and the webauthn-server-core module for core functionality. The library supports WebAuthn Level 2 and provides tools for managing Relying Party identities, user verification, and resident key configurations.

Tokens
12.1K
Snippets
34
Records
46
Agent score
69%

What's inside java-webauthn-server

  1. Replace `MetadataService` with `AttestationTrustSource`

    main

    The MetadataService interface has been replaced by AttestationTrustSource.

    Key differences:

    • Validation: MetadataService implementations were responsible for validating the attestation certificate path. AttestationTrustSource implementations only need to retrieve trust root certificates; RelyingParty.finishRegistration now performs path validation internally.
    • Scope: AttestationTrustSource focuses on what is necessary for certificate path validation. While it can return a CertStore for untrusted certificates/CRLs, it does not return full attestation metadata to the core library's result types. For metadata access, use the webauthn-server-attestation module.
  2. How to select trusted authenticators using filters

    main

    The FidoMetadataService uses an "allow-list" policy via two types of filters. Any metadata entry that passes these filters is considered a valid trust root.

    1. Prefilter: Executed once when the FidoMetadataService is constructed. It selects which metadata entries from the BLOB are included in the service's data source.
    2. Registration-time Filter: Executed during credential registration and when calling findEntries(). It decides whether a specific metadata entry matches a particular authenticator.

    Customizing Filters

    Use .prefilter(Predicate) and .filter(Predicate) on the FidoMetadataServiceBuilder.

    Warning: Setting a custom filter replaces the default filter. To keep the default behavior (which excludes REVOKED authenticators in the prefilter and ATTESTATION_KEY_COMPROMISE in the registration-time filter), you must combine your custom predicate with the default ones using FidoMetadataService.Filters.allOf().

  3. Managing Metadata BLOB updates

    main

    The FidoMetadataDownloader does not automatically schedule downloads. To keep metadata up to date, you must use an external scheduling mechanism (e.g., a cron job or background task) to periodically execute one of the following:

    • loadCachedBlob(): Downloads a new BLOB only if the cache is empty, invalid, or out of date.
    • refreshBlob(): Always attempts to download a new BLOB, falling back to the cached version only if the new download is invalid.

    After a successful download/refresh, you must rebuild your FidoMetadataService and RelyingParty instances with the updated metadata. This is low-impact because these classes do not maintain internal mutable state.

  4. Migrate from webauthn-server-core-minimal to webauthn-server-core

    main

    If you are using the webauthn-server-core-minimal module, you must replace it with webauthn-server-core when migrating to version 2.0.0 or later.

     <dependency>
       <groupId>com.yubico</groupId>
    -  <artifactId>webauthn-server-core-minimal</artifactId>
    -  <version>1.12.2</version>
    +  <artifactId>webauthn-server-core</artifactId>
    +  <version>2.4.0-RC2</version>
       <scope>compile</scope>
     </dependency>
    -compile 'com.yubico:webauthn-server-core-minimal:1.12.2'
    +compile 'com.yubico:webauthn-server-core:2.4.0-RC2'
  5. Create a Release Candidate (RC) version

    main

    Follow these steps to prepare and publish a pre-release (RC) version. This process involves updating release notes, verifying API changes, running tests, and publishing to Maven Central.

    1. Preparation

    • Update release notes in NEWS.
    • Review public API changes. If new dependencies are exposed in method signatures or throws declarations, change their Gradle dependency type from implementation to api. Remove or downgrade dependencies no longer exposed in the public API.
    • Add @since tags to JavaDoc for new features.
    • Run tests: ./gradlew clean check.
    • Update the Java version in .github/workflows/release-verify-signatures.yml to match your local java -version output.

    2. Pushing Changes

    • If the README needs significant updates that don't reflect the current stable version, push to a new release branch:
      git checkout -b release-1.4.0
      git push origin release-1.4.0
    • Otherwise, push directly to main.

    3. Verification and Tagging

    • After the build workflow finishes, download artifact-checksums-java17-temurin.zip, unpack it, and verify checksums against a local build:
      unzip artifact-checksums-java17-temurin.zip
      VERSION=0.1.0-SNAPSHOT ./gradlew primaryPublishJar
      sha256sum -c java-webauthn-server-artifacts.sha256sum
    • Tag the head commit with an X.Y.Z-RCN format:
      git tag -a -s 1.4.0-RC1 -m "Pre-release 1.4.0-RC1"

    4. Publishing

    • Publish to Sonatype Maven Central:
      ./gradlew publish jreleaserDeploy
    • Push the tag to GitHub:
      git push origin 1.4.0-RC1
    • Create a GitHub release using the tag, check the pre-release checkbox, and copy/reformat release notes from NEWS (convert ASCIIdoc to Markdown).
    # See full procedure in content for multiple command blocks
  6. Replace Attestation with MetadataBLOBPayloadEntry in data models

    main

    If your application (e.g., a front-end) consumes attestation metadata, you must update the data structure mapping. The Attestation object is replaced by MetadataBLOBPayloadEntry.

    Specifically, if you were accessing deviceProperties.description, you should now access metadataStatement.description in the resulting JSON/object structure.

     var registrationResult = fetch(/* ... */).then(response => response.json());
    -var authenticatorName = registrationResult.attestationMetadata?.deviceProperties?.description;
    +
    +var authenticatorName = registrationResult.attestationMetadata?.metadataStatement?.description;
  7. Retrieve attestation metadata after registration

    main

    In version 2.0+, RegistrationResult no longer contains attestation metadata directly. To retrieve metadata (such as device descriptions) after a successful registration, use the findEntries method on your FidoMetadataService instance, passing in the RegistrationResult.

    FidoMetadataService mds = /* ... */;
    RegistrationResult result = rp.finishRegistration(/* ... */);
    Optional<String> authenticatorName = mds.findEntries(result)
        .stream()
        .findAny()
        .flatMap(MetadataBLOBPayloadEntry::getMetadataStatement)
        .flatMap(MetadataStatement::getDescription);
  8. Configure JReleaser for publishing

    main

    To publish releases, you must configure JReleaser by creating or updating the $HOME/.jreleaser/config.properties file. This file requires your Sonatype user token credentials and your GPG key fingerprint.

    1. Generate a Sonatype user token at https://central.sonatype.com/usertoken.
    2. Add the following properties to $HOME/.jreleaser/config.properties:
      • JRELEASER_MAVENCENTRAL_USERNAME
      • JRELEASER_MAVENCENTRAL_PASSWORD
      • JRELEASER_GPG_KEYNAME (the fingerprint of your GPG key)
      • JRELEASER_MAVENCENTRAL_STAGE
      • JRELEASER_GITHUB_TOKEN (a placeholder value is acceptable if a valid token is not used)
    JRELEASER_MAVENCENTRAL_USERNAME=PYgw7b
    JRELEASER_MAVENCENTRAL_PASSWORD=QxExuJ0wwfBzbXVOsaSTUTBkXH8Fa2dFo
    JRELEASER_GPG_KEYNAME=2D6753CFF0B0FB32F9EEBA485B9688125FF0B636
    JRELEASER_MAVENCENTRAL_STAGE=FULL
    JRELEASER_GITHUB_TOKEN=nope
  9. Enable non-standard legacy encoding with TextEncoder

    main

    Standard compliance only allows encoding to UTF-8. To force the polyfill to encode to legacy encodings (like windows-1252), pass the NONSTANDARD_allowLegacyEncoding: true option to the TextEncoder constructor.

    Warning: This will not work if the browser already has a native TextEncoder implementation. To force the polyfill to be used in a browser with native support, nullify the global TextEncoder and TextDecoder before loading the polyfill scripts.

    <!-- Force polyfill usage in browsers with native support -->
    <script>
    window.TextEncoder = window.TextDecoder = null;
    </script>
    <script src="encoding-indexes.js"></script>
    <script src="encoding.js"></script>
    // Force legacy encoding
    var uint8array = new TextEncoder(
      'windows-1252', 
      { NONSTANDARD_allowLegacyEncoding: true }
    ).encode(text);
  10. Update RelyingParty integration for attestation

    main

    When configuring the RelyingParty builder, the method used to provide metadata services has changed. Replace .metadataService(metadataService) with .attestationTrustSource(metadataService).

     RelyingParty rp = RelyingParty.builder()
         .identity(rpIdentity)
         .credentialRepository(credentialRepo)
         .attestationConveyancePreference(AttestationConveyancePreference.DIRECT)
    -    .metadataService(metadataService))
         .allowUntrustedAttestation(true)
         .build();
    +
    +    .attestationTrustSource(metadataService)
         .allowUntrustedAttestation(true)
         .build();
  11. How to use FIDO Metadata Service for attestation verification

    main

    The webauthn-server-attestation module extends the core library by interfacing with the FIDO Metadata Service (MDS). This allows you to verify attestation statements during credential registration.

    Implementation Steps

    1. Initialize the Downloader and Service: Use FidoMetadataDownloader to manage downloading and caching metadata BLOBs, and FidoMetadataService to provide the metadata to the RelyingParty.

      • Note: FidoMetadataDownloader is NOT thread-safe because it performs cache read/write operations. Ensure only one call to loadCachedBlob() or refreshBlob() executes at a time. FidoMetadataService is thread-safe.
      • Recommendation: Use .verifyDownloadsOnly(true) on the downloader to prevent cache expiration if the BLOB certificate expires.
    2. Configure the RelyingParty: Set the attestationTrustSource to your FidoMetadataService instance. To request attestation, set attestationConveyancePreference to AttestationConveyancePreference.DIRECT. Optionally, set allowUntrustedAttestation(false) to reject authenticators not found in the MDS.

    3. Inspect Registration Results: After finishRegistration(), check RegistrationResult.isAttestationTrusted() to see if the authenticator's attestation was verified against trusted certificates in the MDS.

    4. Retrieve Metadata: Use FidoMetadataService.findEntries(result) to get additional metadata for a successful registration.

    FidoMetadataDownloader downloader = FidoMetadataDownloader.builder()
      .expectLegalHeader("Lorem ipsum dolor sit amet")
      .useDefaultTrustRoot()
      .useTrustRootCacheFile(new File("/var/cache/webauthn-server/fido-mds-trust-root.bin"))
      .useDefaultBlob()
      .useBlobCacheFile(new File("/var/cache/webauthn-server/fido-mds-blob.bin"))
      .verifyDownloadsOnly(true)
      .build();
    
    FidoMetadataService mds = FidoMetadataService.builder()
      .useBlob(downloader.loadCachedBlob())
      .build();
    
    RelyingParty rp = RelyingParty.builder()
      .identity(/* ... */)
      .credentialRepository(/* ... */)
      .attestationTrustSource(mds)
      .attestationConveyancePreference(AttestationConveyancePreference.DIRECT)
      .allowUntrustedAttestation(true)
      .build();
    
    RegistrationResult result = rp.finishRegistration(/* ... */);
    
    if (result.isAttestationTrusted()) {
      // Do something...
    } else {
      // Do something else...
    }
    
    Set<MetadataBLOBPayloadEntry> metadata = mds.findEntries(result);
  12. Format code using Spotless

    main

    The project uses the Spotless formatter to maintain code style according to the Google Java Style Guide. You can automatically format your code using the Gradle wrapper.

    To run the formatter once: ./gradlew spotlessApply

    To run the formatter in continuous mode (reformatting files whenever they change): ./gradlew --continuous spotlessApply

    ./gradlew spotlessApply