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