F-4309: anchor peer/leaf cert at chain index 0 in WolfSSLTrustX509

pull/380/head
Chris Conlon 2026-07-16 14:26:18 -06:00
parent a663fa393b
commit 48c54ff5db
2 changed files with 40 additions and 89 deletions

View File

@ -95,7 +95,6 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
X509Certificate[] certs) throws CertificateException {
int i, curr, next;
int leafIdx = -1;
boolean nextFound = false;
final X509Certificate[] chain;
X509Certificate[] retChain = null;
@ -122,38 +121,10 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
chain[tmpI].getSubjectX500Principal().getName());
}
/* Find the leaf certificate using BasicConstraints extension.
* Per RFC 5280, leaf/end-entity certs have getBasicConstraints()
* return -1, while CA certs return >= 0. */
for (i = 0; i < chain.length; i++) {
if (chain[i].getBasicConstraints() == -1) {
leafIdx = i;
final int tmpLeaf = i;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "Identified leaf cert at index " + tmpLeaf +
" (BasicConstraints CA=false)");
break;
}
}
/* If we couldn't identify leaf cert by BasicConstraints, default
* to treat the first cert as peer */
if (leafIdx == -1) {
final int tmpLeaf = 0;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "Could not identify leaf cert by BasicConstraints, " +
"assuming index " + tmpLeaf);
leafIdx = 0;
}
/* Move leaf cert to position 0 if not already there */
if (leafIdx != 0) {
X509Certificate tmp = chain[0];
chain[0] = chain[leafIdx];
chain[leafIdx] = tmp;
}
/* Now build chain from leaf to root */
/* certs[0] is the peer cert and stays fixed at index 0. Native
* wolfSSL (WolfSSLX509StoreCtx.getCerts()) provides it first and the
* X509TrustManager contract expects that ordering. Sort only the
* intermediates after it, peer to root. */
for (curr = 0; curr < chain.length; curr++) {
nextFound = false;
for (next = curr + 1; next < chain.length; next++) {

View File

@ -2521,13 +2521,20 @@ public class WolfSSLTrustX509Test {
}
/**
* Verify checkServerTrusted() keeps whatever cert is at certs[0] as the
* peer/leaf and does not re-select a different leaf from deeper in the
* chain by BasicConstraints. In a real handshake certs[0] is the peer
* end-entity cert, delivered first by native wolfSSL and required first
* by the X509TrustManager contract, so the verified chain starts with it.
*
* To confirm no re-selection happens, this test intentionally places a
* CA cert at certArray[0] and a different end-entity cert (server-int-ecc)
* deeper in the chain. The verified chain must start with certArray[0] and
* must not promote the deeper end-entity cert to index 0.
*/
@Test
public void testExtendedKeyUsageWithLeafNotFirst() throws Exception {
/* This test reproduces the case where a certificate chain arrives
* with CA before leaf cert and helps verify the peer chain sorting
* logic. It exercises a common code path to validate the Extended
* Key Usage as a sanity check. */
public void testCheckServerTrustedDoesNotReorderLeaf() throws Exception {
String eccServerCert =
"examples/certs/intermediate/server-int-ecc-cert.pem";
@ -2546,27 +2553,25 @@ public class WolfSSLTrustX509Test {
eccIntCaCert = "/data/local/tmp/" + eccIntCaCert;
}
/* Load certificates in REVERSE order (CA first, server last)
* to simulate the bug scenario */
CertificateFactory cf = CertificateFactory.getInstance("X.509");
/* certArray[0]: Intermediate2 CA - has CA:TRUE, no serverAuth EKU */
fis = new FileInputStream(eccInt2CaCert);
bis = new java.io.BufferedInputStream(fis);
/* certArray[0]: intermediate CA, a CA-flagged cert at index 0 */
fis = new FileInputStream(eccIntCaCert);
bis = new BufferedInputStream(fis);
certArray[0] = (X509Certificate)cf.generateCertificate(bis);
bis.close();
fis.close();
/* certArray[1]: Intermediate CA - has CA:TRUE, no serverAuth EKU */
fis = new FileInputStream(eccIntCaCert);
bis = new java.io.BufferedInputStream(fis);
/* certArray[1]: a different trusted end-entity leaf, placed deeper */
fis = new FileInputStream(eccServerCert);
bis = new BufferedInputStream(fis);
certArray[1] = (X509Certificate)cf.generateCertificate(bis);
bis.close();
fis.close();
/* certArray[2]: Server cert - has CA:FALSE, HAS serverAuth EKU */
fis = new FileInputStream(eccServerCert);
bis = new java.io.BufferedInputStream(fis);
/* certArray[2]: intermediate2 CA */
fis = new FileInputStream(eccInt2CaCert);
bis = new BufferedInputStream(fis);
certArray[2] = (X509Certificate)cf.generateCertificate(bis);
bis.close();
fis.close();
@ -2576,56 +2581,31 @@ public class WolfSSLTrustX509Test {
tf.caJKS, provider);
WolfSSLTrustX509 wolfTm = (WolfSSLTrustX509) baseTm[0];
/* Call checkServerTrusted() with chain-returning overload.
* The sorting logic code should:
* 1. Identify the leaf using BasicConstraints (index 2)
* 2. Move server cert to index 0
* 3. Return chain with server cert first */
List<X509Certificate> sortedChain = null;
/* Chain-returning overload lets us inspect the validated leaf */
List<X509Certificate> verifiedChain = null;
try {
sortedChain = wolfTm.checkServerTrusted(
certArray, "EC", (String)null);
verifiedChain = wolfTm.checkServerTrusted(certArray, "EC",
(String)null);
} catch (CertificateException e) {
e.printStackTrace();
fail("checkServerTrusted() threw exception: " + e.getMessage());
}
/* Validate Extended Key Usage on the peer cert.
* The peer cert should be first in the sorted chain. */
if (sortedChain == null || sortedChain.size() == 0) {
fail("Sorted chain is null or empty");
if (verifiedChain == null || verifiedChain.size() == 0) {
fail("Verified chain is null or empty");
}
X509Certificate peerCert = sortedChain.get(0);
List<String> ekuOids = null;
/* Cert at the peer position must be certArray[0] */
X509Certificate verifiedPeer = verifiedChain.get(0);
try {
ekuOids = peerCert.getExtendedKeyUsage();
assertEquals("checkServerTrusted() must keep certArray[0] at the " +
"peer position and not re-select a different leaf from the chain",
certArray[0], verifiedPeer);
} catch (java.security.cert.CertificateParsingException e) {
e.printStackTrace();
fail("Failed to parse Extended Key Usage: " + e.getMessage());
}
/* Check for serverAuth EKU (1.3.6.1.5.5.7.3.1) */
boolean hasServerAuth = false;
if (ekuOids != null) {
for (String oid : ekuOids) {
if (oid.equals("1.3.6.1.5.5.7.3.1")) {
hasServerAuth = true;
break;
}
}
}
if (!hasServerAuth) {
fail("EKU validation failed. The certificate chain was not " +
"properly sorted, and a CA cert (without serverAuth EKU) " +
"was treated as the peer cert instead of the server cert. " +
"Peer cert: " + peerCert.getSubjectX500Principal() +
", EKU list: " + ekuOids);
}
assertNotEquals("checkServerTrusted() must not promote a different " +
"end-entity cert from deeper in the chain to the peer position",
certArray[1], verifiedPeer);
}
/**