Skip to content

Commit 99947d3

Browse files
committed
JSSE: sort peer chain based off BasicConstraints for more accurate leaf cert detection in WolfSSLTrustX509 (X509ExtendedTrustManager)
1 parent 60c4d0b commit 99947d3

2 files changed

Lines changed: 146 additions & 1 deletion

File tree

src/java/com/wolfssl/provider/jsse/WolfSSLTrustX509.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private X509Certificate[] sortCertChainBySubjectIssuer(
9494
X509Certificate[] certs) throws CertificateException {
9595

9696
int i, curr, next;
97+
int leafIdx = -1;
9798
boolean nextFound = false;
9899
final X509Certificate[] chain;
99100
X509Certificate[] retChain = null;
@@ -120,7 +121,38 @@ private X509Certificate[] sortCertChainBySubjectIssuer(
120121
chain[tmpI].getSubjectX500Principal().getName());
121122
}
122123

123-
/* Assume peer/leaf cert is first in array */
124+
/* Find the leaf certificate using BasicConstraints extension.
125+
* Per RFC 5280, leaf/end-entity certs have getBasicConstraints()
126+
* return -1, while CA certs return >= 0. */
127+
for (i = 0; i < chain.length; i++) {
128+
if (chain[i].getBasicConstraints() == -1) {
129+
leafIdx = i;
130+
final int tmpLeaf = i;
131+
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
132+
() -> "Identified leaf cert at index " + tmpLeaf +
133+
" (BasicConstraints CA=false)");
134+
break;
135+
}
136+
}
137+
138+
/* If we couldn't identify leaf cert by BasicConstraints, default
139+
* to treat the first cert as peer */
140+
if (leafIdx == -1) {
141+
final int tmpLeaf = 0;
142+
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
143+
() -> "Could not identify leaf cert by BasicConstraints, " +
144+
"assuming index " + tmpLeaf);
145+
leafIdx = 0;
146+
}
147+
148+
/* Move leaf cert to position 0 if not already there */
149+
if (leafIdx != 0) {
150+
X509Certificate tmp = chain[0];
151+
chain[0] = chain[leafIdx];
152+
chain[leafIdx] = tmp;
153+
}
154+
155+
/* Now build chain from leaf to root */
124156
for (curr = 0; curr < chain.length; curr++) {
125157
nextFound = false;
126158
for (next = curr + 1; next < chain.length; next++) {

src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.security.cert.Certificate;
4646
import java.security.cert.CertificateFactory;
4747
import java.security.cert.CertificateException;
48+
import java.security.cert.CertificateParsingException;
4849
import java.security.cert.X509Certificate;
4950
import java.net.Socket;
5051
import java.net.InetSocketAddress;
@@ -1145,6 +1146,7 @@ public void testCheckServerTrustedWithChainWrongOrder()
11451146
pass("\t... passed");
11461147
}
11471148

1149+
11481150
@Test
11491151
public void testCheckServerTrustedWithChainReturnsChain()
11501152
throws NoSuchProviderException, NoSuchAlgorithmException,
@@ -2487,6 +2489,117 @@ public void testX509ExtendedTrustManagerExternal()
24872489
pass("\t... passed");
24882490
}
24892491

