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