Skip to content

Infinite recursion in CertPathBuilder with multiple root CA certificates #2291

@charlesvdv

Description

@charlesvdv

While testing the JCA CertPathBuilder provider with my internal company CA topology, I have encountered an infinite recursion leading to a stack overflow. I have created a minimal reproducible test case in the following gist. The same example work fine when switching to the SUN provider.

From my investigation, in order to reproduce the infinite recursion, the following must be true:

  • CRL must be used for revocation
  • There must be more than one valid root CA certificate

Here is the stack overflow:

	at java.base/java.io.FilterInputStream.read(FilterInputStream.java:71)
	at org.bouncycastle.asn1.DefiniteLengthInputStream.read(Unknown Source)
	at java.base/java.io.FilterInputStream.read(FilterInputStream.java:71)
	at org.bouncycastle.asn1.DefiniteLengthInputStream.read(Unknown Source)
	at java.base/java.io.FilterInputStream.read(FilterInputStream.java:71)
	at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readVector(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readVector(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.buildObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readVector(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readVector(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.buildObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readVector(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readVector(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.buildObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1InputStream.readObject(Unknown Source)
	at org.bouncycastle.asn1.ASN1Primitive.fromByteArray(Unknown Source)
	at org.bouncycastle.asn1.ASN1UniversalType.fromByteArray(Unknown Source)
	at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
	at org.bouncycastle.asn1.x500.X500Name.getInstance(Unknown Source)
	at org.bouncycastle.jce.provider.PrincipalUtils.getX500Name(Unknown Source)
	at org.bouncycastle.jce.provider.PrincipalUtils.getIssuerPrincipal(Unknown Source)
	at org.bouncycastle.jce.provider.CertPathValidatorUtilities.findIssuerCerts(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.engineBuild(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCRLF(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRL(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRLs(Unknown Source)
	at org.bouncycastle.jce.provider.ProvCrlRevocationChecker.check(Unknown Source)
	at org.bouncycastle.jce.provider.ProvRevocationChecker.check(Unknown Source)
	at java.base/java.security.cert.PKIXCertPathChecker.check(PKIXCertPathChecker.java:176)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCertA(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi_8.engineValidate(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.engineBuild(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCRLF(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRL(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRLs(Unknown Source)
	at org.bouncycastle.jce.provider.ProvCrlRevocationChecker.check(Unknown Source)
	at org.bouncycastle.jce.provider.ProvRevocationChecker.check(Unknown Source)
	at java.base/java.security.cert.PKIXCertPathChecker.check(PKIXCertPathChecker.java:176)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCertA(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi_8.engineValidate(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.engineBuild(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCRLF(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRL(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRLs(Unknown Source)
	at org.bouncycastle.jce.provider.ProvCrlRevocationChecker.check(Unknown Source)
	at org.bouncycastle.jce.provider.ProvRevocationChecker.check(Unknown Source)
	at java.base/java.security.cert.PKIXCertPathChecker.check(PKIXCertPathChecker.java:176)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCertA(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi_8.engineValidate(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.engineBuild(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCRLF(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRL(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRLs(Unknown Source)
	at org.bouncycastle.jce.provider.ProvCrlRevocationChecker.check(Unknown Source)
	at org.bouncycastle.jce.provider.ProvRevocationChecker.check(Unknown Source)
	at java.base/java.security.cert.PKIXCertPathChecker.check(PKIXCertPathChecker.java:176)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCertA(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi_8.engineValidate(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.build(Unknown Source)
	at org.bouncycastle.jce.provider.PKIXCertPathBuilderSpi_8.engineBuild(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.processCRLF(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRL(Unknown Source)
	at org.bouncycastle.jce.provider.RFC3280CertPathUtilities.checkCRLs(Unknown Source)
	at org.bouncycastle.jce.provider.ProvCrlRevocationChecker.check(Unknown Source)
	at org.bouncycastle.jce.provider.ProvRevocationChecker.check(Unknown Source)
	at java.base/java.security.cert.PKIXCertPathChecker.check(PKIXCertPathChecker.java:176)
...

The root cause seems to come from here. The validation for the CRL re-trigger a validation of the root CA certificate which includes a validation of the CRL, etc.

In RFC5280 (which obsolete RFC3280), the following security consideration has been added (emphasis mine) which seems similar to the issue described above:

When certificates include a cRLDistributionPoints extension with an
https URI or similar scheme, circular dependencies can be introduced.
The relying party is forced to perform an additional path validation
in order to obtain the CRL required to complete the initial path
validation!
Circular conditions can also be created with an https
URI (or similar scheme) in the authorityInfoAccess or
subjectInfoAccess extensions. At worst, this situation can create
unresolvable dependencies.

Thank you for your support.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions