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