Skip to content

Fix jarsigner PKIX error for non-exportable AKV certificates via AIA chain completion#47977

Open
rujche with Copilot wants to merge 45 commits into
mainfrom
copilot/fix-jarsigner-certificate-chain-issue
Open

Fix jarsigner PKIX error for non-exportable AKV certificates via AIA chain completion#47977
rujche with Copilot wants to merge 45 commits into
mainfrom
copilot/fix-jarsigner-certificate-chain-issue

Conversation

Copilot AI commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

Problem

When a non-exportable DigiCert certificate in Azure Key Vault is used with jarsigner, the signed JAR fails verification with:

Warning:
This jar contains entries whose certificate chain is invalid. Reason:
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException:
  unable to find valid certification path to requested target

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 during MergeCertificate). The getCertificateChain() 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:

  • jarsigner embeds only the leaf cert in the JAR signature block
  • jarsigner -verify calls Java's PKIX path builder to trace leaf -> intermediate -> root CA
  • The intermediate is not in the JAR and not in cacerts -> path building fails

Solution

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 the caIssuers URL, 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):

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            12:34:56:78:9a:bc
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Example PKI, CN=Example Intermediate CA
        Validity
            Not Before: Jul  1 00:00:00 2026 GMT
            Not After : Jul  1 23:59:59 2028 GMT
        Subject: C=US, O=Example Corp, CN=example-signer

        X509v3 extensions:
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature
            Authority Information Access:
                OCSP - URI:http://ocsp.example.com
                CA Issuers - URI:http://cacerts.example.com/ExampleIntermediateCA.crt
            X509v3 Subject Key Identifier:
                4A:7F:01:2B:...

In this flow, chain completion follows CA Issuers level by level (leaf -> intermediate -> root) until the chain is complete or no issuer can be resolved.

Implementation

Core Algorithm

  • Chain Completion: Standard AIA-based chain completion per RFC 4158.
  • Leaf Selection: Prioritizes certificates with existing issuers in chain; falls back to first non-issuer (order-independent). Self-signed roots are only used as fallback in incomplete chains.
  • Chain End Detection: Uses findValidChainEnd() to identify true chain end (prevents interfering with extra/unplaced certs).
  • Issuer Selection from AIA Payloads: When AIA responds with multiple certs (bundle), selects the cert that matches expected issuer DN and can verify the current cert signature.
  • Duplicate/Existing Issuer Handling: Detects valid issuers already present in chain and repositions to contiguous order when needed.
  • Safety Limits: Max 10 issuer-resolution attempts per chain to prevent infinite loops.

Security Hardening

  • SSRF Protection: System property azure.keyvault.jca.disable-aia-download allows disabling AIA in locked-down environments.
  • Self-Signed Verification: Validates both subject==issuer and signature verification.
  • Issuer Validation:
    • Signature must verify with issuer public key.
    • Issuer must be a CA (basicConstraints or self-signed root).
    • If KeyUsage is present, keyCertSign must be set (RFC 5280 alignment).
  • Robust Fallback Parsing: Supports DER and PEM AIA responses and validates issuer before chain insertion.

Lint and Review Follow-ups

  • Narrowed exception type in isValidIssuer to satisfy SpotBugs (REC_CATCH_EXCEPTION).
  • Updated long log messages to satisfy Checkstyle line-length limits.
  • Aligned chain diagnostic logging (Self-Signed) with signature-based self-signed verification.
  • Corrected findValidChainEnd JavaDoc wording to match actual chain-walking direction.

Changes

File Change
CertificateUtil.java Add completeChainViaAia(), downloadIssuerCertificateFromAia(), findValidChainEnd(), isSelfSignedCertificate(), isValidIssuer(); improve orderCertificateChain(); enforce KeyUsage keyCertSign; select matching issuer from AIA bundles; improve diagnostics and lint compliance
HttpUtil.java Add getBytes(String url) for binary downloads (10s timeout) with exception logging for diagnostics
AiaCertificateChainTest.java Expanded AIA coverage to 13 tests including PEM bundle issuer selection and KeyUsage edge case
CertificateOrderTest.java 5 tests verifying certificate chain ordering (leaf -> intermediate -> root) for PEM and PKCS12 formats, including regression test for incomplete [root, leaf] chains and concrete single-cert edge case
CHANGELOG.md Document bug fix, system property, and security advisory
README.md Add azure.keyvault.jca.disable-aia-download to Exposed Options
checkstyle-suppressions.xml Keep module-level GoodLoggingCheck suppression for CertificateUtil.java/HttpUtil.java (module continues to use java.util.logging)

Test Coverage

PKIX Path Building:

Certificate Ordering (PEM/PKCS12):

  • testPemCertificateChainOrder ✓ Verifies correct leaf -> intermediate -> root ordering
  • testPkcs12CertificateChainOrder ✓ Validates PKCS12 format chain ordering
  • testOrderCertificateChainIncompleteRootFirst ✓ Regression test: [root, leaf] input correctly orders to [leaf, root]

