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      */
initializeTimeout()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 
OCSP()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      */
check(X509Certificate cert, X509Certificate issuerCert)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      */
check(X509Certificate cert, X509Certificate issuerCert, URI responderURI, X509Certificate responderCert, Date date)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
check(X509Certificate cert, X509Certificate issuerCert, URI responderURI, X509Certificate responderCert, Date date, List<Extension> extensions)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      */
check(List<CertId> certIds, URI responderURI, X509Certificate issuerCert, X509Certificate responderCert, Date date, List<Extension> extensions)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
getResponderURI(X509Certificate cert)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 
getResponderURI(X509CertImpl certImpl)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          */
getCertStatus()347         CertStatus getCertStatus();
348         /**
349          * Returns the time when the certificate was revoked, or null
350          * if it has not been revoked.
351          */
getRevocationTime()352         Date getRevocationTime();
353         /**
354          * Returns the reason the certificate was revoked, or null if it
355          * has not been revoked.
356          */
getRevocationReason()357         CRLReason getRevocationReason();
358 
359         /**
360          * Returns a Map of additional extensions.
361          */
getSingleExtensions()362         Map<String, Extension> getSingleExtensions();
363     }
364 }
365