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 }