From 31299d44139a13455f266b7b53dbfae392337e8e Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Wed, 21 Oct 2026 15:16:36 -0600 Subject: [PATCH] F-15740: check basicConstraints and keyUsage before loading chain certs as issuers in certManagerVerify --- .../provider/jsse/WolfSSLTrustX509.java | 24 +++++++ .../jsse/test/WolfSSLTestFactory.java | 23 +++++++ .../jsse/test/WolfSSLTrustX509Test.java | 64 +++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java index f20105c2..763a9f5d 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java @@ -519,6 +519,30 @@ private List certManagerVerify( } } + /* Only load chain certs that may sign certs, or KeyStore + * entries when peer certs are trusted. getBasicConstraints() + * returns -1 unless the cert has basicConstraints cA=true. + * getKeyUsage() is null when the extension is absent, and + * index 5 is the keyCertSign bit */ + boolean[] keyUsage = sortedCerts[tmpI].getKeyUsage(); + boolean mayIssue = + (sortedCerts[tmpI].getBasicConstraints() >= 0) && + (keyUsage == null || (keyUsage.length > 5 && keyUsage[5])); + + if (!mayIssue && WolfSSL.trustPeerCertEnabled()) { + try { + String alias = + this.store.getCertificateAlias(sortedCerts[tmpI]); + mayIssue = (alias != null); + } catch (KeyStoreException e) { + /* not a trusted entry */ + } + } + if (!mayIssue) { + throw new CertificateException("Intermediate chain cert " + + "is not a CA, not allowed to sign certificates"); + } + /* Load chain cert as trusted CA */ ret = cm.CertManagerLoadCABuffer(encoded, encoded.length, WolfSSL.SSL_FILETYPE_ASN1); diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java index 0d6ff131..e1ee0ba1 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java @@ -1029,6 +1029,26 @@ protected KeyStore.PrivateKeyEntry generateCert(String commonName, throws CertificateException, WolfSSLException, NoSuchAlgorithmException, IOException, WolfSSLJNIException { + return generateCert(commonName, isCA, issuer, null); + } + + /** + * Generate a new RSA 2048-bit certificate with basicConstraints and an + * optional keyUsage extension set. + * + * @param commonName subject common name + * @param isCA true to set CA:TRUE, false to set CA:FALSE + * @param issuer entry whose key signs the new cert, or null to self sign + * @param keyUsage keyUsage extension value (ex: "keyCertSign,cRLSign"), + * or null to leave the extension out + * + * @return new private key and certificate + */ + protected KeyStore.PrivateKeyEntry generateCert(String commonName, + boolean isCA, KeyStore.PrivateKeyEntry issuer, String keyUsage) + throws CertificateException, WolfSSLException, + NoSuchAlgorithmException, IOException, WolfSSLJNIException { + WolfSSLCertificate x509 = null; WolfSSLCertificate issuerCert = null; WolfSSLX509Name subjectName = null; @@ -1059,6 +1079,9 @@ protected KeyStore.PrivateKeyEntry generateCert(String commonName, } x509.setPublicKey(keyPair.getPublic()); x509.addExtension(WolfSSL.NID_basic_constraints, isCA, true); + if (keyUsage != null) { + x509.addExtension(WolfSSL.NID_key_usage, keyUsage, true); + } x509.signCert((issuer != null) ? issuer.getPrivateKey() : keyPair.getPrivate(), "SHA256"); diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java index 847fc896..390417a0 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java @@ -4297,6 +4297,70 @@ public void testNonCaKeyStoreEntryNotUsedAsIssuer() throws Exception { } } + @Test + public void testChainCertMustBeCaToIssue() throws Exception { + + KeyStore.PrivateKeyEntry ca = tf.generateCert("Test CA", true, null); + KeyStore ks = KeyStore.getInstance(tf.keyStoreType); + ks.load(null, null); + ks.setCertificateEntry("ca", ca.getCertificate()); + X509TrustManager tm = (X509TrustManager)tf.createTrustManager( + "SunX509", ks, provider)[0]; + + String[] goodKeyUsage = { null, "keyCertSign,cRLSign" }; + for (String ku : goodKeyUsage) { + KeyStore.PrivateKeyEntry inter = + tf.generateCert("Good Inter", true, ca, ku); + X509Certificate leaf = (X509Certificate)tf.generateCert( + "good.example.com", false, inter).getCertificate(); + tm.checkServerTrusted(new X509Certificate[] { + leaf, (X509Certificate)inter.getCertificate() }, "RSA"); + } + + KeyStore.PrivateKeyEntry[] badInters = { + tf.generateCert("Non-CA Inter", false, ca), + tf.generateCert("No CertSign Inter", true, ca, "digitalSignature") + }; + for (KeyStore.PrivateKeyEntry inter : badInters) { + X509Certificate[] chain = new X509Certificate[] { + (X509Certificate)tf.generateCert( + "bad.example.com", false, inter).getCertificate(), + (X509Certificate)inter.getCertificate() }; + try { + tm.checkServerTrusted(chain, "RSA"); + fail("Server chain through non-issuing cert was trusted"); + } catch (CertificateException e) { + /* expected */ + } + try { + tm.checkClientTrusted(chain, "RSA"); + fail("Client chain through non-issuing cert was trusted"); + } catch (CertificateException e) { + /* expected */ + } + } + } + + /* With trusted peer certs, a self-signed non-CA KeyStore entry is + * trusted even when the peer also sends it as a chain cert */ + @Test + public void testTrustedNonCaPeerCertSentTwice() throws Exception { + + Assume.assumeTrue(WolfSSL.trustPeerCertEnabled()); + + X509Certificate selfSigned = (X509Certificate)tf.generateCert( + "localhost", false, null).getCertificate(); + KeyStore ks = KeyStore.getInstance(tf.keyStoreType); + ks.load(null, null); + ks.setCertificateEntry("peer", selfSigned); + X509TrustManager tm = (X509TrustManager)tf.createTrustManager( + "SunX509", ks, provider)[0]; + + tm.checkServerTrusted(new X509Certificate[] { selfSigned }, "RSA"); + tm.checkServerTrusted( + new X509Certificate[] { selfSigned, selfSigned }, "RSA"); + } + /** * Test that when checkServerTrusted() returns the cert chain, it excludes * CA certificates that were skipped during verification due to alternate