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