/* * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.security.ssl; import java.io.IOException; import java.math.BigInteger; import java.security.cert.*; import java.util.*; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.PublicKey; import java.util.concurrent.TimeUnit; import sun.security.testlibrary.SimpleOCSPServer; import sun.security.testlibrary.CertificateBuilder; /* * Checks that the hash value for a certificate's issuer name is generated * correctly. Requires any certificate that is not self-signed. * * NOTE: this test uses Sun private classes which are subject to change. */ public class StatusResponseManagerTests { private static final boolean debug = true; private static final boolean ocspDebug = false; // PKI components we will need for this test static String passwd = "passphrase"; static String ROOT_ALIAS = "root"; static String INT_ALIAS = "intermediate"; static String SSL_ALIAS = "ssl"; static KeyStore rootKeystore; // Root CA Keystore static KeyStore intKeystore; // Intermediate CA Keystore static KeyStore serverKeystore; // SSL Server Keystore static KeyStore trustStore; // SSL Client trust store static X509Certificate rootCert; static X509Certificate intCert; static X509Certificate sslCert; static SimpleOCSPServer rootOcsp; // Root CA OCSP Responder static int rootOcspPort; // Port number for root OCSP static SimpleOCSPServer intOcsp; // Intermediate CA OCSP Responder static int intOcspPort; // Port number for intermed. OCSP static X509Certificate[] chain; public static void main(String[] args) throws Exception { Map<String, TestCase> testList = new LinkedHashMap<String, TestCase>() {{ put("Basic OCSP fetch test", testOcspFetch); put("Clear StatusResponseManager cache", testClearSRM); put("Basic OCSP_MULTI fetch test", testOcspMultiFetch); put("Test Cache Expiration", testCacheExpiry); }}; // Create the CAs and OCSP responders createPKI(); // Grab the certificates and make a chain we can reuse for tests sslCert = (X509Certificate)serverKeystore.getCertificate(SSL_ALIAS); intCert = (X509Certificate)intKeystore.getCertificate(INT_ALIAS); rootCert = (X509Certificate)rootKeystore.getCertificate(ROOT_ALIAS); chain = new X509Certificate[3]; chain[0] = sslCert; chain[1] = intCert; chain[2] = rootCert; TestUtils.runTests(testList); intOcsp.stop(); rootOcsp.stop(); } // Test a simple RFC 6066 server-side fetch public static final TestCase testOcspFetch = new TestCase() { @Override public Map.Entry<Boolean, String> runTest() { StatusResponseManager srm = new StatusResponseManager(); Boolean pass = Boolean.FALSE; String message = null; StatusRequest oReq = new OCSPStatusRequest(); try { // Get OCSP responses for non-root certs in the chain Map<X509Certificate, byte[]> responseMap = srm.get( StatusRequestType.OCSP, oReq, chain, 5000, TimeUnit.MILLISECONDS); // There should be one entry in the returned map and // one entry in the cache when the operation is complete. if (responseMap.size() != 1) { message = "Incorrect number of responses: expected 1, got " + responseMap.size(); } else if (!responseMap.containsKey(sslCert)) { message = "Response map key is incorrect, expected " + sslCert.getSubjectX500Principal().toString(); } else if (srm.size() != 1) { message = "Incorrect number of cache entries: " + "expected 1, got " + srm.size(); } else { pass = Boolean.TRUE; } } catch (Exception e) { e.printStackTrace(System.out); message = e.getClass().getName(); } return new AbstractMap.SimpleEntry<>(pass, message); } }; // Test clearing the StatusResponseManager cache. public static final TestCase testClearSRM = new TestCase() { @Override public Map.Entry<Boolean, String> runTest() { StatusResponseManager srm = new StatusResponseManager(); Boolean pass = Boolean.FALSE; String message = null; StatusRequest oReq = new OCSPStatusRequest(); try { // Get OCSP responses for non-root certs in the chain srm.get(StatusRequestType.OCSP_MULTI, oReq, chain, 5000, TimeUnit.MILLISECONDS); // There should be two entries in the returned map and // two entries in the cache when the operation is complete. if (srm.size() != 2) { message = "Incorrect number of responses: expected 2, got " + srm.size(); } else { // Next, clear the SRM, then check the size again srm.clear(); if (srm.size() != 0) { message = "Incorrect number of responses: expected 0," + " got " + srm.size(); } else { pass = Boolean.TRUE; } } } catch (Exception e) { e.printStackTrace(System.out); message = e.getClass().getName(); } return new AbstractMap.SimpleEntry<>(pass, message); } }; // Test a simple RFC 6961 server-side fetch public static final TestCase testOcspMultiFetch = new TestCase() { @Override public Map.Entry<Boolean, String> runTest() { StatusResponseManager srm = new StatusResponseManager(); Boolean pass = Boolean.FALSE; String message = null; StatusRequest oReq = new OCSPStatusRequest(); try { // Get OCSP responses for non-root certs in the chain Map<X509Certificate, byte[]> responseMap = srm.get( StatusRequestType.OCSP_MULTI, oReq, chain, 5000, TimeUnit.MILLISECONDS); // There should be two entries in the returned map and // two entries in the cache when the operation is complete. if (responseMap.size() != 2) { message = "Incorrect number of responses: expected 2, got " + responseMap.size(); } else if (!responseMap.containsKey(sslCert) || !responseMap.containsKey(intCert)) { message = "Response map keys are incorrect, expected " + sslCert.getSubjectX500Principal().toString() + " and " + intCert.getSubjectX500Principal().toString(); } else if (srm.size() != 2) { message = "Incorrect number of cache entries: " + "expected 2, got " + srm.size(); } else { pass = Boolean.TRUE; } } catch (Exception e) { e.printStackTrace(System.out); message = e.getClass().getName(); } return new AbstractMap.SimpleEntry<>(pass, message); } }; // Test cache expiration public static final TestCase testCacheExpiry = new TestCase() { @Override public Map.Entry<Boolean, String> runTest() { // For this test, we will set the cache expiry to 5 seconds System.setProperty("jdk.tls.stapling.cacheLifetime", "5"); StatusResponseManager srm = new StatusResponseManager(); Boolean pass = Boolean.FALSE; String message = null; StatusRequest oReq = new OCSPStatusRequest(); try { // Get OCSP responses for non-root certs in the chain srm.get(StatusRequestType.OCSP_MULTI, oReq, chain, 5000, TimeUnit.MILLISECONDS); // There should be two entries in the returned map and // two entries in the cache when the operation is complete. if (srm.size() != 2) { message = "Incorrect number of responses: expected 2, got " + srm.size(); } else { // Next, wait for more than 5 seconds so the responses // in the SRM will expire. Thread.sleep(7000); if (srm.size() != 0) { message = "Incorrect number of responses: expected 0," + " got " + srm.size(); } else { pass = Boolean.TRUE; } } } catch (Exception e) { e.printStackTrace(System.out); message = e.getClass().getName(); } // Set the cache lifetime back to the default System.setProperty("jdk.tls.stapling.cacheLifetime", ""); return new AbstractMap.SimpleEntry<>(pass, message); } }; /** * Creates the PKI components necessary for this test, including * Root CA, Intermediate CA and SSL server certificates, the keystores * for each entity, a client trust store, and starts the OCSP responders. */ private static void createPKI() throws Exception { CertificateBuilder cbld = new CertificateBuilder(); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); KeyStore.Builder keyStoreBuilder = KeyStore.Builder.newInstance("PKCS12", null, new KeyStore.PasswordProtection(passwd.toCharArray())); // Generate Root, IntCA, EE keys KeyPair rootCaKP = keyGen.genKeyPair(); log("Generated Root CA KeyPair"); KeyPair intCaKP = keyGen.genKeyPair(); log("Generated Intermediate CA KeyPair"); KeyPair sslKP = keyGen.genKeyPair(); log("Generated SSL Cert KeyPair"); // Set up the Root CA Cert cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany"); cbld.setPublicKey(rootCaKP.getPublic()); cbld.setSerialNumber(new BigInteger("1")); // Make a 3 year validity starting from 60 days ago long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60); long end = start + TimeUnit.DAYS.toMillis(1085); cbld.setValidity(new Date(start), new Date(end)); addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic()); addCommonCAExts(cbld); // Make our Root CA Cert! X509Certificate rootCert = cbld.build(null, rootCaKP.getPrivate(), "SHA256withRSA"); log("Root CA Created:\n" + certInfo(rootCert)); // Now build a keystore and add the keys and cert rootKeystore = keyStoreBuilder.getKeyStore(); Certificate[] rootChain = {rootCert}; rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(), passwd.toCharArray(), rootChain); // Now fire up the OCSP responder rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null); rootOcsp.enableLog(ocspDebug); rootOcsp.setNextUpdateInterval(3600); rootOcsp.start(); // Wait 5 seconds for server ready for (int i = 0; (i < 100 && !rootOcsp.isServerReady()); i++) { Thread.sleep(50); } if (!rootOcsp.isServerReady()) { throw new RuntimeException("Server not ready yet"); } rootOcspPort = rootOcsp.getPort(); String rootRespURI = "http://localhost:" + rootOcspPort; log("Root OCSP Responder URI is " + rootRespURI); // Now that we have the root keystore and OCSP responder we can // create our intermediate CA. cbld.reset(); cbld.setSubjectName("CN=Intermediate CA Cert, O=SomeCompany"); cbld.setPublicKey(intCaKP.getPublic()); cbld.setSerialNumber(new BigInteger("100")); // Make a 2 year validity starting from 30 days ago start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(30); end = start + TimeUnit.DAYS.toMillis(730); cbld.setValidity(new Date(start), new Date(end)); addCommonExts(cbld, intCaKP.getPublic(), rootCaKP.getPublic()); addCommonCAExts(cbld); cbld.addAIAExt(Collections.singletonList(rootRespURI)); // Make our Intermediate CA Cert! X509Certificate intCaCert = cbld.build(rootCert, rootCaKP.getPrivate(), "SHA256withRSA"); log("Intermediate CA Created:\n" + certInfo(intCaCert)); // Provide intermediate CA cert revocation info to the Root CA // OCSP responder. Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo = new HashMap<>(); revInfo.put(intCaCert.getSerialNumber(), new SimpleOCSPServer.CertStatusInfo( SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD)); rootOcsp.updateStatusDb(revInfo); // Now build a keystore and add the keys, chain and root cert as a TA intKeystore = keyStoreBuilder.getKeyStore(); Certificate[] intChain = {intCaCert, rootCert}; intKeystore.setKeyEntry(INT_ALIAS, intCaKP.getPrivate(), passwd.toCharArray(), intChain); intKeystore.setCertificateEntry(ROOT_ALIAS, rootCert); // Now fire up the Intermediate CA OCSP responder intOcsp = new SimpleOCSPServer(intKeystore, passwd, INT_ALIAS, null); intOcsp.enableLog(ocspDebug); intOcsp.setNextUpdateInterval(3600); intOcsp.start(); // Wait 5 seconds for server ready for (int i = 0; (i < 100 && !intOcsp.isServerReady()); i++) { Thread.sleep(50); } if (!intOcsp.isServerReady()) { throw new RuntimeException("Server not ready yet"); } intOcspPort = intOcsp.getPort(); String intCaRespURI = "http://localhost:" + intOcspPort; log("Intermediate CA OCSP Responder URI is " + intCaRespURI); // Last but not least, let's make our SSLCert and add it to its own // Keystore cbld.reset(); cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany"); cbld.setPublicKey(sslKP.getPublic()); cbld.setSerialNumber(new BigInteger("4096")); // Make a 1 year validity starting from 7 days ago start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7); end = start + TimeUnit.DAYS.toMillis(365); cbld.setValidity(new Date(start), new Date(end)); // Add extensions addCommonExts(cbld, sslKP.getPublic(), intCaKP.getPublic()); boolean[] kuBits = {true, false, true, false, false, false, false, false, false}; cbld.addKeyUsageExt(kuBits); List<String> ekuOids = new ArrayList<>(); ekuOids.add("1.3.6.1.5.5.7.3.1"); ekuOids.add("1.3.6.1.5.5.7.3.2"); cbld.addExtendedKeyUsageExt(ekuOids); cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost")); cbld.addAIAExt(Collections.singletonList(intCaRespURI)); // Make our SSL Server Cert! X509Certificate sslCert = cbld.build(intCaCert, intCaKP.getPrivate(), "SHA256withRSA"); log("SSL Certificate Created:\n" + certInfo(sslCert)); // Provide SSL server cert revocation info to the Intermeidate CA // OCSP responder. revInfo = new HashMap<>(); revInfo.put(sslCert.getSerialNumber(), new SimpleOCSPServer.CertStatusInfo( SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD)); intOcsp.updateStatusDb(revInfo); // Now build a keystore and add the keys, chain and root cert as a TA serverKeystore = keyStoreBuilder.getKeyStore(); Certificate[] sslChain = {sslCert, intCaCert, rootCert}; serverKeystore.setKeyEntry(SSL_ALIAS, sslKP.getPrivate(), passwd.toCharArray(), sslChain); serverKeystore.setCertificateEntry(ROOT_ALIAS, rootCert); // And finally a Trust Store for the client trustStore = keyStoreBuilder.getKeyStore(); trustStore.setCertificateEntry(ROOT_ALIAS, rootCert); } private static void addCommonExts(CertificateBuilder cbld, PublicKey subjKey, PublicKey authKey) throws IOException { cbld.addSubjectKeyIdExt(subjKey); cbld.addAuthorityKeyIdExt(authKey); } private static void addCommonCAExts(CertificateBuilder cbld) throws IOException { cbld.addBasicConstraintsExt(true, true, -1); // Set key usage bits for digitalSignature, keyCertSign and cRLSign boolean[] kuBitSettings = {true, false, false, false, false, true, true, false, false}; cbld.addKeyUsageExt(kuBitSettings); } /** * Helper routine that dumps only a few cert fields rather than * the whole toString() output. * * @param cert An X509Certificate to be displayed * * @return The {@link String} output of the issuer, subject and * serial number */ private static String certInfo(X509Certificate cert) { StringBuilder sb = new StringBuilder(); sb.append("Issuer: ").append(cert.getIssuerX500Principal()). append("\n"); sb.append("Subject: ").append(cert.getSubjectX500Principal()). append("\n"); sb.append("Serial: ").append(cert.getSerialNumber()).append("\n"); return sb.toString(); } /** * Log a message on stdout * * @param message The message to log */ private static void log(String message) { if (debug) { System.out.println(message); } } }