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 }