AIA Chain Completion:

  • Leaf-only -> downloads intermediate and root ✓
  • Full chain -> skips unnecessary downloads ✓
  • Cross-signed certificates -> handled correctly ✓
  • Duplicate detection/repositioning -> preserves contiguous valid chain ✓
  • PEM bundle AIA response -> selects matching issuer cert, not first entry ✓
  • KeyUsage CA edge case -> rejects issuer missing keyCertSign when KeyUsage present ✓
  • System property disable -> azure.keyvault.jca.disable-aia-download=true prevents 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:

FEIN: Downloading issuer certificate from AIA URL: http://cacerts.digicert.com/DigiCertTrustedG4CodeSigningRSA4096SHA3842021CA1.crt
FEIN: Downloaded intermediate CA certificate via AIA: CN=DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1,...
FEIN: Downloading issuer certificate from AIA URL: http://cacerts.digicert.com/DigiCertTrustedRootG4.crt
FEIN: Downloaded intermediate CA certificate via AIA: CN=DigiCert Trusted Root G4,...
FEIN: Certificate chain is complete. Root CA: CN=DigiCert Trusted Root G4,...
FEIN: Certificate chain after AIA completion [3 certs]:
  [0] leaf (CN=Industrie Informatik GmbH)
  [1] CN=DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1 | Self-Signed: false
  [2] CN=DigiCert Trusted Root G4 | Self-Signed: true

jarsigner produced no warnings and the full chain was embedded correctly.

Backward Compatibility

  • ✓ Existing behavior preserved for complete chains
  • ✓ AIA completion enabled by default
  • ✓ System property opt-out for locked-down environments
  • ✓ No breaking changes to public APIs

Copilot AI and others added 4 commits February 11, 2026 02:26
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>
Copilot AI changed the title [WIP] Fix jarsigner warning for invalid certificate chain Fix certificate chain ordering for jarsigner compatibility Feb 11, 2026
Copilot AI requested a review from rujche February 11, 2026 02:36
@rujche rujche added azure-spring All azure-spring related issues azure-spring-jca labels Feb 11, 2026
@rujche rujche added this to the 2026-03 milestone Feb 11, 2026
@rujche rujche moved this from Todo to In Progress in Spring Cloud Azure Feb 11, 2026
@rujche rujche moved this from In Progress to Need Feedback in Spring Cloud Azure Feb 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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.

@rujche rujche modified the milestones: 2026-03, Backlog May 8, 2026
rujche added 2 commits July 8, 2026 10:23
…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
@rujche rujche changed the title Fix certificate chain ordering for jarsigner compatibility Fix jarsigner PKIX error for non-exportable AKV certificates via AIA chain completion Jul 8, 2026
@rujche rujche requested a review from Copilot July 8, 2026 02:54
@rujche rujche moved this from Need Feedback to In Progress in Spring Cloud Azure Jul 8, 2026
@rujche rujche removed this from the Backlog milestone Jul 8, 2026
…alidation

Addresses review comments from PR #47977 review 4652501161:

1. **Leaf selection with cross-signed intermediates**: Previously used subject-DN
   matching only. Now verifies that potential issuers can actually validate the
   certificate's signature using isValidIssuer(). This prevents misselection when
   cross-signed/re-issued intermediates share a subject DN but have different keys.

2. **Self-signed detection in leaf selection**: Changed from subject==issuer check
   to signature-verified isSelfSignedCertificate(). This correctly distinguishes
   self-issued certs (same DN but not self-signed) from true self-signed roots.

3. **Chain-building root detection**: Changed from subject==issuer check to
   signature-verified isSelfSignedCertificate() when detecting chain end. Prevents
   early termination on self-issued-but-not-self-signed certs.

4. **Test mock timing**: Moved HttpUtil mock setup before completeChainViaAia
   invocation in aiaDownloadDisabledBySystemProperty test. Ensures property check
   regression would not trigger real network I/O.

5. **Incomplete chain completion**: When valid issuer already exists in array
   but at wrong position (e.g., appended as unplaced cert), now moves it to
   make chain contiguous. Continues loop to download next issuer instead of
   stopping early.

All 95 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

- Fix maxDownloads counter to only decrement on actual HTTP downloads, not during certificate reordering. This prevents premature loop exit when reorganizing existing issuer certificates.
- Replace string concatenation with parameterized logging in HttpUtil.getBytes() to satisfy GoodLoggingCheck.
- Restore GoodLoggingCheck suppressions for CertificateUtil.java and HttpUtil.java (module requires java.util.logging for JCA provider bootstrap).

Addresses: maxDownloads safety limit, GoodLoggingCheck violation in exception logging

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

@rujche rujche requested a review from Copilot July 8, 2026 09:36
@rujche rujche marked this pull request as ready for review July 8, 2026 09:38
@rujche rujche requested review from a team as code owners July 8, 2026 09:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

@rujche rujche requested a review from Copilot July 9, 2026 01:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

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

Labels

azure-spring All azure-spring related issues azure-spring-jca

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[BUG] jarsigner + jca still reports that entries in certificate chain are invalid

4 participants