Skip to content

Commit c44f1c8

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 99947d3 commit c44f1c8

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;
@@ -4029,5 +4039,246 @@ public void testCloseWithNullEngineHelper()
40294039

40304040
System.out.println("\t... passed");
40314041
}
4042+
4043+
/**
4044+
* This test verifies that WolfSSLX509StoreCtx.getCerts() returns
4045+
* certificates in the correct peer to root order during a TLS
4046+
* handshake. Register a custom X509TrustManager (not WolfSSLTrustX509)
4047+
* so that the internal sorting logic in WolfSSLTrustX509 doesn't run.
4048+
* This exposes the raw certificate order coming from the JNI layer.
4049+
*/
4050+
@Test
4051+
public void testCertificateChainOrderingFromStoreCtx()
4052+
throws Exception {
4053+
4054+
System.out.print("\tProper chain ordering");
4055+
4056+
/* Custom TrustManager that checks certificate order */
4057+
final boolean[] wasCalled = {false};
4058+
final boolean[] orderCorrect = {false};
4059+
final String[] errorMsg = {null};
4060+
final int[] chainLen = {0};
4061+
4062+
String serverIntCert =
4063+
"examples/certs/intermediate/server-int-ecc-cert.pem";
4064+
String serverIntKey = "examples/certs/ecc-key.pem";
4065+
String intCaCert = "examples/certs/intermediate/ca-int-ecc-cert.pem";
4066+
String int2CaCert =
4067+
"examples/certs/intermediate/ca-int2-ecc-cert.pem";
4068+
4069+
X509TrustManager customTM = new X509TrustManager() {
4070+
@Override
4071+
public void checkClientTrusted(X509Certificate[] chain,
4072+
String authType) throws CertificateException {
4073+
/* Not used in this test */
4074+
}
4075+
4076+
@Override
4077+
public void checkServerTrusted(X509Certificate[] chain,
4078+
String authType) throws CertificateException {
4079+
wasCalled[0] = true;
4080+
4081+
if (chain == null || chain.length == 0) {
4082+
errorMsg[0] = "Certificate chain is null or empty";
4083+
throw new CertificateException(errorMsg[0]);
4084+
}
4085+
4086+
chainLen[0] = chain.length;
4087+
4088+
/* Per RFC 5280, leaf/end-entity certs have
4089+
* getBasicConstraints() return -1, while CA certs return
4090+
* >= 0. The first certificate in the chain MUST be the
4091+
* peer/leaf certificate. */
4092+
int firstCertBC = chain[0].getBasicConstraints();
4093+
4094+
if (firstCertBC == -1) {
4095+
/* First cert is leaf/peer cert, order is correct */
4096+
orderCorrect[0] = true;
4097+
4098+
} else {
4099+
/* First cert is a CA cert, order is WRONG */
4100+
orderCorrect[0] = false;
4101+
errorMsg[0] = "Certificate chain order is incorrect: " +
4102+
"first cert is CA (BasicConstraints=" + firstCertBC +
4103+
"), expected leaf/peer cert (BasicConstraints=-1). " +
4104+
"Chain length: " + chain.length + ". " +
4105+
"First cert subject: " +
4106+
chain[0].getSubjectX500Principal().getName();
4107+
/* NOTE: We don't throw here so test can verify the flag,
4108+
* but in production a wrapper TrustManager would fail */
4109+
}
4110+
}
4111+
4112+
@Override
4113+
public X509Certificate[] getAcceptedIssuers() {
4114+
return new X509Certificate[0];
4115+
}
4116+
};
4117+
4118+
/* Build server KeyStore with intermediate chain from pem files */
4119+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
4120+
4121+
FileInputStream fis = new FileInputStream(serverIntCert);
4122+
BufferedInputStream bis = new BufferedInputStream(fis);
4123+
X509Certificate serverCert =
4124+
(X509Certificate)cf.generateCertificate(bis);
4125+
bis.close();
4126+
fis.close();
4127+
4128+
fis = new FileInputStream(intCaCert);
4129+
bis = new BufferedInputStream(fis);
4130+
X509Certificate intCert = (X509Certificate)cf.generateCertificate(bis);
4131+
bis.close();
4132+
fis.close();
4133+
4134+
fis = new FileInputStream(int2CaCert);
4135+
bis = new BufferedInputStream(fis);
4136+
X509Certificate int2Cert = (X509Certificate)cf.generateCertificate(bis);
4137+
bis.close();
4138+
fis.close();
4139+
4140+
/* Create KeyStore and add server cert with chain */
4141+
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
4142+
serverKeyStore.load(null, null);
4143+
4144+
/* Build certificate chain: server, int2 (immediate issuer), int */
4145+
Certificate[] certChain = new Certificate[3];
4146+
certChain[0] = serverCert;
4147+
certChain[1] = int2Cert;
4148+
certChain[2] = intCert;
4149+
4150+
/* Load existing ECC private key from server-ecc.jks, since Java
4151+
* doesn't natively support SEC1 ECC format without Bouncy Castle */
4152+
KeyStore tmpKS = KeyStore.getInstance("JKS");
4153+
fis = new FileInputStream("examples/provider/server-ecc.jks");
4154+
tmpKS.load(fis, "wolfSSL test".toCharArray());
4155+
fis.close();
4156+
4157+
java.security.PrivateKey privateKey =
4158+
(java.security.PrivateKey)tmpKS.getKey("server-ecc",
4159+
"wolfSSL test".toCharArray());
4160+
4161+
/* Add private key with intermediate certificate chain to keystore */
4162+
serverKeyStore.setKeyEntry("server-int-ecc", privateKey,
4163+
"wolfSSL test".toCharArray(), certChain);
4164+
4165+
/* Set up server with intermediate certificate chain */
4166+
ExecutorService executor = Executors.newSingleThreadExecutor();
4167+
SSLServerSocket serverSocket = null;
4168+
4169+
try {
4170+
KeyManagerFactory serverKM =
4171+
KeyManagerFactory.getInstance("SunX509");
4172+
serverKM.init(serverKeyStore, "wolfSSL test".toCharArray());
4173+
4174+
/* Server uses default wolfSSL TrustManager */
4175+
TrustManagerFactory serverTM =
4176+
TrustManagerFactory.getInstance("SunX509");
4177+
serverTM.init(serverKeyStore);
4178+
4179+
SSLContext serverCtx =
4180+
SSLContext.getInstance("TLSv1.2", "wolfJSSE");
4181+
serverCtx.init(serverKM.getKeyManagers(),
4182+
serverTM.getTrustManagers(), null);
4183+
4184+
serverSocket =
4185+
(SSLServerSocket)serverCtx.getServerSocketFactory()
4186+
.createServerSocket(0);
4187+
final int serverPort = serverSocket.getLocalPort();
4188+
final SSLServerSocket finalServerSocket = serverSocket;
4189+
4190+
/* Start server thread */
4191+
Future<Void> serverFuture =
4192+
executor.submit(new Callable<Void>() {
4193+
@Override
4194+
public Void call() throws Exception {
4195+
SSLSocket serverSock = null;
4196+
try {
4197+
serverSock =
4198+
(SSLSocket)finalServerSocket.accept();
4199+
InputStream in = serverSock.getInputStream();
4200+
OutputStream out = serverSock.getOutputStream();
4201+
4202+
/* Simple echo */
4203+
byte[] buf = new byte[1024];
4204+
int read = in.read(buf);
4205+
if (read > 0) {
4206+
out.write(buf, 0, read);
4207+
}
4208+
} finally {
4209+
if (serverSock != null) {
4210+
serverSock.close();
4211+
}
4212+
}
4213+
return null;
4214+
}
4215+
});
4216+
4217+
/* Set up client with CUSTOM TrustManager (not WolfSSLTrustX509) */
4218+
KeyStore clientKeyStore = KeyStore.getInstance(tf.keyStoreType);
4219+
InputStream stream = new FileInputStream(tf.clientJKS);
4220+
clientKeyStore.load(stream, jksPass);
4221+
stream.close();
4222+
4223+
KeyManagerFactory clientKM =
4224+
KeyManagerFactory.getInstance("SunX509");
4225+
clientKM.init(clientKeyStore, jksPass);
4226+
4227+
/* Client uses our CUSTOM TrustManager */
4228+
SSLContext clientCtx =
4229+
SSLContext.getInstance("TLSv1.2", "wolfJSSE");
4230+
clientCtx.init(clientKM.getKeyManagers(),
4231+
new TrustManager[] { customTM }, null);
4232+
4233+
/* Connect client */
4234+
SSLSocket clientSocket = null;
4235+
try {
4236+
clientSocket = (SSLSocket)clientCtx.getSocketFactory()
4237+
.createSocket("localhost", serverPort);
4238+
4239+
/* Force handshake - this will call our custom TrustManager */
4240+
clientSocket.startHandshake();
4241+
4242+
/* Send test data */
4243+
OutputStream out = clientSocket.getOutputStream();
4244+
out.write("test".getBytes());
4245+
out.flush();
4246+
4247+
/* Read response */
4248+
InputStream in = clientSocket.getInputStream();
4249+
byte[] buf = new byte[1024];
4250+
in.read(buf);
4251+
4252+
} finally {
4253+
if (clientSocket != null) {
4254+
clientSocket.close();
4255+
}
4256+
}
4257+
4258+
/* Wait for server to finish */
4259+
serverFuture.get(10, TimeUnit.SECONDS);
4260+
4261+
} finally {
4262+
if (serverSocket != null) {
4263+
serverSocket.close();
4264+
}
4265+
executor.shutdown();
4266+
}
4267+
4268+
/* Verify TrustManager was called */
4269+
assertTrue("Custom TrustManager.checkServerTrusted() was not called",
4270+
wasCalled[0]);
4271+
4272+
/* Verify we got a chain with multiple certs */
4273+
assertTrue("Expected chain length > 1, got: " + chainLen[0],
4274+
chainLen[0] > 1);
4275+
4276+
assertTrue("Certificate chain order is incorrect: " +
4277+
(errorMsg[0] != null ? errorMsg[0] :
4278+
"first cert was not peer/leaf cert"),
4279+
orderCorrect[0]);
4280+
4281+
System.out.println("\t... passed");
4282+
}
40324283
}
40334284

0 commit comments

Comments
 (0)