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