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