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