X.509 Certificate with critical extendedKeyUsage (EKU) extension behaves differently between JDK and BC
Loading an X.509 certificate through the JCE CertificateFactory using either the JDK or the BC provider results in an instance of the JCE X509Certificate that gives different answers from its hasUnsupportedCriticalExtension() method.
While the JDK apparently treats the EKU extension as supported when being marked as critical, the instance from the BC provider considers such an EKU as unsupported.
I'd assume that the method X509CertificateImpl.hasUnsupportedCriticalExtension() ([1]) should be extended to also skip over the Extension.extendedKeyUsage OID to treat it as supported.
However, I am uncertain what implications would arise from such a change. But I still assume that this extension can be considered as fully supported in BC, especially as RFC 5280 ([2]) explicitly states it being allowed to be marked as critical.
[1] https://github.com/bcgit/bc-java/blob/7324d9ad6bd8443c6ef0b08fd9eb854de79039a1/prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java#L404
[2] https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12
PS: in case sample code or a sample certificate would help, I'd be happy to provide the same.
You can mark any extension as Critical. The impact:
- If a certificate processing entity cannot recognize/parse/process an extension, and it is not marked Critical - this extension is ignored and the processing continues;
- If a processing entity can't recognize an extension, and it is marked Critical - the processing aborts.
Thanks for that clarification.
The question I would like to clarify in this issue is, whether inclusion of the EKU as being supported in BC will have any unintended side effects.
If code that makes use of the JCE method hasUnsupportedCriticalExtension() relies on a stable behavior, regardless of whether a certificate had been parsed using the JDK or the BC CertificateFactory, that code will currently see a different behavior.
Client code sometimes uses the outcome of that method to determine whether to accept a certificate or not - which might make such code fail when the BC provider is added with higher priority than the standard JDK provider.
Here's a small sample that illustrates the different behavior:
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public final class CriticalEKU {
private static final String CERT_PEM =
"-----BEGIN CERTIFICATE-----\n" +
"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\n" +
"-----END CERTIFICATE-----\n";
public static void main( final String[] pArgs ) throws Exception
{
Security.addProvider( new BouncyCastleProvider() );
evaluateCertFromProvider( null );
evaluateCertFromProvider( "BC" );
} // main
private static void evaluateCertFromProvider( final String pProviderName ) throws Exception
{
System.out.println( "----------------------------------------------" );
System.out.println( "provider: " + ( pProviderName == null ? "default" : pProviderName ) );
final CertificateFactory lFactory = pProviderName == null ?
CertificateFactory.getInstance( "X509" ) :
CertificateFactory.getInstance( "X509", pProviderName );
final X509Certificate lCert =
( X509Certificate )lFactory.generateCertificate(
new ByteArrayInputStream( CERT_PEM.getBytes( StandardCharsets.UTF_8 ) ) );
System.out.println( "extension OIDs:");
System.out.println( "- non-critical: " + lCert.getNonCriticalExtensionOIDs() );
System.out.println( "- critical: " + lCert.getCriticalExtensionOIDs() );
System.out.println( "=> has unsupported critical extension: " +
lCert.hasUnsupportedCriticalExtension() );
} // evaluateCertFromProvider
}
which will result in the following output when run on Java 17 with BouncyCastle 1.78.1:
----------------------------------------------
provider: default
extension OIDs:
- non-critical: [1.3.6.1.5.5.7.1.1, 2.5.29.14, 2.5.29.31, 2.5.29.35]
- critical: [2.5.29.15, 2.5.29.19, 2.5.29.37]
=> has unsupported critical extension: false
----------------------------------------------
provider: BC
extension OIDs:
- non-critical: [1.3.6.1.5.5.7.1.1, 2.5.29.31, 2.5.29.35, 2.5.29.14]
- critical: [2.5.29.19, 2.5.29.15, 2.5.29.37]
=> has unsupported critical extension: true
the code of that method in BC currently looks as follows:
public boolean hasUnsupportedCriticalExtension()
{
if (this.getVersion() == 3)
{
Extensions extensions = c.getTBSCertificate().getExtensions();
if (extensions != null)
{
Enumeration e = extensions.oids();
while (e.hasMoreElements())
{
ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier)e.nextElement();
if (oid.equals(Extension.keyUsage)
|| oid.equals(Extension.certificatePolicies)
|| oid.equals(Extension.policyMappings)
|| oid.equals(Extension.inhibitAnyPolicy)
|| oid.equals(Extension.cRLDistributionPoints)
|| oid.equals(Extension.issuingDistributionPoint)
|| oid.equals(Extension.deltaCRLIndicator)
|| oid.equals(Extension.policyConstraints)
|| oid.equals(Extension.basicConstraints)
|| oid.equals(Extension.subjectAlternativeName)
|| oid.equals(Extension.nameConstraints))
{
continue;
}
Extension ext = extensions.getExtension(oid);
if (ext.isCritical())
{
return true;
}
}
}
}
return false;
}
=> would it be safe to add one more extension OID to the combined ifstatement to also treat the Extension.extendedKeyUsage as being supported when marked critical?
this issue might be of interest, indicating a related discrepancy between the Java and C# versions of BC:
https://github.com/bcgit/bc-csharp/issues/395
The question I would like to clarify in this issue is, whether inclusion of the EKU as being supported in BC will have any unintended side effects
Yes. Any entity (including BC) that does not know how to process this EKU will simply ignore it - usually it's no big deal. If you marked it Critical - your authentication (or whatever else you're going to use that certificate) will fail, which can be a big deal.
should it be considered a BC bug that BC treats the certificate differently from the JDK?
=> code that runs correctly with the JDK provider will fail on the BC provider ... (although the certificate is perfectly valid as issued by a CA)