2492+
@Test
2493+
public void testExtendedKeyUsageWithLeafNotFirst() throws Exception {
2494+
2495+
System.out.print("\tEKU validation leaf not first");
2496+
2497+
/* This test reproduces the case where a certificate chain arrives
2498+
* with CA before leaf cert and helps verify the peer chain sorting
2499+
* logic. It exercises a common code path to validate the Extended
2500+
* Key Usage as a sanity check. */
2501+
2502+
String eccServerCert =
2503+
"examples/certs/intermediate/server-int-ecc-cert.pem";
2504+
String eccInt2CaCert =
2505+
"examples/certs/intermediate/ca-int2-ecc-cert.pem";
2506+
String eccIntCaCert =
2507+
"examples/certs/intermediate/ca-int-ecc-cert.pem";
2508+
2509+
FileInputStream fis;
2510+
BufferedInputStream bis;
2511+
X509Certificate[] certArray = new X509Certificate[3];
2512+
2513+
if (WolfSSLTestFactory.isAndroid()) {
2514+
eccServerCert = "/sdcard/" + eccServerCert;
2515+
eccInt2CaCert = "/sdcard/" + eccInt2CaCert;
2516+
eccIntCaCert = "/sdcard/" + eccIntCaCert;
2517+
}
2518+
2519+
/* Load certificates in REVERSE order (CA first, server last)
2520+
* to simulate the bug scenario */
2521+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
2522+
2523+
/* certArray[0]: Intermediate2 CA - has CA:TRUE, no serverAuth EKU */
2524+
fis = new FileInputStream(eccInt2CaCert);
2525+
bis = new java.io.BufferedInputStream(fis);
2526+
certArray[0] = (X509Certificate)cf.generateCertificate(bis);
2527+
bis.close();
2528+
fis.close();
2529+
2530+
/* certArray[1]: Intermediate CA - has CA:TRUE, no serverAuth EKU */
2531+
fis = new FileInputStream(eccIntCaCert);
2532+
bis = new java.io.BufferedInputStream(fis);
2533+
certArray[1] = (X509Certificate)cf.generateCertificate(bis);
2534+
bis.close();
2535+
fis.close();
2536+
2537+
/* certArray[2]: Server cert - has CA:FALSE, HAS serverAuth EKU */
2538+
fis = new FileInputStream(eccServerCert);
2539+
bis = new java.io.BufferedInputStream(fis);
2540+
certArray[2] = (X509Certificate)cf.generateCertificate(bis);
2541+
bis.close();
2542+
fis.close();
2543+
2544+
/* Create wolfSSL TrustManager for verification. */
2545+
TrustManager[] baseTm = tf.createTrustManager("SunX509",
2546+
tf.caJKS, provider);
2547+
WolfSSLTrustX509 wolfTm = (WolfSSLTrustX509) baseTm[0];
2548+
2549+
/* Call checkServerTrusted() with chain-returning overload.
2550+
* The sorting logic code should:
2551+
* 1. Identify the leaf using BasicConstraints (index 2)
2552+
* 2. Move server cert to index 0
2553+
* 3. Return chain with server cert first */
2554+
List<X509Certificate> sortedChain = null;
2555+
try {
2556+
sortedChain = wolfTm.checkServerTrusted(
2557+
certArray, "EC", (String)null);
2558+
2559+
} catch (CertificateException e) {
2560+
e.printStackTrace();
2561+
fail("checkServerTrusted() threw exception: " + e.getMessage());
2562+
}
2563+
2564+
/* Validate Extended Key Usage on the peer cert.
2565+
* The peer cert should be first in the sorted chain. */
2566+
if (sortedChain == null || sortedChain.size() == 0) {
2567+
fail("Sorted chain is null or empty");
2568+
}
2569+
2570+
X509Certificate peerCert = sortedChain.get(0);
2571+
List<String> ekuOids = null;
2572+
2573+
try {
2574+
ekuOids = peerCert.getExtendedKeyUsage();
2575+
2576+
} catch (java.security.cert.CertificateParsingException e) {
2577+
e.printStackTrace();
2578+
fail("Failed to parse Extended Key Usage: " + e.getMessage());
2579+
}
2580+
2581+
/* Check for serverAuth EKU (1.3.6.1.5.5.7.3.1) */
2582+
boolean hasServerAuth = false;
2583+
if (ekuOids != null) {
2584+
for (String oid : ekuOids) {
2585+
if (oid.equals("1.3.6.1.5.5.7.3.1")) {
2586+
hasServerAuth = true;
2587+
break;
2588+
}
2589+
}
2590+
}
2591+
2592+
if (!hasServerAuth) {
2593+
fail("EKU validation failed. The certificate chain was not " +
2594+
"properly sorted, and a CA cert (without serverAuth EKU) " +
2595+
"was treated as the peer cert instead of the server cert. " +
2596+
"Peer cert: " + peerCert.getSubjectX500Principal() +
2597+
", EKU list: " + ekuOids);
2598+
}
2599+
2600+
System.out.println("\t... passed");
2601+
}
2602+
24902603
/* TrustManager that trusts all certificates */
24912604
TrustManager[] trustAllCerts = {
24922605
new X509ExtendedTrustManager() {

0 commit comments

Comments
 (0)