From 02f0529264774dc7c03bbe8ff1287ed3a13e06fc Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Fri, 6 Feb 2026 10:42:17 +0100 Subject: [PATCH 1/8] Add RETIRED status to AuthenticatorStatus --- .../fido/metadata/AuthenticatorStatus.java | 12 +++++ .../yubico/fido/metadata/FidoMds3Spec.scala | 48 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java index 49c66572c..2e945f95d 100644 --- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java +++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java @@ -99,6 +99,18 @@ public enum AuthenticatorStatus { */ UPDATE_AVAILABLE(0), + /** + * The authenticator vendor has decided to retire the product, that this authenticator should not + * be accepted any longer. For example if a prototype version of the authenticator was added to + * FIDO MDS and has now been superseded by the final product, the entry for the prototype might be + * set to "retired". + * + * @see FIDO + * Metadata Service §3.1.4. AuthenticatorStatus enum + */ + RETIRED(0), + /** * The FIDO Alliance has determined that this authenticator should not be trusted for any reason. * For example if it is known to be a fraudulent product or contain a deliberate backdoor. Relying diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala index 6a0feb143..49c7179f5 100644 --- a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala +++ b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala @@ -562,6 +562,54 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { } } + it("RETIRED AuthenticatorStatus is parsed correctly.") { + val (blobJwt, cert, crls) = makeBlob("""{ + "legalHeader" : "Kom ihåg att du aldrig får snyta dig i mattan!", + "nextUpdate" : "2022-12-01", + "no" : 0, + "entries": [ + { + "aaguid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "statusReports": [ + { + "status": "RETIRED", + "effectiveDate": "2022-02-15" + } + ], + "timeOfLastStatusChange": "2022-02-15" + } + ] + }""") + val downloader: FidoMetadataDownloader = FidoMetadataDownloader + .builder() + .expectLegalHeader("Kom ihåg att du aldrig får snyta dig i mattan!") + .useTrustRoot(cert) + .useBlob(blobJwt) + .clock( + Clock.fixed(Instant.parse("2022-02-15T18:00:00Z"), ZoneOffset.UTC) + ) + .useCrls(crls) + .build() + val mds = + FidoMetadataService.builder().useBlob(downloader.loadCachedBlob()).build() + mds should not be null + + val entries = mds + .findEntries( + Collections.emptyList(), + Some( + new AAGUID(ByteArray.fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + ).toJava, + ) + .asScala + entries should not be empty + entries should have size 1 + entries.head.getStatusReports should have size 1 + entries.head.getStatusReports.get(0).getStatus should be( + AuthenticatorStatus.RETIRED + ) + } + it("More [AuthenticatorTransport] values might be added in the future. FIDO Servers MUST silently ignore all unknown AuthenticatorStatus values.") { val (blobJwt, cert, crls) = makeBlob("""{ "legalHeader" : "Kom ihåg att du aldrig får snyta dig i mattan!", From 1aa28faae45177010e6c445f20dd3779c9bd1541 Mon Sep 17 00:00:00 2001 From: Dennis Fokin Date: Fri, 6 Feb 2026 14:32:50 +0100 Subject: [PATCH 2/8] Add notRetired filter --- .../fido/metadata/FidoMetadataService.java | 20 ++++- .../yubico/fido/metadata/FidoMds3Spec.scala | 84 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java index fdf4b2c95..db235fbbc 100644 --- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java +++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java @@ -263,8 +263,10 @@ public FidoMetadataServiceBuilder useBlob(@NonNull MetadataBLOBPayload blobPaylo * *

The default is {@link Filters#notRevoked() Filters.notRevoked()}. Setting a different * filter overrides this default; to preserve the "not revoked" condition in addition to the new - * filter, you must explicitly include the condition in the few filter. For example, by using - * {@link Filters#allOf(Predicate[]) Filters.allOf(Predicate...)}. + * filter, you must explicitly include the condition in the new filter, for example by using + * {@link Filters#allOf(Predicate[]) Filters.allOf(Predicate...)}. To add the {@link + * Filters#notRetired() Filters.notRetired()} filter, use: + * .prefilter(Filters.allOf(Filters.notRevoked(), Filters.notRetired())). * * @param prefilter a {@link Predicate} which returns true for metadata entries to * include in the data source. @@ -378,6 +380,20 @@ public static Predicate notRevoked() { statusReport -> AuthenticatorStatus.REVOKED.equals(statusReport.getStatus())); } + /** + * Include any metadata entry whose {@link MetadataBLOBPayloadEntry#getStatusReports() + * statusReports} array contains no entry with {@link AuthenticatorStatus#RETIRED RETIRED} + * status. + * + * @see AuthenticatorStatus#RETIRED + */ + public static Predicate notRetired() { + return (entry) -> + entry.getStatusReports().stream() + .noneMatch( + statusReport -> AuthenticatorStatus.RETIRED.equals(statusReport.getStatus())); + } + /** * Accept any authenticator whose matched metadata entry does NOT indicate a compromised * attestation key. diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala index 49c7179f5..871c2ce4e 100644 --- a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala +++ b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala @@ -1037,4 +1037,88 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { } + describe("The notRetired filter") { + val attestationRoot = TestAuthenticator.generateAttestationCaCertificate() + val rootCertBase64 = new ByteArray(attestationRoot._1.getEncoded).getBase64 + + val (goodCert, _) = TestAuthenticator.generateAttestationCertificate( + name = new X500Name("CN=Good cert"), + caCertAndKey = Some(attestationRoot), + ) + + val goodCertKeyIdentifier = new ByteArray( + CertificateParser.computeSubjectKeyIdentifier(goodCert) + ).getHex + + val aaguidRetired = + new AAGUID(ByteArray.fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + + val blob: MetadataBLOBPayload = + JacksonCodecs.jsonWithDefaultEnums.readValue( + s"""{ + "legalHeader" : "Kom ihåg att du aldrig får snyta dig i mattan!", + "nextUpdate" : "2022-12-01", + "no" : 0, + "entries": [ + { + "aaguid": "${aaguidRetired.asGuidString()}", + "attestationCertificateKeyIdentifiers": ["${goodCertKeyIdentifier}"], + "metadataStatement": { + "aaguid": "${aaguidRetired.asGuidString()}", + "attestationCertificateKeyIdentifiers": ["${goodCertKeyIdentifier}"], + "authenticatorVersion": 1, + "attachmentHint" : ["internal"], + "attestationRootCertificates": ["${rootCertBase64}"], + "attestationTypes" : ["basic_full"], + "authenticationAlgorithms" : ["secp256r1_ecdsa_sha256_raw"], + "description" : "Test authenticator", + "keyProtection" : ["software"], + "matcherProtection" : ["software"], + "protocolFamily" : "u2f", + "publicKeyAlgAndEncodings" : ["ecc_x962_raw"], + "schema" : 3, + "tcDisplay" : [], + "upv" : [{ "major" : 1, "minor" : 1 }], + "userVerificationDetails" : [[{ "userVerificationMethod" : "presence_internal" }]] + }, + "statusReports": [ + { "status": "RETIRED", "effectiveDate": "2022-02-01" } + ], + "timeOfLastStatusChange": "2022-02-15" + } + ] + }""".stripMargin, + classOf[MetadataBLOBPayload], + ) + + it("is not enabled by default.") { + val mds = FidoMetadataService.builder().useBlob(blob).build() + + mds + .findTrustRoots( + List(goodCert).asJava, + Some(aaguidRetired.asBytes).toJava, + ) + .getTrustRoots + .asScala should not be empty + } + + it("can be enabled explicitly as a prefilter.") { + val mds = FidoMetadataService + .builder() + .useBlob(blob) + .prefilter(FidoMetadataService.Filters.notRetired()) + .build() + + mds + .findTrustRoots( + List(goodCert).asJava, + Some(aaguidRetired.asBytes).toJava, + ) + .getTrustRoots + .asScala shouldBe empty + } + + } + } From f1121dfb249cc3113198e8d6fcfccc5ef1052f8a Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Mon, 9 Feb 2026 14:31:58 +0100 Subject: [PATCH 3/8] Add `@since` tags to `AuthenticatorStatus.RETIRED` features javadoc --- .../main/java/com/yubico/fido/metadata/AuthenticatorStatus.java | 1 + .../main/java/com/yubico/fido/metadata/FidoMetadataService.java | 1 + 2 files changed, 2 insertions(+) diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java index 2e945f95d..d21a63c94 100644 --- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java +++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java @@ -105,6 +105,7 @@ public enum AuthenticatorStatus { * FIDO MDS and has now been superseded by the final product, the entry for the prototype might be * set to "retired". * + * @since 2.9.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java index db235fbbc..58b1233e3 100644 --- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java +++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java @@ -385,6 +385,7 @@ public static Predicate notRevoked() { * statusReports} array contains no entry with {@link AuthenticatorStatus#RETIRED RETIRED} * status. * + * @since 2.9.0 * @see AuthenticatorStatus#RETIRED */ public static Predicate notRetired() { From 648f4f82e0ba31ae9c6f4267c7aa24a2d25fbfd5 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Mon, 9 Feb 2026 14:32:03 +0100 Subject: [PATCH 4/8] Add `AuthenticatorStatus.RETIRED` features to NEWS --- NEWS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS b/NEWS index 645119bc2..4a865d749 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,12 @@ +== Version 2.9.0 (unreleased) == + +`webauthn-server-attestation`: + +New features: + +* Added `AuthenticatorStatus.RETIRED` and `Filters.notRetired()`. + + == Version 2.8.1 == Fixes: From 260aff2ef867755f66b498a0e8bcd8e7f97f4d76 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Mon, 9 Feb 2026 14:37:49 +0100 Subject: [PATCH 5/8] Add `@since` tags to AttestationTrustSource and FidoMetadataService javadoc --- NEWS | 10 +++++++ .../fido/metadata/FidoMetadataService.java | 28 +++++++++++++++++++ .../attestation/AttestationTrustSource.java | 27 ++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/NEWS b/NEWS index 4a865d749..4792a89d7 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,21 @@ == Version 2.9.0 (unreleased) == +`webauthn-server-core`: + +Fixes: + +* Added `@since` tags to `AttestationTrustSource` javadoc. + `webauthn-server-attestation`: New features: * Added `AuthenticatorStatus.RETIRED` and `Filters.notRetired()`. +Fixes: + +* Added `@since` tags to `FidoMetadataService` javadoc. + == Version 2.8.1 == diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java index 58b1233e3..f5c756e07 100644 --- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java +++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/FidoMetadataService.java @@ -87,6 +87,8 @@ * *

Use the {@link #builder() builder} to configure settings, then use the {@link * #findEntries(List, AAGUID)} method or its overloads to retrieve metadata entries. + * + * @since 2.0.0 */ @Slf4j public final class FidoMetadataService implements AttestationTrustSource { @@ -234,6 +236,7 @@ public static class Step1 { * *

This is an alias of useBlob(blob.getPayload(). * + * @since 2.0.0 * @see FidoMetadataDownloader#loadCachedBlob() * @see #useBlob(MetadataBLOBPayload) */ @@ -247,6 +250,7 @@ public FidoMetadataServiceBuilder useBlob(@NonNull MetadataBLOB blob) { *

The {@link FidoMetadataDownloader#loadCachedBlob()} method returns a value whose {@link * MetadataBLOB#getPayload() .getPayload()} result is suitable for use here. * + * @since 2.0.0 * @see FidoMetadataDownloader#loadCachedBlob() * @see #useBlob(MetadataBLOB) */ @@ -268,6 +272,7 @@ public FidoMetadataServiceBuilder useBlob(@NonNull MetadataBLOBPayload blobPaylo * Filters#notRetired() Filters.notRetired()} filter, use: * .prefilter(Filters.allOf(Filters.notRevoked(), Filters.notRetired())). * + * @since 2.0.0 * @param prefilter a {@link Predicate} which returns true for metadata entries to * include in the data source. * @see #filter(Predicate) @@ -302,6 +307,7 @@ public FidoMetadataServiceBuilder prefilter( * @param filter a {@link Predicate} which returns true for metadata entries to * allow for the corresponding authenticator during credential registration and metadata * lookup. + * @since 2.0.0 * @see #prefilter(Predicate) * @see AuthenticatorToBeFiltered * @see Filters#allOf(Predicate[]) @@ -320,6 +326,7 @@ public FidoMetadataServiceBuilder filter( * * @param certStore a {@link CertStore} of additional CRLs and/or intermediate certificates to * use while validating attestation certificate paths. + * @since 2.0.0 */ public FidoMetadataServiceBuilder certStore(@NonNull CertStore certStore) { this.certStore = certStore; @@ -347,6 +354,7 @@ public FidoMetadataService build() * FidoMetadataServiceBuilder#prefilter(Predicate) prefilter} and {@link * FidoMetadataServiceBuilder#filter(Predicate) filter} settings. * + * @since 2.0.0 * @see FidoMetadataServiceBuilder#prefilter(Predicate) * @see FidoMetadataServiceBuilder#filter(Predicate) */ @@ -360,6 +368,7 @@ public static class Filters { * @param filters A set of filters. * @return A filter which only accepts inputs that satisfy ALL of the given * filters. + * @since 2.0.0 */ @SafeVarargs public static Predicate allOf(Predicate... filters) { @@ -371,6 +380,7 @@ public static Predicate allOf(Predicate... filters) { * statusReports} array contains no entry with {@link AuthenticatorStatus#REVOKED REVOKED} * status. * + * @since 2.0.0 * @see AuthenticatorStatus#REVOKED */ public static Predicate notRevoked() { @@ -407,6 +417,7 @@ public static Predicate notRetired() { * {@link AuthenticatorToBeFiltered#getAttestationCertificateChain() attestation certificate * chain}. * + * @since 2.0.0 * @see AuthenticatorStatus#ATTESTATION_KEY_COMPROMISE */ public static Predicate noAttestationKeyCompromise() { @@ -434,6 +445,8 @@ public static Predicate noAttestationKeyCompromise() /** * This class encapsulates parameters for filtering authenticators in the {@link * FidoMetadataServiceBuilder#filter(Predicate) filter} setting of {@link FidoMetadataService}. + * + * @since 2.0.0 */ @Value @AllArgsConstructor(access = AccessLevel.PRIVATE) @@ -443,12 +456,16 @@ public static class AuthenticatorToBeFiltered { * The attestation certificate chain from the attestation * statement from an authenticator about ot be registered. + * + * @since 2.0.0 */ @NonNull List attestationCertificateChain; /** * A metadata BLOB entry that matches the {@link #getAttestationCertificateChain()} and {@link * #getAaguid()} in this same {@link AuthenticatorToBeFiltered} object. + * + * @since 2.0.0 */ @NonNull MetadataBLOBPayloadEntry metadataEntry; @@ -461,6 +478,8 @@ public static class AuthenticatorToBeFiltered { * *

This will not be present if the attested credential data contained an AAGUID of all * zeroes. + * + * @since 2.0.0 */ public Optional getAaguid() { return Optional.ofNullable(aaguid); @@ -508,6 +527,7 @@ public Optional getAaguid() { * attestationCertificateChain, if any. * * + * @since 2.0.0 * @see #findEntries(List) * @see #findEntries(List, AAGUID) */ @@ -583,6 +603,7 @@ public Set findEntries( /** * Alias of findEntries(attestationCertificateChain, Optional.empty()). * + * @since 2.0.0 * @see #findEntries(List, Optional) */ public Set findEntries( @@ -593,6 +614,7 @@ public Set findEntries( /** * Alias of findEntries(attestationCertificateChain, Optional.of(aaguid)). * + * @since 2.0.0 * @see #findEntries(List, Optional) */ public Set findEntries( @@ -611,6 +633,7 @@ public Set findEntries( * .orElseGet(Collections::emptySet) * * + * @since 2.0.0 * @see #findEntries(List, Optional) */ public Set findEntries(@NonNull RegistrationResult registrationResult) { @@ -623,6 +646,7 @@ public Set findEntries(@NonNull RegistrationResult reg /** * Find metadata entries matching the given AAGUID. * + * @since 2.0.0 * @see #findEntries(List, Optional) */ public Set findEntries(@NonNull AAGUID aaguid) { @@ -640,6 +664,7 @@ public Set findEntries(@NonNull AAGUID aaguid) { * @return All metadata entries which satisfy the {@link * FidoMetadataServiceBuilder#prefilter(Predicate) prefilter} AND for which the filter * returns true. + * @since 2.0.0 * @see #findEntries(List, Optional) */ public Set findEntries( @@ -654,6 +679,9 @@ public Set findEntries( .collect(Collectors.toSet()); } + /** + * @since 2.0.0 + */ @Override public TrustRootsResult findTrustRoots( List attestationCertificateChain, Optional aaguid) { diff --git a/webauthn-server-core/src/main/java/com/yubico/webauthn/attestation/AttestationTrustSource.java b/webauthn-server-core/src/main/java/com/yubico/webauthn/attestation/AttestationTrustSource.java index 290437efc..76ae15b7b 100644 --- a/webauthn-server-core/src/main/java/com/yubico/webauthn/attestation/AttestationTrustSource.java +++ b/webauthn-server-core/src/main/java/com/yubico/webauthn/attestation/AttestationTrustSource.java @@ -57,6 +57,7 @@ public interface AttestationTrustSource { * an empty result. Implementations MAY reuse the same result object, or parts of it, for * multiple calls of this method, even with different arguments, but MUST return an empty set * of trust roots for authenticators that should not be trusted. + * @since 2.0.0 */ TrustRootsResult findTrustRoots( List attestationCertificateChain, Optional aaguid); @@ -77,6 +78,8 @@ TrustRootsResult findTrustRoots( *

  • define a policy tree validator for the PKIX policy tree result - see {@link * TrustRootsResultBuilder#policyTreeValidator(Predicate) policyTreeValidator(Predicate)}. * + * + * @since 2.0.0 */ @Value @Builder(toBuilder = true) @@ -86,6 +89,8 @@ class TrustRootsResult { * A set of attestation root certificates trusted to certify the relevant attestation statement. * If the attestation statement is not trusted, or if no trust roots were found, this should be * an empty set. + * + * @since 2.0.0 */ @NonNull private final Set trustRoots; @@ -99,6 +104,8 @@ class TrustRootsResult { * trustRoots}. * *

    The default is null. + * + * @since 2.0.0 */ @Builder.Default private final CertStore certStore = null; @@ -106,6 +113,8 @@ class TrustRootsResult { * Whether certificate revocation should be checked during certificate path validation. * *

    The default is true. + * + * @since 2.0.0 */ @Builder.Default private final boolean enableRevocationChecking = true; @@ -129,6 +138,8 @@ class TrustRootsResult { * Predicate}. * *

    The default is null. + * + * @since 2.1.0 */ @Builder.Default private final Predicate policyTreeValidator = null; @@ -153,6 +164,8 @@ private TrustRootsResult( * trustRoots}. * *

    The default is null. + * + * @since 2.0.0 */ public Optional getCertStore() { return Optional.ofNullable(certStore); @@ -178,6 +191,8 @@ public Optional getCertStore() { * Predicate}. * *

    The default is null. + * + * @since 2.1.0 */ public Optional> getPolicyTreeValidator() { return Optional.ofNullable(policyTreeValidator); @@ -193,6 +208,8 @@ public static class Step1 { * A set of attestation root certificates trusted to certify the relevant attestation * statement. If the attestation statement is not trusted, or if no trust roots were found, * this should be an empty set. + * + * @since 2.0.0 */ public TrustRootsResultBuilder trustRoots(@NonNull Set trustRoots) { return new TrustRootsResultBuilder().trustRoots(trustRoots); @@ -203,6 +220,8 @@ public TrustRootsResultBuilder trustRoots(@NonNull Set trustRoo * A set of attestation root certificates trusted to certify the relevant attestation * statement. If the attestation statement is not trusted, or if no trust roots were found, * this should be an empty set. + * + * @since 2.0.0 */ // TODO: Let this auto-generate (investigate why Lombok fails to copy javadoc) public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder trustRoots( @@ -224,6 +243,8 @@ public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder trustRoot * TrustRootsResultBuilder#trustRoots(Set) trustRoots}. * *

    The default is null. + * + * @since 2.0.0 */ // TODO: Let this auto-generate (investigate why Lombok fails to copy javadoc) public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder certStore( @@ -237,6 +258,8 @@ public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder certStore * Whether certificate revocation should be checked during certificate path validation. * *

    The default is true. + * + * @since 2.0.0 */ // TODO: Let this auto-generate (investigate why Lombok fails to copy javadoc) public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder @@ -267,6 +290,8 @@ public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder certStore * Predicate}. * *

    The default is null. + * + * @since 2.1.0 */ // TODO: Let this auto-generate (investigate why Lombok fails to copy javadoc) public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder policyTreeValidator( @@ -281,6 +306,8 @@ public AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder policyTre * A set of attestation root certificates trusted to certify the relevant attestation statement. * If the attestation statement is not trusted, or if no trust roots were found, this should be * an empty set. + * + * @since 2.0.0 */ // TODO: Let this auto-generate (investigate why Lombok fails to copy javadoc) @NonNull From 382529263d3d3fbe600735a89650071cf7ab7f74 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Mon, 9 Feb 2026 14:37:51 +0100 Subject: [PATCH 6/8] Add `@since` tags to AuthenticatorStatus javadoc --- NEWS | 2 +- .../fido/metadata/AuthenticatorStatus.java | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 4792a89d7..74f310f93 100644 --- a/NEWS +++ b/NEWS @@ -14,7 +14,7 @@ New features: Fixes: -* Added `@since` tags to `FidoMetadataService` javadoc. +* Added `@since` tags to `AuthenticatorStatus` and `FidoMetadataService` javadoc. == Version 2.8.1 == diff --git a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java index d21a63c94..b05b9ef7c 100644 --- a/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java +++ b/webauthn-server-attestation/src/main/java/com/yubico/fido/metadata/AuthenticatorStatus.java @@ -7,18 +7,24 @@ * or attestationCertificateKeyIdentifiers and potentially some additional information (such as a * specific attestation key). * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum */ public enum AuthenticatorStatus { - /** (NOT DEFINED IN SPEC) Placeholder for any unknown {@link AuthenticatorStatus} value. */ + /** + * (NOT DEFINED IN SPEC) Placeholder for any unknown {@link AuthenticatorStatus} value. + * + * @since 2.0.0 + */ @JsonEnumDefaultValue UNKNOWN(0), /** * This authenticator is not FIDO certified. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -29,6 +35,7 @@ public enum AuthenticatorStatus { * This authenticator has passed FIDO functional certification. This certification scheme is * phased out and will be replaced by {@link #FIDO_CERTIFIED_L1}. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -40,6 +47,7 @@ public enum AuthenticatorStatus { * authenticator could be used without the user’s consent and potentially even without the user’s * knowledge. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -53,6 +61,7 @@ public enum AuthenticatorStatus { * new registrations of the compromised authenticator. The Authenticator manufacturer should set * the date to the date when compromise has occurred. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -65,6 +74,7 @@ public enum AuthenticatorStatus { * to be generated or side channels that allow keys or signatures to be forged, guessed or * extracted. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -75,6 +85,7 @@ public enum AuthenticatorStatus { * This authenticator has known weaknesses in its key protection mechanism(s) that allow user keys * to be extracted by an adversary in physical possession of the device. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -93,6 +104,7 @@ public enum AuthenticatorStatus { * #USER_KEY_PHYSICAL_COMPROMISE}, {@link #REVOKED}. The Relying party MUST reject the Metadata * Statement if the authenticatorVersion has not increased * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -117,6 +129,7 @@ public enum AuthenticatorStatus { * For example if it is known to be a fraudulent product or contain a deliberate backdoor. Relying * parties SHOULD reject any future registration of this authenticator model. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -128,6 +141,7 @@ public enum AuthenticatorStatus { * FIDO Alliance. If this completed checklist is publicly available, the URL will be specified in * url. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -138,6 +152,7 @@ public enum AuthenticatorStatus { * The authenticator has passed FIDO Authenticator certification at level 1. This level is the * more strict successor of {@link #FIDO_CERTIFIED}. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -148,6 +163,7 @@ public enum AuthenticatorStatus { * The authenticator has passed FIDO Authenticator certification at level 1+. This level is the * more than level 1. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -158,6 +174,7 @@ public enum AuthenticatorStatus { * The authenticator has passed FIDO Authenticator certification at level 2. This level is more * strict than level 1+. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -168,6 +185,7 @@ public enum AuthenticatorStatus { * The authenticator has passed FIDO Authenticator certification at level 2+. This level is more * strict than level 2. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -178,6 +196,7 @@ public enum AuthenticatorStatus { * The authenticator has passed FIDO Authenticator certification at level 3. This level is more * strict than level 2+. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum @@ -188,6 +207,7 @@ public enum AuthenticatorStatus { * The authenticator has passed FIDO Authenticator certification at level 3+. This level is more * strict than level 3. * + * @since 2.0.0 * @see FIDO * Metadata Service §3.1.4. AuthenticatorStatus enum From c186ad8d57b7cfde262e2fa48eecd16ca30ef876 Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Mon, 9 Feb 2026 15:01:56 +0100 Subject: [PATCH 7/8] Delete unnecessary attributes from notRetired filter test --- .../yubico/fido/metadata/FidoMds3Spec.scala | 31 +++---------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala index 871c2ce4e..f66394166 100644 --- a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala +++ b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala @@ -1038,18 +1038,6 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { } describe("The notRetired filter") { - val attestationRoot = TestAuthenticator.generateAttestationCaCertificate() - val rootCertBase64 = new ByteArray(attestationRoot._1.getEncoded).getBase64 - - val (goodCert, _) = TestAuthenticator.generateAttestationCertificate( - name = new X500Name("CN=Good cert"), - caCertAndKey = Some(attestationRoot), - ) - - val goodCertKeyIdentifier = new ByteArray( - CertificateParser.computeSubjectKeyIdentifier(goodCert) - ).getHex - val aaguidRetired = new AAGUID(ByteArray.fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) @@ -1062,16 +1050,13 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { "entries": [ { "aaguid": "${aaguidRetired.asGuidString()}", - "attestationCertificateKeyIdentifiers": ["${goodCertKeyIdentifier}"], + "attestationCertificateKeyIdentifiers": [], "metadataStatement": { "aaguid": "${aaguidRetired.asGuidString()}", - "attestationCertificateKeyIdentifiers": ["${goodCertKeyIdentifier}"], "authenticatorVersion": 1, - "attachmentHint" : ["internal"], - "attestationRootCertificates": ["${rootCertBase64}"], + "attestationRootCertificates": [], "attestationTypes" : ["basic_full"], "authenticationAlgorithms" : ["secp256r1_ecdsa_sha256_raw"], - "description" : "Test authenticator", "keyProtection" : ["software"], "matcherProtection" : ["software"], "protocolFamily" : "u2f", @@ -1095,11 +1080,7 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { val mds = FidoMetadataService.builder().useBlob(blob).build() mds - .findTrustRoots( - List(goodCert).asJava, - Some(aaguidRetired.asBytes).toJava, - ) - .getTrustRoots + .findEntries(aaguidRetired) .asScala should not be empty } @@ -1111,11 +1092,7 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { .build() mds - .findTrustRoots( - List(goodCert).asJava, - Some(aaguidRetired.asBytes).toJava, - ) - .getTrustRoots + .findEntries(aaguidRetired) .asScala shouldBe empty } From 157d79adb7f205abdd3d29e2c6ca0ac9b7e461ea Mon Sep 17 00:00:00 2001 From: Emil Lundberg Date: Mon, 9 Feb 2026 15:08:26 +0100 Subject: [PATCH 8/8] Test that Filters.notRetired() does not filter non-retired entries For example, the test now detects if the filter implementation is changed to `(entry) -> false` which was not detected before. --- .../yubico/fido/metadata/FidoMds3Spec.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala index f66394166..069b548a9 100644 --- a/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala +++ b/webauthn-server-attestation/src/test/scala/com/yubico/fido/metadata/FidoMds3Spec.scala @@ -1041,6 +1041,9 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { val aaguidRetired = new AAGUID(ByteArray.fromHex("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + val aaguidNotRetired = + new AAGUID(ByteArray.fromHex("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) + val blob: MetadataBLOBPayload = JacksonCodecs.jsonWithDefaultEnums.readValue( s"""{ @@ -1070,6 +1073,27 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { { "status": "RETIRED", "effectiveDate": "2022-02-01" } ], "timeOfLastStatusChange": "2022-02-15" + }, + { + "aaguid": "${aaguidNotRetired.asGuidString()}", + "attestationCertificateKeyIdentifiers": [], + "metadataStatement": { + "aaguid": "${aaguidNotRetired.asGuidString()}", + "authenticatorVersion": 1, + "attestationRootCertificates": [], + "attestationTypes" : ["basic_full"], + "authenticationAlgorithms" : ["secp256r1_ecdsa_sha256_raw"], + "keyProtection" : ["software"], + "matcherProtection" : ["software"], + "protocolFamily" : "u2f", + "publicKeyAlgAndEncodings" : ["ecc_x962_raw"], + "schema" : 3, + "tcDisplay" : [], + "upv" : [{ "major" : 1, "minor" : 1 }], + "userVerificationDetails" : [[{ "userVerificationMethod" : "presence_internal" }]] + }, + "statusReports": [], + "timeOfLastStatusChange": "2022-02-15" } ] }""".stripMargin, @@ -1082,6 +1106,9 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { mds .findEntries(aaguidRetired) .asScala should not be empty + mds + .findEntries(aaguidNotRetired) + .asScala should not be empty } it("can be enabled explicitly as a prefilter.") { @@ -1094,6 +1121,9 @@ class FidoMds3Spec extends AnyFunSpec with Matchers { mds .findEntries(aaguidRetired) .asScala shouldBe empty + mds + .findEntries(aaguidNotRetired) + .asScala should not be empty } }