1 /*
  2  * Copyright (c) 2023, 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 //
 25 // Security properties, once set, cannot revert to unset.  To avoid
 26 // conflicts with tests running in the same VM isolate this test by
 27 // running it in otherVM mode.
 28 //
 29 
 30 /**
 31  * @test
 32  * @bug 8179502
 33  * @summary Enhance OCSP, CRL and Certificate Fetch Timeouts
 34  * @modules java.base/sun.security.x509
 35  *          java.base/sun.security.provider.certpath
 36  *          java.base/sun.security.util
 37  * @library ../../../../../java/security/testlibrary
 38  * @build CertificateBuilder SimpleOCSPServer
 39  * @run main/othervm OCSPTimeout 1000 true
 40  * @run main/othervm -Dcom.sun.security.ocsp.readtimeout=5
 41  *      OCSPTimeout 1000 true
 42  * @run main/othervm -Dcom.sun.security.ocsp.readtimeout=1
 43  *      OCSPTimeout 5000 false
 44  * @run main/othervm -Dcom.sun.security.ocsp.readtimeout=1s
 45  *      OCSPTimeout 5000 false
 46  * @run main/othervm -Dcom.sun.security.ocsp.readtimeout=1500ms
 47  *      OCSPTimeout 5000 false
 48  * @run main/othervm -Dcom.sun.security.ocsp.readtimeout=4500ms
 49  *      OCSPTimeout 1000 true
 50  */
 51 
 52 import java.io.*;
 53 import java.math.BigInteger;
 54 import java.net.*;
 55 import java.security.*;
 56 import java.security.cert.Certificate;
 57 import java.security.spec.ECGenParameterSpec;
 58 import java.util.*;
 59 import java.security.cert.*;
 60 import java.util.concurrent.TimeUnit;
 61 
 62 import sun.security.testlibrary.SimpleOCSPServer;
 63 import sun.security.testlibrary.CertificateBuilder;
 64 
 65 import static java.security.cert.PKIXRevocationChecker.Option.*;
 66 
 67 public class OCSPTimeout {
 68 
 69     static String passwd = "passphrase";
 70     static String ROOT_ALIAS = "root";
 71     static String EE_ALIAS = "endentity";
 72 
 73     // Enable debugging for additional output
 74     static final boolean debug = true;
 75 
 76     // PKI components we will need for this test
 77     static X509Certificate rootCert;        // The root CA certificate
 78     static X509Certificate eeCert;          // The end entity certificate
 79     static KeyStore rootKeystore;           // Root CA Keystore
 80     static KeyStore eeKeystore;             // End Entity Keystore
 81     static KeyStore trustStore;             // SSL Client trust store
 82     static SimpleOCSPServer rootOcsp;       // Root CA OCSP Responder
 83     static int rootOcspPort;                // Port number for root OCSP
 84 
 85     public static void main(String args[]) throws Exception {
 86         int ocspTimeout = 15000;
 87         boolean expected = false;
 88 
 89         createPKI();
 90 
 91         if (args[0] != null) {
 92             ocspTimeout = Integer.parseInt(args[0]);
 93         }
 94         rootOcsp.setDelay(ocspTimeout);
 95 
 96         expected = (args[1] != null && Boolean.parseBoolean(args[1]));
 97         log("Test case expects to " + (expected ? "pass" : "fail"));
 98 
 99         // validate chain
100         CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
101         PKIXRevocationChecker prc =
102                 (PKIXRevocationChecker) cpv.getRevocationChecker();
103         prc.setOptions(EnumSet.of(NO_FALLBACK, SOFT_FAIL));
104         PKIXParameters params =
105                 new PKIXParameters(Set.of(new TrustAnchor(rootCert, null)));
106         params.addCertPathChecker(prc);
107         CertificateFactory cf = CertificateFactory.getInstance("X.509");
108         CertPath cp = cf.generateCertPath(List.of(eeCert));
109         cpv.validate(cp, params);
110 
111         // unwrap soft fail exceptions and check for SocketTimeoutException
112         List<CertPathValidatorException> softExc = prc.getSoftFailExceptions();
113         if (expected) {
114             if (softExc.size() > 0) {
115                 throw new RuntimeException("Expected to pass, found " +
116                         softExc.size() + " soft fail exceptions");
117             }
118         } else {
119             // If we expect to fail the validation then there should be a
120             // SocketTimeoutException
121             boolean found = false;
122             for (CertPathValidatorException softFail : softExc) {
123                 log("CPVE: " + softFail);
124                 Throwable cause = softFail.getCause();
125                 log("Cause: " + cause);
126                 while (cause != null) {
127                     if (cause instanceof SocketTimeoutException) {
128                         found = true;
129                         break;
130                     }
131                     cause = cause.getCause();
132                 }
133                 if (found) {
134                     break;
135                 }
136             }
137 
138             if (!found) {
139                 throw new RuntimeException("SocketTimeoutException not thrown");
140             }
141         }
142     }
143 
144     /**
145      * Creates the PKI components necessary for this test, including
146      * Root CA, Intermediate CA and SSL server certificates, the keystores
147      * for each entity, a client trust store, and starts the OCSP responders.
148      */
149     private static void createPKI() throws Exception {
150         CertificateBuilder cbld = new CertificateBuilder();
151         KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
152         keyGen.initialize(new ECGenParameterSpec("secp256r1"));
153         KeyStore.Builder keyStoreBuilder =
154                 KeyStore.Builder.newInstance("PKCS12", null,
155                         new KeyStore.PasswordProtection(passwd.toCharArray()));
156 
157         // Generate Root and EE keys
158         KeyPair rootCaKP = keyGen.genKeyPair();
159         log("Generated Root CA KeyPair");
160         KeyPair eeKP = keyGen.genKeyPair();
161         log("Generated End Entity KeyPair");
162 
163         // Set up the Root CA Cert
164         cbld.setSubjectName("CN=Root CA Cert, O=SomeCompany");
165         cbld.setPublicKey(rootCaKP.getPublic());
166         cbld.setSerialNumber(new BigInteger("1"));
167         // Make a 3 year validity starting from 60 days ago
168         long start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(60);
169         long end = start + TimeUnit.DAYS.toMillis(1085);
170         cbld.setValidity(new Date(start), new Date(end));
171         addCommonExts(cbld, rootCaKP.getPublic(), rootCaKP.getPublic());
172         addCommonCAExts(cbld);
173         // Make our Root CA Cert!
174         rootCert = cbld.build(null, rootCaKP.getPrivate(),
175                 "SHA256withECDSA");
176         log("Root CA Created:\n%s", certInfo(rootCert));
177 
178         // Now build a keystore and add the keys and cert
179         rootKeystore = keyStoreBuilder.getKeyStore();
180         Certificate[] rootChain = {rootCert};
181         rootKeystore.setKeyEntry(ROOT_ALIAS, rootCaKP.getPrivate(),
182                 passwd.toCharArray(), rootChain);
183 
184         // Now fire up the OCSP responder
185         rootOcsp = new SimpleOCSPServer(rootKeystore, passwd, ROOT_ALIAS, null);
186         rootOcsp.enableLog(debug);
187         rootOcsp.setNextUpdateInterval(3600);
188         rootOcsp.setDisableContentLength(true);
189         rootOcsp.start();
190 
191         // Wait 5 seconds for server ready
192         boolean readyStatus = rootOcsp.awaitServerReady(60, TimeUnit.SECONDS);
193         if (!readyStatus) {
194             throw new RuntimeException("Server not ready");
195         }
196 
197         rootOcspPort = rootOcsp.getPort();
198         String rootRespURI = "http://localhost:" + rootOcspPort;
199         log("Root OCSP Responder URI is %s", rootRespURI);
200 
201         // Now that we have the root keystore and OCSP responder we can
202         // create our end entity certificate
203         cbld.reset();
204         cbld.setSubjectName("CN=SSLCertificate, O=SomeCompany");
205         cbld.setPublicKey(eeKP.getPublic());
206         cbld.setSerialNumber(new BigInteger("4096"));
207         // Make a 1 year validity starting from 7 days ago
208         start = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
209         end = start + TimeUnit.DAYS.toMillis(365);
210         cbld.setValidity(new Date(start), new Date(end));
211 
212         // Add extensions
213         addCommonExts(cbld, eeKP.getPublic(), rootCaKP.getPublic());
214         boolean[] kuBits = {true, false, false, false, false, false,
215                 false, false, false};
216         cbld.addKeyUsageExt(kuBits);
217         List<String> ekuOids = new ArrayList<>();
218         ekuOids.add("1.3.6.1.5.5.7.3.1");
219         ekuOids.add("1.3.6.1.5.5.7.3.2");
220         cbld.addExtendedKeyUsageExt(ekuOids);
221         cbld.addSubjectAltNameDNSExt(Collections.singletonList("localhost"));
222         cbld.addAIAExt(Collections.singletonList(rootRespURI));
223         // Make our End Entity Cert!
224         eeCert = cbld.build(rootCert, rootCaKP.getPrivate(),
225                 "SHA256withECDSA");
226         log("SSL Certificate Created:\n%s", certInfo(eeCert));
227 
228         // Provide end entity cert revocation info to the Root CA
229         // OCSP responder.
230         Map<BigInteger, SimpleOCSPServer.CertStatusInfo> revInfo =
231                 new HashMap<>();
232         revInfo.put(eeCert.getSerialNumber(),
233                 new SimpleOCSPServer.CertStatusInfo(
234                         SimpleOCSPServer.CertStatus.CERT_STATUS_GOOD));
235         rootOcsp.updateStatusDb(revInfo);
236 
237         // Now build a keystore and add the keys, chain and root cert as a TA
238         eeKeystore = keyStoreBuilder.getKeyStore();
239         Certificate[] eeChain = {eeCert, rootCert};
240         eeKeystore.setKeyEntry(EE_ALIAS, eeKP.getPrivate(),
241                 passwd.toCharArray(), eeChain);
242         eeKeystore.setCertificateEntry(ROOT_ALIAS, rootCert);
243 
244         // And finally a Trust Store for the client
245         trustStore = keyStoreBuilder.getKeyStore();
246         trustStore.setCertificateEntry(ROOT_ALIAS, rootCert);
247     }
248 
249     private static void addCommonExts(CertificateBuilder cbld,
250             PublicKey subjKey, PublicKey authKey) throws IOException {
251         cbld.addSubjectKeyIdExt(subjKey);
252         cbld.addAuthorityKeyIdExt(authKey);
253     }
254 
255     private static void addCommonCAExts(CertificateBuilder cbld)
256             throws IOException {
257         cbld.addBasicConstraintsExt(true, true, -1);
258         // Set key usage bits for digitalSignature, keyCertSign and cRLSign
259         boolean[] kuBitSettings = {true, false, false, false, false, true,
260                 true, false, false};
261         cbld.addKeyUsageExt(kuBitSettings);
262     }
263 
264     /**
265      * Helper routine that dumps only a few cert fields rather than
266      * the whole toString() output.
267      *
268      * @param cert an X509Certificate to be displayed
269      *
270      * @return the String output of the issuer, subject and
271      * serial number
272      */
273     private static String certInfo(X509Certificate cert) {
274         StringBuilder sb = new StringBuilder();
275         sb.append("Issuer: ").append(cert.getIssuerX500Principal()).
276                 append("\n");
277         sb.append("Subject: ").append(cert.getSubjectX500Principal()).
278                 append("\n");
279         sb.append("Serial: ").append(cert.getSerialNumber()).append("\n");
280         return sb.toString();
281     }
282 
283     /**
284      * Log a message on stdout
285      *
286      * @param format the format string for the log entry
287      * @param args zero or more arguments corresponding to the format string
288      */
289     private static void log(String format, Object ... args) {
290         System.out.format(format + "\n", args);
291     }
292 }