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