Retry MDS trust path validation with truncated x5c - #501
Open
emlun wants to merge 1 commit into
Open
Conversation
In August 2026, the FIDO Metadata Service (MDS) [updated the trust root certificate](https://fidoalliance.org/mds-changelog/) from GlobalSign R3 to R46. Soon after, the `x5c` JWT header was adjusted again to include the new R46 root cross-signed by the old R3 root. This `x5c` that includes the cross-signed R46 fails to validate with trust anchor R46, but validates successfully with trust anchor R3. `CertPathValidator` implements RFC 5280, whose section 6 defines certification path _validation_, but not certification path _construction_. The validation algorithm only validates the particular cert path provided as input, but does not attempt to discover alternative certification paths. The library constructs the cert path explicitly using `CertificateFactory.generateCertPath(List<Certificate>)` with the `x5c` array from the JWT header, which (currently) includes the trailing cross-signed R46 certificate and therefore fails path validation. This change makes `FidoMetadataDownloader` retry the path validation with `x5c` truncated one cert at a time. This makes the `CertPathValidator` successfully validate the cross-signed MDS trust path in the 2nd iteration where the cross-signed R46 is omitted from the cert path. An alternative solution is to use `CertPathBuilder` to _discover_ the cert path (with the intermediate certs from `x5c` provided as an untrusted `CertStore`) instead of taking `x5c` directly as the trust path. This has the drawback of introducing a new checked `CertPathBuilderException` into the public API. We can hide the `CertPathBuilderException` from the public API by wrapping it with `CertPathValidatorException`, but this effectively strips the `reason` value from the `CertPathValidatorException` that would have been thrown previously. See also: #498
emlun
force-pushed
the
mds-truncate-trust-path
branch
from
September 4, 2026 15:34
0127616 to
d1e3f64
Compare
Test Results 288 files 288 suites 6m 0s ⏱️ Results for commit d1e3f64. ♻️ This comment has been updated with latest results. |
fdennis
approved these changes
Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In August 2026, the FIDO Metadata Service (MDS) updated the trust root certificate from GlobalSign R3 to R46. Soon after, the
x5cJWT header was adjusted again to include the new R46 root cross-signed by the old R3 root. Thisx5cthat includes the cross-signed R46 fails to validate with trust anchor R46, but validates successfully with trust anchor R3.CertPathValidatorimplements RFC 5280, whose section 6 defines certification path validation, but not certification path construction. The validation algorithm only validates the particular cert path provided as input, but does not attempt to discover alternative certification paths.The library constructs the cert path explicitly using
CertificateFactory.generateCertPath(List<Certificate>)with thex5carray from the JWT header, which (currently) includes the trailing cross-signed R46 certificate and therefore fails path validation.This change makes
FidoMetadataDownloaderretry the path validation withx5ctruncated one cert at a time. This makes theCertPathValidatorsuccessfully validate the cross-signed MDS trust path in the 2nd iteration where the cross-signed R46 is omitted from the cert path.An alternative solution is to use
CertPathBuilderto discover the cert path (with the intermediate certs fromx5cprovided as an untrustedCertStore) instead of takingx5cdirectly as the trust path. This has the drawback of introducing a new checkedCertPathBuilderExceptioninto the public API. We can hide theCertPathBuilderExceptionfrom the public API by wrapping it withCertPathValidatorException, but this effectively strips thereasonvalue from theCertPathValidatorExceptionthat would have been thrown previously.See also: #498