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