|
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; |
@@ -3936,5 +3946,246 @@ public void testCloseWithNullEngineHelper() |
3936 | 3946 |
|
3937 | 3947 | System.out.println("\t... passed"); |
3938 | 3948 | } |
| 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 | + } |
3939 | 4190 | } |
3940 | 4191 |
|
0 commit comments