|
58 | 58 | import javax.net.ssl.SSLParameters; |
59 | 59 | import javax.net.ssl.KeyManagerFactory; |
60 | 60 | 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; |
61 | 65 | import javax.net.ssl.SSLException; |
62 | 66 | import javax.net.ssl.SSLHandshakeException; |
63 | 67 | import javax.net.ssl.HandshakeCompletedListener; |
|
81 | 85 | import java.security.cert.Certificate; |
82 | 86 | import java.security.cert.X509Certificate; |
83 | 87 | 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; |
84 | 92 | import java.lang.reflect.Field; |
| 93 | +import java.io.BufferedInputStream; |
| 94 | +import java.util.Base64; |
85 | 95 |
|
86 | 96 | import com.wolfssl.provider.jsse.WolfSSLProvider; |
87 | 97 | import com.wolfssl.provider.jsse.WolfSSLSocketFactory; |
@@ -4029,5 +4039,246 @@ public void testCloseWithNullEngineHelper() |
4029 | 4039 |
|
4030 | 4040 | System.out.println("\t... passed"); |
4031 | 4041 | } |
| 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 | + } |
4032 | 4283 | } |
4033 | 4284 |
|
0 commit comments