Fix jarsigner PKIX error for non-exportable AKV certificates via AIA chain completion#47977
Conversation
Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
|
Hi @copilot. Thank you for your interest in helping to improve the Azure SDK experience and for your contribution. We've noticed that there hasn't been recent engagement on this pull request. If this is still an active work stream, please let us know by pushing some changes or leaving a comment. Otherwise, we'll close this out in 7 days. |
…ficates When a non-exportable Azure Key Vault certificate is used with jarsigner, the /secrets/ endpoint returns only the leaf certificate (no intermediate CAs). This causes jarsigner -verify to fail with: PKIX path building failed: unable to find valid certification path to requested target Fix: after loading certificates from the AKV secret bundle, walk the chain upward from the current top. If the top cert is not self-signed (i.e. the chain is incomplete), parse the AIA (Authority Information Access) extension (OID 1.3.6.1.5.5.7.1.1) to find the CA Issuers URL and download the missing intermediate CA certificate. Repeat until the chain reaches a self-signed root CA. Changes: - CertificateUtil: add completeChainViaAia() and downloadIssuerCertificateFromAia() methods; call completeChainViaAia() from loadCertificatesFromSecretBundleValue() after ordering - HttpUtil: add getBytes(String url) for binary (DER) certificate downloads - AiaCertificateChainTest: 10 unit tests including two PKIX path-building tests that reproduce the exact reported error and confirm it is resolved Fixes: #44267
vcolin7
left a comment
There was a problem hiding this comment.
The changes look good for an initial review, but there are a few places where I think we can make improvements. If you have any questions, I'd be happy to discuss my comments further :)
…es downloaded via the AIA extension.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
sdk/keyvault/azure-security-keyvault-jca/src/main/java/com/azure/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java:344
- AIA CA Issuers URLs are treated case-sensitively here. URI schemes are case-insensitive, and some certificates may contain
HTTP:///HTTPS://or surrounding whitespace, which would cause chain completion to skip an otherwise valid issuer URL.
String url = location.getName().toString();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
continue; // Only HTTP/HTTPS URLs are supported
}
|
@vcolin7 Thank you so much for your valuable comments. I have analyzed and resolved them one by one. Please review this PR again. Thank you very much! |
completeChainViaAia() logged "Downloaded intermediate CA certificate via AIA" for every issuer it added, but the message was wrong on both counts: the certificate is served from the response cache whenever the URL was already fetched, and the last hop of a chain is a root rather than an intermediate. A production trace showed the message emitted 6 times while only 2 HTTP requests were made, which overstates AIA outbound traffic three-fold when diagnosing network issues. Report the issuer as resolved instead, and assert the download and resolution counts stay distinct across a cached second run.
LOGGER.entering() renders every parameter it is given, so enabling FINER logging wrote the client secret in clear text, along with every Key Vault JSON response body and the private key PEM. A production trace captured at that level held the client secret, 3 access tokens and 6 PKCS12 key bundles. Pass only non-secret parameters. The char[] passwords elsewhere are left untouched: they render as "[C@hash" and reveal nothing.
vcolin7
left a comment
There was a problem hiding this comment.
Thank you for addressing all of my comments @moarychan! This is really great work. There are still a few things to consider I left comments for. Additionally, here's a design suggestion I think we should consider:
I think the cache should retain successful and failed resolutions. Successful responses would honor HTTP freshness metadata, with a bounded fallback such as your already implemented 24 hours and never beyond the downloaded certificate’s validity. Failed or empty responses should have a short negative TTL, perhaps one minute, so a missing or temporarily unavailable endpoint is not retried for every load. Concurrent misses for the same URL could share one in-flight request so they do not issue duplicate downloads. A somewhat larger bounded result cache, such as 128 entries, with targeted expired/LRU eviction would also avoid the current whole-cache clearing behavior.
Co-authored-by: vcolin7 <vicolina@microsoft.com>
…re/security/keyvault/jca/implementation/utils/AiaCertificateChainUtil.java Co-authored-by: vcolin7 <vicolina@microsoft.com>
Co-authored-by: vcolin7 <vicolina@microsoft.com>
A contiguous chain may still be missing issuers above its terminal certificate. Invoke cache-first AIA completion whenever the valid chain does not end in a self-signed root, so multi-level PKI chains can be completed without repeating HTTP requests on subsequent loads.
|
Implemented the cache design in |
Problem
When a non-exportable DigiCert certificate in Azure Key Vault is used with
jarsigner, the signed JAR fails verification with:Fixes #44267 (regression confirmed unfixed in 2.11.0-beta.1 per this comment).
Root Cause
The Azure Key Vault
/secrets/{alias}endpoint returns only the leaf certificate for non-exportable keys (no private key, and no intermediate CA certificates when the caller only provided the leaf cert duringMergeCertificate). ThegetCertificateChain()method therefore returns a one-element array. An earlier fix (PR #41303) added intermediate cert support and PR #47977 added ordering — neither helps when the intermediate is genuinely absent.Without the intermediate CA in the chain:
jarsignerembeds only the leaf cert in the JAR signature blockjarsigner -verifycalls Java's PKIX path builder to trace leaf -> intermediate -> root CAcacerts-> path building failsSolution
After loading certificates from the AKV secret bundle, walk the chain upward. If the top certificate is not self-signed (i.e. the chain is incomplete), parse its AIA (Authority Information Access) extension (OID
1.3.6.1.5.5.7.1.1) to find thecaIssuersURL, download the missing intermediate CA certificate (DER or PEM), and append it. Repeat until the chain reaches a self-signed root CA or no AIA URL is found.This is the standard mechanism used by browsers, AzureSignTool, and major signing tools to complete certificate chains at runtime.
Example (AIA in certificate context):
In this flow, chain completion follows
CA Issuerslevel by level (leaf -> intermediate -> root) until the chain is complete or no issuer can be resolved.Implementation
Core Algorithm
findValidChainEnd()to identify true chain end (prevents interfering with extra/unplaced certs).Security Hardening
azure.keyvault.jca.disable-aia-downloadallows disabling AIA in locked-down environments.basicConstraintsor self-signed root).keyCertSignmust be set (RFC 5280 alignment).Lint and Review Follow-ups
isValidIssuerto satisfy SpotBugs (REC_CATCH_EXCEPTION).Self-Signed) with signature-based self-signed verification.findValidChainEndJavaDoc wording to match actual chain-walking direction.Changes
CertificateUtil.javacompleteChainViaAia(),downloadIssuerCertificateFromAia(),findValidChainEnd(),isSelfSignedCertificate(),isValidIssuer(); improveorderCertificateChain(); enforce KeyUsagekeyCertSign; select matching issuer from AIA bundles; improve diagnostics and lint complianceHttpUtil.javagetBytes(String url)for binary downloads (10s timeout) with exception logging for diagnosticsAiaCertificateChainTest.javaCertificateOrderTest.javaCHANGELOG.mdREADME.mdazure.keyvault.jca.disable-aia-downloadto Exposed Optionscheckstyle-suppressions.xmlCertificateUtil.java/HttpUtil.java(module continues to usejava.util.logging)Test Coverage
PKIX Path Building:
pkixPathBuildingWithoutFixFailsWithReportedError✓ Reproduces exact error from [BUG] jarsigner + jca still reports that entries in certificate chain are invalid #44267pkixPathBuildingWithFixSucceeds✓ Full chain built and PKIX validation succeedsCertificate Ordering (PEM/PKCS12):
testPemCertificateChainOrder✓ Verifies correct leaf -> intermediate -> root orderingtestPkcs12CertificateChainOrder✓ Validates PKCS12 format chain orderingtestOrderCertificateChainIncompleteRootFirst✓ Regression test: [root, leaf] input correctly orders to [leaf, root]AIA Chain Completion:
keyCertSignwhen KeyUsage present ✓azure.keyvault.jca.disable-aia-download=trueprevents AIA downloads ✓Results: 97/97 tests pass (0 failures, 29 skipped)
Customer Validation
Confirmed working by the original reporter (@manfrede) in #44267 (comment) using
azure-security-keyvault-jca-2.12.0-beta.1.jar. The AIA chain completion path executed correctly as shown in debug logs:jarsigner produced no warnings and the full chain was embedded correctly.
Backward Compatibility