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