1 /* |
|
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * questions. |
|
22 */ |
|
23 |
|
24 package sun.security.ssl; |
|
25 |
|
26 /* |
|
27 * @test |
|
28 * @bug 8046321 |
|
29 * @summary OCSP Stapling for TLS (StatusResponseManager tests) |
|
30 * @library ../../../../java/security/testlibrary |
|
31 * @build CertificateBuilder SimpleOCSPServer |
|
32 * @build StatusResponseManagerTests TestCase TestUtils |
|
33 * @run main/othervm -Djavax.net.debug=ssl:respmgr |
|
34 * sun.security.ssl.StatusResponseManagerTests |
|
35 */ |
|
36 |
|
37 import java.io.IOException; |
|
38 import java.math.BigInteger; |
|
39 import java.security.cert.*; |
|
40 import java.util.*; |
|
41 import java.security.KeyPair; |
|
42 import java.security.KeyPairGenerator; |
|
43 import java.security.KeyStore; |
|
44 import java.security.PublicKey; |
|
45 import java.util.concurrent.TimeUnit; |
|
46 |
|
47 import sun.security.testlibrary.SimpleOCSPServer; |
|
48 import sun.security.testlibrary.CertificateBuilder; |
|
49 |
|
50 /* |
|
51 * Checks that the hash value for a certificate's issuer name is generated |
|
52 * correctly. Requires any certificate that is not self-signed. |
|
53 * |
|
54 * NOTE: this test uses Sun private classes which are subject to change. |
|
55 */ |
|
56 public class StatusResponseManagerTests { |
|
57 |
|
58 private static final boolean debug = true; |
|
59 private static final boolean ocspDebug = false; |
|
60 |
|
61 // PKI components we will need for this test |
|
62 static String passwd = "passphrase"; |
|
63 static String ROOT_ALIAS = "root"; |
|
64 static String INT_ALIAS = "intermediate"; |
|
65 static String SSL_ALIAS = "ssl"; |
|
66 static KeyStore rootKeystore; // Root CA Keystore |
|
67 static KeyStore intKeystore; // Intermediate CA Keystore |
|
68 static KeyStore serverKeystore; // SSL Server Keystore |
|
69 static KeyStore trustStore; // SSL Client trust store |
|
70 static X509Certificate rootCert; |
|
71 static X509Certificate intCert; |
|
72 static X509Certificate sslCert; |
|
73 static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder |
|
74 static int rootOcspPort; // Port number for root OCSP |
|
75 static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder |
|
76 static int intOcspPort; // Port number for intermed. OCSP |
|
77 |
|
78 static X509Certificate[] chain; |
|
79 |
|
80 public static void main(String[] args) throws Exception { |
|
81 Map<String, TestCase> testList = |
|
82 new LinkedHashMap<String, TestCase>() {{ |
|
83 put("Basic OCSP fetch test", testOcspFetch); |
|
84 put("Clear StatusResponseManager cache", testClearSRM); |
|
85 put("Basic OCSP_MULTI fetch test", testOcspMultiFetch); |
|
86 put("Test Cache Expiration", testCacheExpiry); |
|
87 }}; |
|
88 |
|
89 // Create the CAs and OCSP responders |
|
90 createPKI(); |
|
91 |
|
92 // Grab the certificates and make a chain we can reuse for tests |
|
93 sslCert = (X509Certificate)serverKeystore.getCertificate(SSL_ALIAS); |
|
94 intCert = (X509Certificate)intKeystore.getCertificate(INT_ALIAS); |
|
95 rootCert = (X509Certificate)rootKeystore.getCertificate(ROOT_ALIAS); |
|
96 chain = new X509Certificate[3]; |
|
97 chain[0] = sslCert; |
|
98 chain[1] = intCert; |
|
99 chain[2] = rootCert; |
|
100 |
|
101 TestUtils.runTests(testList); |
|
102 |
|
103 intOcsp.stop(); |
|
104 rootOcsp.stop(); |
|
105 } |
|
106 |
|
107 // Test a simple RFC 6066 server-side fetch |
|
108 public static final TestCase testOcspFetch = new TestCase() { |
|
109 @Override |
|
110 public Map.Entry<Boolean, String> runTest() { |
|
111 StatusResponseManager srm = new StatusResponseManager(); |
|
112 Boolean pass = Boolean.FALSE; |
|
113 String message = null; |
|
114 StatusRequest oReq = new OCSPStatusRequest(); |
|
115 |
|
116 try { |
|
117 // Get OCSP responses for non-root certs in the chain |
|
118 Map<X509Certificate, byte[]> responseMap = srm.get( |
|
119 StatusRequestType.OCSP, oReq, chain, 5000, |
|
120 TimeUnit.MILLISECONDS); |
|
121 |
|
122 // There should be one entry in the returned map and |
|
123 // one entry in the cache when the operation is complete. |
|
124 if (responseMap.size() != 1) { |
|
125 message = "Incorrect number of responses: expected 1, got " |
|
126 + responseMap.size(); |
|
127 } else if (!responseMap.containsKey(sslCert)) { |
|
128 message = "Response map key is incorrect, expected " + |
|
129 sslCert.getSubjectX500Principal().toString(); |
|
130 } else if (srm.size() != 1) { |
|
131 message = "Incorrect number of cache entries: " + |
|
132 "expected 1, got " + srm.size(); |
|
133 } else { |
|
134 pass = Boolean.TRUE; |
|
135 } |
|
136 } catch (Exception e) { |
|
137 e.printStackTrace(System.out); |
|
138 message = e.getClass().getName(); |
|
139 } |
|
140 |
|
141 return new AbstractMap.SimpleEntry<>(pass, message); |
|
142 } |
|
143 }; |
|
144 |
|
145 // Test clearing the StatusResponseManager cache. |
|
146 public static final TestCase testClearSRM = new TestCase() { |
|
147 @Override |
|
148 public Map.Entry<Boolean, String> runTest() { |
|
149 StatusResponseManager srm = new StatusResponseManager(); |
|
150 Boolean pass = Boolean.FALSE; |
|
151 String message = null; |
|
152 StatusRequest oReq = new OCSPStatusRequest(); |
|
153 |
|
154 try { |
|
155 // Get OCSP responses for non-root certs in the chain |
|
156 srm.get(StatusRequestType.OCSP_MULTI, oReq, chain, 5000, |
|
157 TimeUnit.MILLISECONDS); |
|
158 |
|
159 // There should be two entries in the returned map and |
|
160 // two entries in the cache when the operation is complete. |
|
161 if (srm.size() != 2) { |
|
162 message = "Incorrect number of responses: expected 2, got " |
|
163 + srm.size(); |
|
164 } else { |
|
165 // Next, clear the SRM, then check the size again |
|
166 srm.clear(); |
|
167 if (srm.size() != 0) { |
|
168 message = "Incorrect number of responses: expected 0," + |
|
169 " got " + srm.size(); |
|
170 } else { |
|
171 pass = Boolean.TRUE; |
|
172 } |
|
173 } |
|
174 } catch (Exception e) { |
|
175 e.printStackTrace(System.out); |
|
176 message = e.getClass().getName(); |
|
177 } |
|
178 |
|
179 return new AbstractMap.SimpleEntry<>(pass, message); |
|
180 } |
|
181 }; |
|
182 |
|
183 // Test a simple RFC 6961 server-side fetch |
|
184 public static final TestCase testOcspMultiFetch = new TestCase() { |
|
185 @Override |
|
186 public Map.Entry<Boolean, String> runTest() { |
|
187 StatusResponseManager srm = new StatusResponseManager(); |
|
188 Boolean pass = Boolean.FALSE; |
|
189 String message = null; |
|
190 StatusRequest oReq = new OCSPStatusRequest(); |
|
191 |
|
192 try { |
|
193 // Get OCSP responses for non-root certs in the chain |
|
194 Map<X509Certificate, byte[]> responseMap = srm.get( |
|
195 StatusRequestType.OCSP_MULTI, oReq, chain, 5000, |
|
196 TimeUnit.MILLISECONDS); |
|
197 |
|
198 // There should be two entries in the returned map and |
|
199 // two entries in the cache when the operation is complete. |
|
200 if (responseMap.size() != 2) { |
|
201 message = "Incorrect number of responses: expected 2, got " |
|
202 + responseMap.size(); |
|
203 } else if (!responseMap.containsKey(sslCert) || |
|
204 !responseMap.containsKey(intCert)) { |
|
205 message = "Response map keys are incorrect, expected " + |
|
206 sslCert.getSubjectX500Principal().toString() + |
|
207 " and " + |
|
208 intCert.getSubjectX500Principal().toString(); |
|
209 } else if (srm.size() != 2) { |
|
210 message = "Incorrect number of cache entries: " + |
|
211 "expected 2, got " + srm.size(); |
|
212 } else { |
|
213 pass = Boolean.TRUE; |
|
214 } |
|
215 } catch (Exception e) { |
|
216 e.printStackTrace(System.out); |
|
217 message = e.getClass().getName(); |
|
218 } |
|
219 |
|
220 return new AbstractMap.SimpleEntry<>(pass, message); |
|
221 } |
|
222 }; |
|
223 |
|
224 // Test cache expiration |
|
225 public static final TestCase testCacheExpiry = new TestCase() { |
|
226 @Override |
|
227 public Map.Entry<Boolean, String> runTest() { |
|
228 // For this test, we will set the cache expiry to 5 seconds |
|
229 System.setProperty("jdk.tls.stapling.cacheLifetime", "5"); |
|
230 StatusResponseManager srm = new StatusResponseManager(); |
|
231 Boolean pass = Boolean.FALSE; |
|
232 String message = null; |
|
233 StatusRequest oReq = new OCSPStatusRequest(); |
|
234 |
|
235 try { |
|
236 // Get OCSP responses for non-root certs in the chain |
|
237 srm.get(StatusRequestType.OCSP_MULTI, oReq, chain, 5000, |
|
238 TimeUnit.MILLISECONDS); |
|
239 |
|
240 // There should be two entries in the returned map and |
|
241 // two entries in the cache when the operation is complete. |
|
242 if (srm.size() != 2) { |
|
243 message = "Incorrect number of responses: expected 2, got " |
|
244 + srm.size(); |
|
245 } else { |
|
246 // Next, wait for more than 5 seconds so the responses |
|
247 // in the SRM will expire. |
|
248 Thread.sleep(7000); |
|
249 if (srm.size() != 0) { |
|
250 message = "Incorrect number of responses: expected 0," + |
|
251 " got " + srm.size(); |
|
252 } else { |
|
253 pass = Boolean.TRUE; |
|
254 } |
|
255 } |
|
256 } catch (Exception e) { |
|
257 e.printStackTrace(System.out); |
|
258 message = e.getClass().getName(); |
|
259 } |
|
260 |
|
261 // Set the cache lifetime back to the default |
|
262 System.setProperty("jdk.tls.stapling.cacheLifetime", ""); |
|
263 return new AbstractMap.SimpleEntry<>(pass, message); |
|
264 } |
|
265 }; |
|
266 |
|
267 /** |
|
268 * Creates the PKI components necessary for this test, including |
|
269 * Root CA, Intermediate CA and SSL server certificates, the keystores |
|
270 * for each entity, a client trust store, and starts the OCSP responders. |
|
271 */ |
|
272 private static void createPKI() throws Exception { |
|
273 CertificateBuilder cbld = new CertificateBuilder(); |
|
274 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); |
|
275 keyGen.initialize(2048); |
|
276 KeyStore.Builder keyStoreBuilder = |
|
277 KeyStore.Builder.newInstance("PKCS12", null, |
|
278 new KeyStore.PasswordProtection(passwd.toCharArray())); |
|
279 |
|
280 // Generate Root, IntCA, EE keys |
|
281 KeyPair rootCaKP = keyGen.genKeyPair(); |
|
282 log("Generated Root CA KeyPair"); |
|
283 KeyPair intCaKP = keyGen.genKeyPair(); |
|
284 log("Generated Intermediate CA KeyPair"); |
|
285 KeyPair sslKP = keyGen.genKeyPair(); |
|
286 log("Generated SSL Cert KeyPair"); |
|
287 |
|
288 // Set up the Root CA Cert |
|
289 cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany"); |
|
290 cbld.setPublicKey(rootCaKP.getPublic()); |
|
291 cbld.setSerialNumber(new BigInteger("1")); |
|
292 // Make a 3 year validity starting from 60 days ago |
|
293 long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60); |
|
294 long end = start + TimeUnit.DAYS.toMillis(1085); |
|
295 cbld.setValidity(new Date(start), new Date(end)); |
|
296 addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic()); |
|
297 addCommonCAExts(cbld); |
|
298 // Make our Root CA Cert! |
|
299 X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(), |
|
300 "SHA256withRSA"); |
|
301 log("Root CA Created:\n" + certInfo(rootCert)); |
|
302 |
|
303 // Now build a keystore and add the keys and cert |
|
304 rootKeystore = keyStoreBuilder.getKeyStore(); |
|
305 Certificate[] rootChain = {rootCert}; |
|
306 rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(), |
|
307 passwd.toCharArray(), rootChain); |
|
308 |
|
309 // Now fire up the OCSP responder |
|
310 rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null); |
|
311 rootOcsp.enableLog(ocspDebug); |
|
312 rootOcsp.setNextUpdateInterval(3600); |
|
313 rootOcsp.start(); |
|
314 Thread.sleep(1000); // Give the server a second to start up |
|
315 rootOcspPort = rootOcsp.getPort(); |
|
316 String rootRespURI = "http://localhost:" + rootOcspPort; |
|
317 log("Root OCSP Responder URI is " + rootRespURI); |
|
318 |
|
319 // Now that we have the root keystore and OCSP responder we can |
|
320 // create our intermediate CA. |
|
321 cbld.reset(); |
|
322 cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany"); |
|
323 cbld.setPublicKey(intCaKP.getPublic()); |
|
324 cbld.setSerialNumber(new BigInteger("100")); |
|
325 // Make a 2 year validity starting from 30 days ago |
|
326 start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30); |
|
327 end = start + TimeUnit.DAYS.toMillis(730); |
|
328 cbld.setValidity(new Date(start), new Date(end)); |
|
329 addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic()); |
|
330 addCommonCAExts(cbld); |
|
331 cbld.addAIAExt(Collections.singletonList(rootRespURI)); |
|
332 // Make our Intermediate CA Cert! |
|
333 X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(), |
|
334 "SHA256withRSA"); |
|
335 log("Intermediate CA Created:\n" + certInfo(intCaCert)); |
|
336 |
|
337 // Provide intermediate CA cert revocation info to the Root CA |
|
338 // OCSP responder. |
|
339 Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo = |
|
340 new HashMap<>(); |
|
341 revInfo.put(intCaCert.getSerialNumber(), |
|
342 new SimpleOCSPServer.CertStatusInfo( |
|
343 SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD)); |
|
344 rootOcsp.updateStatusDb(revInfo); |
|
345 |
|
346 // Now build a keystore and add the keys, chain and root cert as a TA |
|
347 intKeystore = keyStoreBuilder.getKeyStore(); |
|
348 Certificate[] intChain = {intCaCert, rootCert}; |
|
349 intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(), |
|
350 passwd.toCharArray(), intChain); |
|
351 intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert); |
|
352 |
|
353 // Now fire up the Intermediate CA OCSP responder |
|
354 intOcsp = new SimpleOCSPServer(intKeystore, passwd, |
|
355 INT_ALIAS, null); |
|
356 intOcsp.enableLog(ocspDebug); |
|
357 intOcsp.setNextUpdateInterval(3600); |
|
358 intOcsp.start(); |
|
359 Thread.sleep(1000); |
|
360 intOcspPort = intOcsp.getPort(); |
|
361 String intCaRespURI = "http://localhost:" + intOcspPort; |
|
362 log("Intermediate CA OCSP Responder URI is " + intCaRespURI); |
|
363 |
|
364 // Last but not least, let's make our SSLCert and add it to its own |
|
365 // Keystore |
|
366 cbld.reset(); |
|
367 cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany"); |
|
368 cbld.setPublicKey(sslKP.getPublic()); |
|
369 cbld.setSerialNumber(new BigInteger("4096")); |
|
370 // Make a 1 year validity starting from 7 days ago |
|
371 start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7); |
|
372 end = start + TimeUnit.DAYS.toMillis(365); |
|
373 cbld.setValidity(new Date(start), new Date(end)); |
|
374 |
|
375 // Add extensions |
|
376 addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic()); |
|
377 boolean[] kuBits = {true, false, true, false, false, false, |
|
378 false, false, false}; |
|
379 cbld.addKeyUsageExt(kuBits); |
|
380 List<String> ekuOids = new ArrayList<>(); |
|
381 ekuOids.add("1.3.6.1.5.5.7.3.1"); |
|
382 ekuOids.add("1.3.6.1.5.5.7.3.2"); |
|
383 cbld.addExtendedKeyUsageExt(ekuOids); |
|
384 cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost")); |
|
385 cbld.addAIAExt(Collections.singletonList(intCaRespURI)); |
|
386 // Make our SSL Server Cert! |
|
387 X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(), |
|
388 "SHA256withRSA"); |
|
389 log("SSL Certificate Created:\n" + certInfo(sslCert)); |
|
390 |
|
391 // Provide SSL server cert revocation info to the Intermeidate CA |
|
392 // OCSP responder. |
|
393 revInfo = new HashMap<>(); |
|
394 revInfo.put(sslCert.getSerialNumber(), |
|
395 new SimpleOCSPServer.CertStatusInfo( |
|
396 SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD)); |
|
397 intOcsp.updateStatusDb(revInfo); |
|
398 |
|
399 // Now build a keystore and add the keys, chain and root cert as a TA |
|
400 serverKeystore = keyStoreBuilder.getKeyStore(); |
|
401 Certificate[] sslChain = {sslCert, intCaCert, rootCert}; |
|
402 serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(), |
|
403 passwd.toCharArray(), sslChain); |
|
404 serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert); |
|
405 |
|
406 // And finally a Trust Store for the client |
|
407 trustStore = keyStoreBuilder.getKeyStore(); |
|
408 trustStore.setCertificateEntry(ROOT_ALIAS, rootCert); |
|
409 } |
|
410 |
|
411 private static void addCommonExts(CertificateBuilder cbld, |
|
412 PublicKey subjKey, PublicKey authKey) throws IOException { |
|
413 cbld.addSubjectKeyIdExt(subjKey); |
|
414 cbld.addAuthorityKeyIdExt(authKey); |
|
415 } |
|
416 |
|
417 private static void addCommonCAExts(CertificateBuilder cbld) |
|
418 throws IOException { |
|
419 cbld.addBasicConstraintsExt(true, true, -1); |
|
420 // Set key usage bits for digitalSignature, keyCertSign and cRLSign |
|
421 boolean[] kuBitSettings = {true, false, false, false, false, true, |
|
422 true, false, false}; |
|
423 cbld.addKeyUsageExt(kuBitSettings); |
|
424 } |
|
425 |
|
426 /** |
|
427 * Helper routine that dumps only a few cert fields rather than |
|
428 * the whole toString() output. |
|
429 * |
|
430 * @param cert An X509Certificate to be displayed |
|
431 * |
|
432 * @return The {@link String} output of the issuer, subject and |
|
433 * serial number |
|
434 */ |
|
435 private static String certInfo(X509Certificate cert) { |
|
436 StringBuilder sb = new StringBuilder(); |
|
437 sb.append("Issuer: ").append(cert.getIssuerX500Principal()). |
|
438 append("\n"); |
|
439 sb.append("Subject: ").append(cert.getSubjectX500Principal()). |
|
440 append("\n"); |
|
441 sb.append("Serial: ").append(cert.getSerialNumber()).append("\n"); |
|
442 return sb.toString(); |
|
443 } |
|
444 |
|
445 /** |
|
446 * Log a message on stdout |
|
447 * |
|
448 * @param message The message to log |
|
449 */ |
|
450 private static void log(String message) { |
|
451 if (debug) { |
|
452 System.out.println(message); |
|
453 } |
|
454 } |
|
455 } |
|