Skip to content

Commit bb70b9d

Browse files
committed
JNI/JSSE: fix cert ordering reversal in WolfSSLX509StoreCtx.getCerts(). Native wolfSSL_X509_STORE_GetCerts() direction changed between wolfSSL 5.7.0 and 5.8.0.
1 parent 195fb2a commit bb70b9d

2 files changed

Lines changed: 257 additions & 0 deletions

File tree

native/com_wolfssl_WolfSSLX509StoreCtx.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,17 @@ JNIEXPORT jobjectArray JNICALL Java_com_wolfssl_WolfSSLX509StoreCtx_X509_1STORE_
9393
}
9494
XMEMCPY(buf, der, derSz);
9595
(*jenv)->ReleaseByteArrayElements(jenv, derArr, buf, 0);
96+
97+
#if LIBWOLFSSL_VERSION_HEX >= 0x05008000
9698
/* Reverse order, so peer cert is first in returned array,
9799
* followed by intermediates, lastly by root. Native
98100
* wolfSSL_X509_STORE_GetCerts() returns certs in order of
99101
* root to peer, but Java/JSSE expects peer to root */
100102
(*jenv)->SetObjectArrayElement(jenv, certArr, skNum-1-i, derArr);
103+
#else
104+
/* wolfSSL < 5.8.0 returns certs peer->root, matches Java needs */
105+
(*jenv)->SetObjectArrayElement(jenv, certArr, i, derArr);
106+
#endif
101107
(*jenv)->DeleteLocalRef(jenv, derArr);
102108
}
103109
}

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

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
import javax.net.ssl.SSLParameters;
5959
import javax.net.ssl.KeyManagerFactory;
6060
import javax.net.ssl.TrustManagerFactory;
61+
import javax.net.ssl.TrustManager;
62+
import javax.net.ssl.X509ExtendedTrustManager;
63+
import javax.net.ssl.X509TrustManager;
64+
import javax.net.ssl.SSLEngine;
6165
import javax.net.ssl.SSLException;
6266
import javax.net.ssl.SSLHandshakeException;
6367
import javax.net.ssl.HandshakeCompletedListener;
@@ -81,7 +85,13 @@
8185
import java.security.cert.Certificate;
8286
import java.security.cert.X509Certificate;
8387
import java.security.cert.CertificateException;
88+
import java.security.cert.CertificateFactory;
89+
import java.security.PrivateKey;
90+
import java.security.KeyFactory;
91+
import java.security.spec.PKCS8EncodedKeySpec;
8492
import java.lang.reflect.Field;
93+
import java.io.BufferedInputStream;
94+
import java.util.Base64;
8595

8696
import com.wolfssl.provider.jsse.WolfSSLProvider;
8797
import com.wolfssl.provider.jsse.WolfSSLSocketFactory;
@@ -3936,5 +3946,246 @@ public void testCloseWithNullEngineHelper()
39363946

