Skip to content

feat(jans-fido2): Jans fido2 attestation rejection diagnostics - #14659

Draft
imran-ishaq wants to merge 7 commits into
mainfrom
jans-fido2-attestation-rejection-diagnostics
Draft

feat(jans-fido2): Jans fido2 attestation rejection diagnostics#14659
imran-ishaq wants to merge 7 commits into
mainfrom
jans-fido2-attestation-rejection-diagnostics

Conversation

@imran-ishaq

Copy link
Copy Markdown
Contributor

Prepare


Description

Target issue

The status endpoints tell an administrator what the trust configuration is, but not why individual registrations are being rejected. There is no categorization of attestation rejection causes: an unknown AAGUID, expired metadata, an attestation format not permitted by the mode, and an untrusted root certificate are indistinguishable from each other and from any other registration failure. The metrics subsystem already has the fields for this — Fido2MetricsEntry carries errorReason and errorCategory — but nothing populates them with trust-specific values, and there is no endpoint to aggregate them.

closes #14641

Implementation Details

This is the only one of the three PRs that touches attestation code paths. No rejection behaviour changes; only the reason gets recorded.

Rejections were previously recorded as the raw exception message, so an unknown AAGUID, an untrusted root, and an unacceptable authenticator status were indistinguishable in metrics. AttestationTrustDiagnostic introduces internal
codes written into the existing errorReason field with errorCategory set to ATTESTATION_TRUST — additive, no new store.

The codes map to messages the failure sites actually raise (MdsService, CertificateVerifier), and each enum constant carries those markers in its definition so the mapping stays traceable to the code that raises it. A failure that is not trust related keeps its original message, and the original message is still logged whenever a code is substituted, so no detail is lost.

categorizeError() checks the JFS_ prefix before its existing keyword matching — otherwise a code containing a keyword would be mis-bucketed (e.g. JFS_MDS_METADATA_EXPIRED reads as "expired" → TIMEOUT).

GET /metrics/analytics/attestation-rejections sits alongside /analytics/errors and reads the same store.

Two deviations from the issue as filed, both deliberate:

  1. No topAaguids. Fido2MetricsEntry has no AAGUID field, so it cannot be computed without a persisted-model
    change. The response returns registrationAttempts instead, which is what makes rejectionRate interpretable. Adding an
    AAGUID dimension is a reasonable follow-up.
  2. 5 codes, not 7. JFS_MDS_METADATA_EXPIRED and JFS_APPLE_ROOT_CA_MISSING have no emitting site in the current
    code — nothing rejects on blob expiry during attestation, and the Apple processor fails on a missing x5c rather than a
    missing root CA. Shipping codes that can never fire would be worse than omitting them; they can be added when a site
    exists.

The public FIDO response envelope is unchanged: ErrorResponseFactory still returns {status:"failed", errorMessage:"…"} and codes reach metrics and structured logs only, never the client body.


Test and Document the changes

  • Static code analysis has been run locally and issues have been fixed
  • Relevant unit and integration tests have been added/updated
  • Relevant documentation has been updated if any (i.e. user guides, installation and configuration guides, technical design docs etc)

Please check the below before submitting your PR. The PR will not be merged if there are no commits that start with docs: to indicate documentation changes or if the below checklist is not selected.

  • I confirm that there is no impact on the docs due to the code changes in this PR.

Add GET /jans-fido2/restv1/trust/attestation/config, a read-only view of
the attestation policy the server is applying: effective mode, whether
unattested authenticators are still accepted, enterprise attestation,
whether the metadata service is disabled, and whether the Apple WebAuthn
root CA was loaded.

unattestedAuthenticatorsAllowed is derived as mode != enforced, not
mode == disabled: AttestationCertificateService.isAttestationEnforced()
applies the stricter MDS trust rules only for enforced, so the default
monitor mode still accepts an authenticator that fails attestation.

The configured mode is reported verbatim alongside a flag for whether it
is recognised, since an unrecognised value silently leaves the server
lenient.

Mirrored through the Config API fido2 plugin for the Admin UI.

Also strips trailing tab characters on two pre-existing lines of
jansFido2Swagger.yaml that made the spec unparseable by strict YAML
loaders.

Signed-off-by: imran <imranishaq7071@gmail.com>
…do2-plugin

The plugin imports io.jans.configapi.util.ApiAccessConstants and
ApiConstants directly but relied on them arriving transitively through
jans-config-api-server. Declare the dependency it actually uses.

Signed-off-by: imran <imranishaq7071@gmail.com>
Adds a Trust Diagnostics page covering the attestation-mode endpoint, the
three attestation modes, and the fact that only "enforced" rejects a
failing attestation - the default "monitor" mode does not.

Signed-off-by: imran <imranishaq7071@gmail.com>
Add GET /jans-fido2/restv1/trust/mds/health, reporting blob validity,
loaded TOC entry count, the outcome of the last refresh, and the
configured metadata servers. A stale or failed MDS load is a common
cause of a previously valid authenticator being rejected, and was
previously visible only in the server log.

TocService now retains the last successful refresh timestamp and the
last refresh error, which were caught and logged then discarded, and
exposes the loaded entry count and the in-memory blob nextUpdate. The
existing getNextUpdateDate() reads the document store and throws on
failure, so it is not used on the request path.

DOWN returns 503 so the endpoint can be wired to a monitor; UP and
DISABLED return 200, since a deliberately switched-off metadata service
is a configuration choice rather than an outage.

MetadataServer.rootCert is reported as a presence boolean only; the
certificate is never returned.

Mirrored through the Config API fido2 plugin for the Admin UI.

Signed-off-by: imran <imranishaq7071@gmail.com>
Extends the Trust Diagnostics page with the MDS health endpoint, the
UP/DISABLED/DOWN status semantics, and the stale-metadata troubleshooting
path.

Signed-off-by: imran <imranishaq7071@gmail.com>
… codes

Attestation rejections were recorded as the raw exception message, so an
unknown AAGUID, an untrusted root, and an unacceptable authenticator
status were indistinguishable in metrics. Introduce internal diagnostic
codes for trust and metadata failures, written into the existing
Fido2MetricsEntry errorReason field with errorCategory ATTESTATION_TRUST.
Additive - no new metrics store.

The codes map to the messages the failure sites actually raise
(MdsService, CertificateVerifier), and each enum constant carries those
markers so the mapping stays traceable. A failure that is not trust
related keeps its original message unchanged, and the original message is
still logged when a code is substituted.

categorizeError() checks the code prefix before its keyword matching,
which would otherwise mis-bucket codes containing a keyword.

Add GET /jans-fido2/restv1/metrics/analytics/attestation-rejections,
alongside the existing /analytics/errors, returning counts per reason
code plus the registration attempts they are measured against. Mirrored
through the Config API fido2 plugin.

The public FIDO response envelope is unchanged: ErrorResponseFactory
still returns {status:"failed", errorMessage:"..."} and the codes reach
metrics and structured logs only, never the client body.

Signed-off-by: imran <imranishaq7071@gmail.com>
Adds the rejection-diagnostics endpoint and a code-to-cause table to the
Trust Diagnostics page, and cross-links it from the passkey telemetry
page's analytics reference.

Signed-off-by: imran <imranishaq7071@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4ff4bd3d-b5ca-4808-82d8-5fb6ff995255

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mo-auto

mo-auto commented Jul 29, 2026

Copy link
Copy Markdown
Member

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(jans-fido2): attestation rejection diagnostics — trust reason codes and rejection analytics

2 participants