--- /dev/null 2018-05-11 10:42:23.849000000 -0700 +++ new/test/jdk/sun/security/ssl/Stapling/java.base/sun/security/ssl/StatusResponseManagerTests.java 2018-05-11 15:11:10.401994000 -0700 @@ -0,0 +1,492 @@ +/* + * 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; + +import static sun.security.ssl.CertStatusExtension.*; + +/* + * 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 testList = + new LinkedHashMap() {{ + 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; + + 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 runTest() { + StatusResponseManager srm = new StatusResponseManager(); + Boolean pass = Boolean.FALSE; + String message = null; + CertStatusRequest oReq = OCSPStatusRequest.EMPTY_OCSP; + + try { + // Get OCSP responses for non-root certs in the chain + Map responseMap = srm.get( + CertStatusRequestType.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 runTest() { + StatusResponseManager srm = new StatusResponseManager(); + Boolean pass = Boolean.FALSE; + String message = null; + CertStatusRequest oReq = OCSPStatusRequest.EMPTY_OCSP_MULTI; + + try { + // Get OCSP responses for non-root certs in the chain + srm.get(CertStatusRequestType.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 runTest() { + StatusResponseManager srm = new StatusResponseManager(); + Boolean pass = Boolean.FALSE; + String message = null; + CertStatusRequest oReq = OCSPStatusRequest.EMPTY_OCSP_MULTI; + + try { + // Get OCSP responses for non-root certs in the chain + Map responseMap = srm.get( + CertStatusRequestType.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 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; + CertStatusRequest oReq = OCSPStatusRequest.EMPTY_OCSP_MULTI; + + try { + // Get OCSP responses for non-root certs in the chain + srm.get(CertStatusRequestType.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 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 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); + } + } + + public static void runTests(Map testList) { + int testNo = 0; + int numberFailed = 0; + Map.Entry result; + + System.out.println("============ Tests ============"); + for (String testName : testList.keySet()) { + System.out.println("Test " + ++testNo + ": " + testName); + result = testList.get(testName).runTest(); + System.out.print("Result: " + (result.getKey() ? "PASS" : "FAIL")); + System.out.println(" " + + (result.getValue() != null ? result.getValue() : "")); + System.out.println("-------------------------------------------"); + if (!result.getKey()) { + numberFailed++; + } + } + + System.out.println("End Results: " + (testList.size() - numberFailed) + + " Passed" + ", " + numberFailed + " Failed."); + if (numberFailed > 0) { + throw new RuntimeException( + "One or more tests failed, see test output for details"); + } + } + + public interface TestCase { + Map.Entry runTest(); + } +}