39373947
System.out.println("\t... passed");
39383948
}
3949+
3950+
/**
3951+
* This test verifies that WolfSSLX509StoreCtx.getCerts() returns
3952+
* certificates in the correct peer to root order during a TLS
3953+
* handshake. Register a custom X509TrustManager (not WolfSSLTrustX509)
3954+
* so that the internal sorting logic in WolfSSLTrustX509 doesn't run.
3955+
* This exposes the raw certificate order coming from the JNI layer.
3956+
*/
3957+
@Test
3958+
public void testCertificateChainOrderingFromStoreCtx()
3959+
throws Exception {
3960+
3961+
System.out.print("\tProper chain ordering");
3962+
3963+
/* Custom TrustManager that checks certificate order */
3964+
final boolean[] wasCalled = {false};
3965+
final boolean[] orderCorrect = {false};
3966+
final String[] errorMsg = {null};
3967+
final int[] chainLen = {0};
3968+
3969+
String serverIntCert =
3970+
"examples/certs/intermediate/server-int-ecc-cert.pem";
3971+
String serverIntKey = "examples/certs/ecc-key.pem";
3972+
String intCaCert = "examples/certs/intermediate/ca-int-ecc-cert.pem";
3973+
String int2CaCert =
3974+
"examples/certs/intermediate/ca-int2-ecc-cert.pem";
3975+
3976+
X509TrustManager customTM = new X509TrustManager() {
3977+
@Override
3978+
public void checkClientTrusted(X509Certificate[] chain,
3979+
String authType) throws CertificateException {
3980+
/* Not used in this test */
3981+
}
3982+
3983+
@Override
3984+
public void checkServerTrusted(X509Certificate[] chain,
3985+
String authType) throws CertificateException {
3986+
wasCalled[0] = true;
3987+
3988+
if (chain == null || chain.length == 0) {
3989+
errorMsg[0] = "Certificate chain is null or empty";
3990+
throw new CertificateException(errorMsg[0]);
3991+
}
3992+
3993+
chainLen[0] = chain.length;
3994+
3995+
/* Per RFC 5280, leaf/end-entity certs have
3996+
* getBasicConstraints() return -1, while CA certs return
3997+
* >= 0. The first certificate in the chain MUST be the
3998+
* peer/leaf certificate. */
3999+
int firstCertBC = chain[0].getBasicConstraints();
4000+
4001+
if (firstCertBC == -1) {
4002+
/* First cert is leaf/peer cert, order is correct */
4003+
orderCorrect[0] = true;
4004+
4005+
} else {
4006+
/* First cert is a CA cert, order is WRONG */
4007+
orderCorrect[0] = false;
4008+
errorMsg[0] = "Certificate chain order is incorrect: " +
4009+
"first cert is CA (BasicConstraints=" + firstCertBC +
4010+
"), expected leaf/peer cert (BasicConstraints=-1). " +
4011+
"Chain length: " + chain.length + ". " +
4012+
"First cert subject: " +
4013+
chain[0].getSubjectX500Principal().getName();
4014+
/* NOTE: We don't throw here so test can verify the flag,
4015+
* but in production a wrapper TrustManager would fail */
4016+
}
4017+
}
4018+
4019+
@Override
4020+
public X509Certificate[] getAcceptedIssuers() {
4021+
return new X509Certificate[0];
4022+
}
4023+
};
4024+
4025+
/* Build server KeyStore with intermediate chain from pem files */
4026+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
4027+
4028+
FileInputStream fis = new FileInputStream(serverIntCert);
4029+
BufferedInputStream bis = new BufferedInputStream(fis);
4030+
X509Certificate serverCert =
4031+
(X509Certificate)cf.generateCertificate(bis);
4032+
bis.close();
4033+
fis.close();
4034+
4035+
fis = new FileInputStream(intCaCert);
4036+
bis = new BufferedInputStream(fis);
4037+
X509Certificate intCert = (X509Certificate)cf.generateCertificate(bis);
4038+
bis.close();
4039+
fis.close();
4040+
4041+
fis = new FileInputStream(int2CaCert);
4042+
bis = new BufferedInputStream(fis);
4043+
X509Certificate int2Cert = (X509Certificate)cf.generateCertificate(bis);
4044+
bis.close();
4045+
fis.close();
4046+
4047+
/* Create KeyStore and add server cert with chain */
4048+
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
4049+
serverKeyStore.load(null, null);
4050+
4051+
/* Build certificate chain: server, int2 (immediate issuer), int */
4052+
Certificate[] certChain = new Certificate[3];
4053+
certChain[0] = serverCert;
4054+
certChain[1] = int2Cert;
4055+
certChain[2] = intCert;
4056+
4057+
/* Load existing ECC private key from server-ecc.jks, since Java
4058+
* doesn't natively support SEC1 ECC format without Bouncy Castle */
4059+
KeyStore tmpKS = KeyStore.getInstance("JKS");
4060+
fis = new FileInputStream("examples/provider/server-ecc.jks");
4061+
tmpKS.load(fis, "wolfSSL test".toCharArray());
4062+
fis.close();
4063+
4064+
java.security.PrivateKey privateKey =
4065+
(java.security.PrivateKey)tmpKS.getKey("server-ecc",
4066+
"wolfSSL test".toCharArray());
4067+
4068+
/* Add private key with intermediate certificate chain to keystore */
4069+
serverKeyStore.setKeyEntry("server-int-ecc", privateKey,
4070+
"wolfSSL test".toCharArray(), certChain);
4071+
4072+
/* Set up server with intermediate certificate chain */
4073+
ExecutorService executor = Executors.newSingleThreadExecutor();
4074+
SSLServerSocket serverSocket = null;
4075+
4076+
try {
4077+
KeyManagerFactory serverKM =
4078+
KeyManagerFactory.getInstance("SunX509");
4079+
serverKM.init(serverKeyStore, "wolfSSL test".toCharArray());
4080+
4081+
/* Server uses default wolfSSL TrustManager */
4082+
TrustManagerFactory serverTM =
4083+
TrustManagerFactory.getInstance("SunX509");
4084+
serverTM.init(serverKeyStore);
4085+
4086+
SSLContext serverCtx =
4087+
SSLContext.getInstance("TLSv1.2", "wolfJSSE");
4088+
serverCtx.init(serverKM.getKeyManagers(),
4089+
serverTM.getTrustManagers(), null);
4090+
4091+
serverSocket =
4092+
(SSLServerSocket)serverCtx.getServerSocketFactory()
4093+
.createServerSocket(0);
4094+
final int serverPort = serverSocket.getLocalPort();
4095+
final SSLServerSocket finalServerSocket = serverSocket;
4096+
4097+
/* Start server thread */
4098+
Future<Void> serverFuture =
4099+
executor.submit(new Callable<Void>() {
4100+
@Override
4101+
public Void call() throws Exception {
4102+
SSLSocket serverSock = null;
4103+
try {
4104+
serverSock =
4105+
(SSLSocket)finalServerSocket.accept();
4106+
InputStream in = serverSock.getInputStream();
4107+
OutputStream out = serverSock.getOutputStream();
4108+
4109+
/* Simple echo */
4110+
byte[] buf = new byte[1024];
4111+
int read = in.read(buf);
4112+
if (read > 0) {
4113+
out.write(buf, 0, read);
4114+
}
4115+
} finally {
4116+
if (serverSock != null) {
4117+
serverSock.close();
4118+
}
4119+
}
4120+
return null;
4121+
}
4122+
});
4123+
4124+
/* Set up client with CUSTOM TrustManager (not WolfSSLTrustX509) */
4125+
KeyStore clientKeyStore = KeyStore.getInstance(tf.keyStoreType);
4126+
InputStream stream = new FileInputStream(tf.clientJKS);
4127+
clientKeyStore.load(stream, jksPass);
4128+
stream.close();
4129+
4130+
KeyManagerFactory clientKM =
4131+
KeyManagerFactory.getInstance("SunX509");
4132+
clientKM.init(clientKeyStore, jksPass);
4133+
4134+
/* Client uses our CUSTOM TrustManager */
4135+
SSLContext clientCtx =
4136+
SSLContext.getInstance("TLSv1.2", "wolfJSSE");
4137+
clientCtx.init(clientKM.getKeyManagers(),
4138+
new TrustManager[] { customTM }, null);
4139+
4140+
/* Connect client */
4141+
SSLSocket clientSocket = null;
4142+
try {
4143+
clientSocket = (SSLSocket)clientCtx.getSocketFactory()
4144+
.createSocket("localhost", serverPort);
4145+
4146+
/* Force handshake - this will call our custom TrustManager */
4147+
clientSocket.startHandshake();
4148+
4149+
/* Send test data */
4150+
OutputStream out = clientSocket.getOutputStream();
4151+
out.write("test".getBytes());
4152+
out.flush();
4153+
4154+
/* Read response */
4155+
InputStream in = clientSocket.getInputStream();
4156+
byte[] buf = new byte[1024];
4157+
in.read(buf);
4158+
4159+
} finally {
4160+
if (clientSocket != null) {
4161+
clientSocket.close();
4162+
}
4163+
}
4164+
4165+
/* Wait for server to finish */
4166+
serverFuture.get(10, TimeUnit.SECONDS);
4167+
4168+
} finally {
4169+
if (serverSocket != null) {
4170+
serverSocket.close();
4171+
}
4172+
executor.shutdown();
4173+
}
4174+
4175+
/* Verify TrustManager was called */
4176+
assertTrue("Custom TrustManager.checkServerTrusted() was not called",
4177+
wasCalled[0]);
4178+
4179+
/* Verify we got a chain with multiple certs */
4180+
assertTrue("Expected chain length > 1, got: " + chainLen[0],
4181+
chainLen[0] > 1);
4182+
4183+
assertTrue("Certificate chain order is incorrect: " +
4184+
(errorMsg[0] != null ? errorMsg[0] :
4185+
"first cert was not peer/leaf cert"),
4186+
orderCorrect[0]);
4187+
4188+
System.out.println("\t... passed");
4189+
}
39394190
}
39404191

0 commit comments

Comments
 (0)