1 /*
  2  * Copyright (c) 2009, 2020, 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.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package sun.security.provider.certpath;
 26 
 27 import java.io.InputStream;
 28 import java.io.IOException;
 29 import java.io.OutputStream;
 30 import java.net.URI;
 31 import java.net.URL;
 32 import java.net.HttpURLConnection;
 33 import java.security.cert.CertificateException;
 34 import java.security.cert.CertPathValidatorException;
 35 import java.security.cert.CertPathValidatorException.BasicReason;
 36 import java.security.cert.CRLReason;
 37 import java.security.cert.Extension;
 38 import java.security.cert.TrustAnchor;
 39 import java.security.cert.X509Certificate;
 40 import java.util.Arrays;
 41 import java.util.Collections;
 42 import java.util.Date;
 43 import java.util.List;
 44 import java.util.Map;
 45 
 46 import sun.security.action.GetIntegerAction;
 47 import sun.security.util.Debug;
 48 import sun.security.util.Event;
 49 import sun.security.validator.Validator;
 50 import sun.security.x509.AccessDescription;
 51 import sun.security.x509.AuthorityInfoAccessExtension;
 52 import sun.security.x509.GeneralName;
 53 import sun.security.x509.GeneralNameInterface;
 54 import sun.security.x509.PKIXExtensions;
 55 import sun.security.x509.URIName;
 56 import sun.security.x509.X509CertImpl;
 57 
 58 /**
 59  * This is a class that checks the revocation status of a certificate(s) using
 60  * OCSP. It is not a PKIXCertPathChecker and therefore can be used outside of
 61  * the CertPathValidator framework. It is useful when you want to
 62  * just check the revocation status of a certificate, and you don't want to
 63  * incur the overhead of validating all of the certificates in the
 64  * associated certificate chain.
 65  *
 66  * @author Sean Mullan
 67  */
 68 public final class OCSP {
 69 
 70     private static final Debug debug = Debug.getInstance("certpath");
 71 
 72     private static final int DEFAULT_CONNECT_TIMEOUT = 15000;
 73 
 74     /**
 75      * Integer value indicating the timeout length, in seconds, to be
 76      * used for the OCSP check. A timeout of zero is interpreted as
 77      * an infinite timeout.
 78      */
 79     private static final int CONNECT_TIMEOUT = initializeTimeout();
 80 
 81     /**
 82      * Initialize the timeout length by getting the OCSP timeout
 83      * system property. If the property has not been set, or if its
 84      * value is negative, set the timeout length to the default.
 85      */
 86     private static int initializeTimeout() {
 87         Integer tmp = java.security.AccessController.doPrivileged(
 88                 new GetIntegerAction("com.sun.security.ocsp.timeout"));
 89         if (tmp == null || tmp < 0) {
 90             return DEFAULT_CONNECT_TIMEOUT;
 91         }
 92         // Convert to milliseconds, as the system property will be
 93         // specified in seconds
 94         return tmp * 1000;
 95     }
 96 
 97     private OCSP() {}
 98 
 99 
100     /**
101      * Obtains the revocation status of a certificate using OCSP.
102      *
103      * @param cert the certificate to be checked
104      * @param issuerCert the issuer certificate
105      * @param responderURI the URI of the OCSP responder
106      * @param responderCert the OCSP responder's certificate
107      * @param date the time the validity of the OCSP responder's certificate
108      *    should be checked against. If null, the current time is used.
109      * @return the RevocationStatus
110      * @throws IOException if there is an exception connecting to or
111      *    communicating with the OCSP responder
112      * @throws CertPathValidatorException if an exception occurs while
113      *    encoding the OCSP Request or validating the OCSP Response
114      */
115 
116     // Called by com.sun.deploy.security.TrustDecider
117     public static RevocationStatus check(X509Certificate cert,
118                                          X509Certificate issuerCert,
119                                          URI responderURI,
120                                          X509Certificate responderCert,
121                                          Date date)
122         throws IOException, CertPathValidatorException
123     {
124         return check(cert, issuerCert, responderURI, responderCert, date,
125                      Collections.<Extension>emptyList(), Validator.VAR_GENERIC);
126     }
127 
128 
129     public static RevocationStatus check(X509Certificate cert,
130             X509Certificate issuerCert, URI responderURI,
131             X509Certificate responderCert, Date date, List<Extension> extensions,
132             String variant)
133         throws IOException, CertPathValidatorException
134     {
135         return check(cert, responderURI, null, issuerCert, responderCert, date,
136                 extensions, variant);
137     }
138 
139     public static RevocationStatus check(X509Certificate cert,
140             URI responderURI, TrustAnchor anchor, X509Certificate issuerCert,
141             X509Certificate responderCert, Date date,
142             List<Extension> extensions, String variant)
143             throws IOException, CertPathValidatorException
144     {
145         CertId certId;
146         try {
147             X509CertImpl certImpl = X509CertImpl.toImpl(cert);
148             certId = new CertId(issuerCert, certImpl.getSerialNumberObject());
149         } catch (CertificateException | IOException e) {
150             throw new CertPathValidatorException
151                 ("Exception while encoding OCSPRequest", e);
152         }
153         OCSPResponse ocspResponse = check(Collections.singletonList(certId),
154                 responderURI, new OCSPResponse.IssuerInfo(anchor, issuerCert),
155                 responderCert, date, extensions, variant);
156         return (RevocationStatus) ocspResponse.getSingleResponse(certId);
157     }
158 
159     /**
160      * Checks the revocation status of a list of certificates using OCSP.
161      *
162      * @param certIds the CertIds to be checked
163      * @param responderURI the URI of the OCSP responder
164      * @param issuerInfo the issuer's certificate and/or subject and public key
165      * @param responderCert the OCSP responder's certificate
166      * @param date the time the validity of the OCSP responder's certificate
167      *    should be checked against. If null, the current time is used.
168      * @param extensions zero or more OCSP extensions to be included in the
169      *    request.  If no extensions are requested, an empty {@code List} must
170      *    be used.  A {@code null} value is not allowed.
171      * @return the OCSPResponse
172      * @throws IOException if there is an exception connecting to or
173      *    communicating with the OCSP responder
174      * @throws CertPathValidatorException if an exception occurs while
175      *    encoding the OCSP Request or validating the OCSP Response
176      */
177     static OCSPResponse check(List<CertId> certIds, URI responderURI,
178                               OCSPResponse.IssuerInfo issuerInfo,
179                               X509Certificate responderCert, Date date,
180                               List<Extension> extensions, String variant)
181         throws IOException, CertPathValidatorException
182     {
183         byte[] nonce = null;
184         for (Extension ext : extensions) {
185             if (ext.getId().equals(PKIXExtensions.OCSPNonce_Id.toString())) {
186                 nonce = ext.getValue();
187             }
188         }
189 
190         OCSPResponse ocspResponse = null;
191         try {
192             byte[] response = getOCSPBytes(certIds, responderURI, extensions);
193             ocspResponse = new OCSPResponse(response);
194 
195             // verify the response
196             ocspResponse.verify(certIds, issuerInfo, responderCert, date,
197                     nonce, variant);
198         } catch (IOException ioe) {
199             throw new CertPathValidatorException(
200                 "Unable to determine revocation status due to network error",
201                 ioe, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);
202         }
203 
204         return ocspResponse;
205     }
206 
207 
208     /**
209      * Send an OCSP request, then read and return the OCSP response bytes.
210      *
211      * @param certIds the CertIds to be checked
212      * @param responderURI the URI of the OCSP responder
213      * @param extensions zero or more OCSP extensions to be included in the
214      *    request.  If no extensions are requested, an empty {@code List} must
215      *    be used.  A {@code null} value is not allowed.
216      *
217      * @return the OCSP response bytes
218      *
219      * @throws IOException if there is an exception connecting to or
220      *    communicating with the OCSP responder
221      */
222     public static byte[] getOCSPBytes(List<CertId> certIds, URI responderURI,
223             List<Extension> extensions) throws IOException {
224         OCSPRequest request = new OCSPRequest(certIds, extensions);
225         byte[] bytes = request.encodeBytes();
226 
227         InputStream in = null;
228         OutputStream out = null;
229         byte[] response = null;
230 
231         try {
232             URL url = responderURI.toURL();
233             if (debug != null) {
234                 debug.println("connecting to OCSP service at: " + url);
235             }
236 
237             Event.report("event.ocsp.check", url.toString());
238             HttpURLConnection con = (HttpURLConnection)url.openConnection();
239             con.setConnectTimeout(CONNECT_TIMEOUT);
240             con.setReadTimeout(CONNECT_TIMEOUT);
241             con.setDoOutput(true);
242             con.setDoInput(true);
243             con.setRequestMethod("POST");
244             con.setRequestProperty
245                 ("Content-type", "application/ocsp-request");
246             con.setRequestProperty
247                 ("Content-length", String.valueOf(bytes.length));
248             out = con.getOutputStream();
249             out.write(bytes);
250             out.flush();
251             // Check the response
252             if (debug != null &&
253                 con.getResponseCode() != HttpURLConnection.HTTP_OK) {
254                 debug.println("Received HTTP error: " + con.getResponseCode()
255                     + " - " + con.getResponseMessage());
256             }
257             in = con.getInputStream();
258             int contentLength = con.getContentLength();
259             if (contentLength == -1) {
260                 contentLength = Integer.MAX_VALUE;
261             }
262             response = new byte[contentLength > 2048 ? 2048 : contentLength];
263             int total = 0;
264             while (total < contentLength) {
265                 int count = in.read(response, total, response.length - total);
266                 if (count < 0)
267                     break;
268 
269                 total += count;
270                 if (total >= response.length && total < contentLength) {
271                     response = Arrays.copyOf(response, total * 2);
272                 }
273             }
274             response = Arrays.copyOf(response, total);
275         } finally {
276             if (in != null) {
277                 try {
278                     in.close();
279                 } catch (IOException ioe) {
280                     throw ioe;
281                 }
282             }
283             if (out != null) {
284                 try {
285                     out.close();
286                 } catch (IOException ioe) {
287                     throw ioe;
288                 }
289             }
290         }
291         return response;
292     }
293 
294     /**
295      * Returns the URI of the OCSP Responder as specified in the
296      * certificate's Authority Information Access extension, or null if
297      * not specified.
298      *
299      * @param cert the certificate
300      * @return the URI of the OCSP Responder, or null if not specified
301      */
302     // Called by com.sun.deploy.security.TrustDecider
303     public static URI getResponderURI(X509Certificate cert) {
304         try {
305             return getResponderURI(X509CertImpl.toImpl(cert));
306         } catch (CertificateException ce) {
307             // treat this case as if the cert had no extension
308             return null;
309         }
310     }
311 
312     static URI getResponderURI(X509CertImpl certImpl) {
313 
314         // Examine the certificate's AuthorityInfoAccess extension
315         AuthorityInfoAccessExtension aia =
316             certImpl.getAuthorityInfoAccessExtension();
317         if (aia == null) {
318             return null;
319         }
320 
321         List<AccessDescription> descriptions = aia.getAccessDescriptions();
322         for (AccessDescription description : descriptions) {
323             if (description.getAccessMethod().equals(
324                 AccessDescription.Ad_OCSP_Id)) {
325 
326                 GeneralName generalName = description.getAccessLocation();
327                 if (generalName.getType() == GeneralNameInterface.NAME_URI) {
328                     URIName uri = (URIName) generalName.getName();
329                     return uri.getURI();
330                 }
331             }
332         }
333         return null;
334     }
335 
336     /**
337      * The Revocation Status of a certificate.
338      */
339     public static interface RevocationStatus {
340         public enum CertStatus { GOOD, REVOKED, UNKNOWN };
341 
342         /**
343          * Returns the revocation status.
344          */
345         CertStatus getCertStatus();
346         /**
347          * Returns the time when the certificate was revoked, or null
348          * if it has not been revoked.
349          */
350         Date getRevocationTime();
351         /**
352          * Returns the reason the certificate was revoked, or null if it
353          * has not been revoked.
354          */
355         CRLReason getRevocationReason();
356 
357         /**
358          * Returns a Map of additional extensions.
359          */
360         Map<String, Extension> getSingleExtensions();
361     }
362 }