1 /* 2 * Copyright (c) 2003, 2012, 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 26 package sun.security.provider.certpath; 27 28 import java.io.IOException; 29 import java.math.BigInteger; 30 import java.util.*; 31 import java.security.AccessController; 32 import java.security.PrivilegedAction; 33 import java.security.Security; 34 import java.security.cert.Certificate; 35 import java.security.cert.CertificateException; 36 import java.security.cert.CertificateRevokedException; 37 import java.security.cert.CertPath; 38 import java.security.cert.CertPathValidatorException; 39 import java.security.cert.CertPathValidatorException.BasicReason; 40 import java.security.cert.CertStore; 41 import java.security.cert.CertStoreException; 42 import java.security.cert.PKIXCertPathChecker; 43 import java.security.cert.PKIXParameters; 44 import java.security.cert.TrustAnchor; 45 import java.security.cert.X509Certificate; 46 import java.security.cert.X509CertSelector; 47 import java.net.URI; 48 import java.net.URISyntaxException; 49 import javax.security.auth.x500.X500Principal; 50 51 import static sun.security.provider.certpath.OCSP.*; 52 import sun.security.util.Debug; 53 import sun.security.x509.*; 54 55 /** 56 * OCSPChecker is a <code>PKIXCertPathChecker</code> that uses the 57 * Online Certificate Status Protocol (OCSP) as specified in RFC 2560 58 * <a href="http://www.ietf.org/rfc/rfc2560.txt"> 59 * http://www.ietf.org/rfc/rfc2560.txt</a>. 60 * 61 * @author Ram Marti 62 */ 63 class OCSPChecker extends PKIXCertPathChecker { 64 65 static final String OCSP_ENABLE_PROP = "ocsp.enable"; 66 static final String OCSP_URL_PROP = "ocsp.responderURL"; 67 static final String OCSP_CERT_SUBJECT_PROP = 68 "ocsp.responderCertSubjectName"; 69 static final String OCSP_CERT_ISSUER_PROP = "ocsp.responderCertIssuerName"; 70 static final String OCSP_CERT_NUMBER_PROP = 71 "ocsp.responderCertSerialNumber"; 72 73 private static final String HEX_DIGITS = "0123456789ABCDEFabcdef"; 74 private static final Debug DEBUG = Debug.getInstance("certpath"); 75 private static final boolean dump = false; 76 77 private int remainingCerts; 78 79 private X509Certificate[] certs; 80 81 private CertPath cp; 82 83 private PKIXParameters pkixParams; 84 85 private boolean onlyEECert = false; 86 87 /** 88 * Default Constructor 89 * 90 * @param certPath the X509 certification path 91 * @param pkixParams the input PKIX parameter set 92 * @throws CertPathValidatorException if OCSPChecker can not be created 93 */ 94 OCSPChecker(CertPath certPath, PKIXParameters pkixParams) 95 throws CertPathValidatorException { 96 this(certPath, pkixParams, false); 97 } 98 99 OCSPChecker(CertPath certPath, PKIXParameters pkixParams, boolean onlyEECert) 100 throws CertPathValidatorException { 101 102 this.cp = certPath; 103 this.pkixParams = pkixParams; 104 this.onlyEECert = onlyEECert; 105 List<? extends Certificate> tmp = cp.getCertificates(); 106 certs = tmp.toArray(new X509Certificate[tmp.size()]); 107 init(false); 108 } 109 110 /** 111 * Initializes the internal state of the checker from parameters 112 * specified in the constructor 113 */ 114 @Override 115 public void init(boolean forward) throws CertPathValidatorException { 116 if (!forward) { 117 remainingCerts = certs.length + 1; 118 } else { 119 throw new CertPathValidatorException( 120 "Forward checking not supported"); 121 } 122 } 123 124 @Override public boolean isForwardCheckingSupported() { 125 return false; 126 } 127 128 @Override public Set<String> getSupportedExtensions() { 129 return Collections.<String>emptySet(); 130 } 131 132 /** 133 * Sends an OCSPRequest for the certificate to the OCSP Server and 134 * processes the response back from the OCSP Server. 135 * 136 * @param cert the Certificate 137 * @param unresolvedCritExts the unresolved critical extensions 138 * @exception CertPathValidatorException Exception is thrown if the 139 * certificate has been revoked. 140 */ 141 @Override 142 public void check(Certificate cert, Collection<String> unresolvedCritExts) 143 throws CertPathValidatorException { 144 145 // Decrement the certificate counter 146 remainingCerts--; 147 148 X509CertImpl currCertImpl = null; 149 try { 150 currCertImpl = X509CertImpl.toImpl((X509Certificate)cert); 151 } catch (CertificateException ce) { 152 throw new CertPathValidatorException(ce); 153 } 154 155 if (onlyEECert && currCertImpl.getBasicConstraints() != -1) { 156 if (DEBUG != null) { 157 DEBUG.println("Skipping revocation check, not end entity cert"); 158 } 159 return; 160 } 161 162 /* 163 * OCSP security property values, in the following order: 164 * 1. ocsp.responderURL 165 * 2. ocsp.responderCertSubjectName 166 * 3. ocsp.responderCertIssuerName 167 * 4. ocsp.responderCertSerialNumber 168 */ 169 // should cache these properties to avoid calling every time? 170 String[] properties = getOCSPProperties(); 171 172 // Check whether OCSP is feasible before seeking cert information 173 URI uri = getOCSPServerURI(currCertImpl, properties[0]); 174 175 // When responder's subject name is set then the issuer/serial 176 // properties are ignored 177 X500Principal responderSubjectName = null; 178 X500Principal responderIssuerName = null; 179 BigInteger responderSerialNumber = null; 180 if (properties[1] != null) { 181 responderSubjectName = new X500Principal(properties[1]); 182 } else if (properties[2] != null && properties[3] != null) { 183 responderIssuerName = new X500Principal(properties[2]); 184 // remove colon or space separators 185 String value = stripOutSeparators(properties[3]); 186 responderSerialNumber = new BigInteger(value, 16); 187 } else if (properties[2] != null || properties[3] != null) { 188 throw new CertPathValidatorException( 189 "Must specify both ocsp.responderCertIssuerName and " + 190 "ocsp.responderCertSerialNumber properties"); 191 } 192 193 // If the OCSP responder cert properties are set then the 194 // identified cert must be located in the trust anchors or 195 // in the cert stores. 196 boolean seekResponderCert = false; 197 if (responderSubjectName != null || responderIssuerName != null) { 198 seekResponderCert = true; 199 } 200 201 // Set the issuer certificate to the next cert in the chain 202 // (unless we're processing the final cert). 203 X509Certificate issuerCert = null; 204 boolean seekIssuerCert = true; 205 List<X509Certificate> responderCerts = new ArrayList<X509Certificate>(); 206 207 if (remainingCerts < certs.length) { 208 issuerCert = certs[remainingCerts]; 209 seekIssuerCert = false; // done 210 211 // By default, the OCSP responder's cert is the same as the 212 // issuer of the cert being validated. 213 if (!seekResponderCert) { 214 responderCerts.add(issuerCert); 215 if (DEBUG != null) { 216 DEBUG.println("Responder's certificate is the same " + 217 "as the issuer of the certificate being validated"); 218 } 219 } 220 } 221 222 // Check anchor certs for: 223 // - the issuer cert (of the cert being validated) 224 // - the OCSP responder's cert 225 if (seekIssuerCert || seekResponderCert) { 226 227 if (DEBUG != null && seekResponderCert) { 228 DEBUG.println("Searching trust anchors for issuer or " + 229 "responder certificate"); 230 } 231 232 // Extract the anchor certs 233 Iterator<TrustAnchor> anchors 234 = pkixParams.getTrustAnchors().iterator(); 235 if (!anchors.hasNext()) { 236 throw new CertPathValidatorException( 237 "Must specify at least one trust anchor"); 238 } 239 240 X500Principal certIssuerName = 241 currCertImpl.getIssuerX500Principal(); 242 byte[] certIssuerKeyId = null; 243 244 while (anchors.hasNext() && (seekIssuerCert || seekResponderCert)) { 245 246 TrustAnchor anchor = anchors.next(); 247 X509Certificate anchorCert = anchor.getTrustedCert(); 248 X500Principal anchorSubjectName = 249 anchorCert.getSubjectX500Principal(); 250 251 if (dump) { 252 System.out.println("Issuer DN is " + certIssuerName); 253 System.out.println("Subject DN is " + anchorSubjectName); 254 } 255 256 // Check if anchor cert is the issuer cert 257 if (seekIssuerCert && 258 certIssuerName.equals(anchorSubjectName)) { 259 260 // Retrieve the issuer's key identifier 261 if (certIssuerKeyId == null) { 262 certIssuerKeyId = currCertImpl.getIssuerKeyIdentifier(); 263 if (certIssuerKeyId == null) { 264 if (DEBUG != null) { 265 DEBUG.println("No issuer key identifier (AKID) " 266 + "in the certificate being validated"); 267 } 268 } 269 } 270 271 // Check that the key identifiers match, if both are present 272 byte[] anchorKeyId = null; 273 if (certIssuerKeyId != null && 274 (anchorKeyId = 275 OCSPChecker.getKeyId(anchorCert)) != null) { 276 if (!Arrays.equals(certIssuerKeyId, anchorKeyId)) { 277 continue; // try next cert 278 } 279 280 if (DEBUG != null) { 281 DEBUG.println("Issuer certificate key ID: " + 282 String.format("0x%0" + 283 (certIssuerKeyId.length * 2) + "x", 284 new BigInteger(1, certIssuerKeyId))); 285 } 286 } 287 288 issuerCert = anchorCert; 289 seekIssuerCert = false; // done 290 291 // By default, the OCSP responder's cert is the same as 292 // the issuer of the cert being validated. 293 if (!seekResponderCert && responderCerts.isEmpty()) { 294 responderCerts.add(anchorCert); 295 if (DEBUG != null) { 296 DEBUG.println("Responder's certificate is the" + 297 " same as the issuer of the certificate " + 298 "being validated"); 299 } 300 } 301 } 302 303 // Check if anchor cert is the responder cert 304 if (seekResponderCert) { 305 // Satisfy the responder subject name property only, or 306 // satisfy the responder issuer name and serial number 307 // properties only 308 if ((responderSubjectName != null && 309 responderSubjectName.equals(anchorSubjectName)) || 310 (responderIssuerName != null && 311 responderSerialNumber != null && 312 responderIssuerName.equals( 313 anchorCert.getIssuerX500Principal()) && 314 responderSerialNumber.equals( 315 anchorCert.getSerialNumber()))) { 316 317 responderCerts.add(anchorCert); 318 } 319 } 320 } 321 if (issuerCert == null) { 322 throw new CertPathValidatorException( 323 "No trusted certificate for " + currCertImpl.getIssuerDN()); 324 } 325 326 // Check cert stores if responder cert has not yet been found 327 if (seekResponderCert) { 328 if (DEBUG != null) { 329 DEBUG.println("Searching cert stores for responder's " + 330 "certificate"); 331 } 332 X509CertSelector filter = null; 333 if (responderSubjectName != null) { 334 filter = new X509CertSelector(); 335 filter.setSubject(responderSubjectName); 336 } else if (responderIssuerName != null && 337 responderSerialNumber != null) { 338 filter = new X509CertSelector(); 339 filter.setIssuer(responderIssuerName); 340 filter.setSerialNumber(responderSerialNumber); 341 } 342 if (filter != null) { 343 List<CertStore> certStores = pkixParams.getCertStores(); 344 for (CertStore certStore : certStores) { 345 try { 346 responderCerts.addAll( 347 (Collection<X509Certificate>) 348 certStore.getCertificates(filter)); 349 } catch (CertStoreException cse) { 350 // ignore and try next certStore 351 if (DEBUG != null) { 352 DEBUG.println("CertStore exception:" + cse); 353 } 354 continue; 355 } 356 } 357 } 358 } 359 } 360 361 // Could not find the certificate identified in the OCSP properties 362 if (seekResponderCert && responderCerts.isEmpty()) { 363 throw new CertPathValidatorException( 364 "Cannot find the responder's certificate " + 365 "(set using the OCSP security properties)."); 366 } 367 368 if (DEBUG != null) { 369 DEBUG.println("Located " + responderCerts.size() + 370 " trusted responder certificate(s)"); 371 } 372 373 // The algorithm constraints of the OCSP trusted responder certificate 374 // does not need to be checked in this code. The constraints will be 375 // checked when the responder's certificate is validated. 376 377 CertId certId = null; 378 OCSPResponse response = null; 379 try { 380 certId = new CertId 381 (issuerCert, currCertImpl.getSerialNumberObject()); 382 response = OCSP.check(Collections.singletonList(certId), uri, 383 responderCerts, pkixParams.getDate()); 384 } catch (Exception e) { 385 if (e instanceof CertPathValidatorException) { 386 throw (CertPathValidatorException) e; 387 } else { 388 // Wrap exceptions in CertPathValidatorException so that 389 // we can fallback to CRLs, if enabled. 390 throw new CertPathValidatorException(e); 391 } 392 } 393 394 RevocationStatus rs = (RevocationStatus) response.getSingleResponse(certId); 395 RevocationStatus.CertStatus certStatus = rs.getCertStatus(); 396 if (certStatus == RevocationStatus.CertStatus.REVOKED) { 397 Throwable t = new CertificateRevokedException( 398 rs.getRevocationTime(), rs.getRevocationReason(), 399 responderCerts.get(0).getSubjectX500Principal(), 400 rs.getSingleExtensions()); 401 throw new CertPathValidatorException(t.getMessage(), t, 402 null, -1, BasicReason.REVOKED); 403 } else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) { 404 throw new CertPathValidatorException( 405 "Certificate's revocation status is unknown", null, cp, 406 (remainingCerts - 1), 407 BasicReason.UNDETERMINED_REVOCATION_STATUS); 408 } 409 } 410 411 /* 412 * The OCSP security property values are in the following order: 413 * 1. ocsp.responderURL 414 * 2. ocsp.responderCertSubjectName 415 * 3. ocsp.responderCertIssuerName 416 * 4. ocsp.responderCertSerialNumber 417 */ 418 private static URI getOCSPServerURI(X509CertImpl currCertImpl, 419 String responderURL) throws CertPathValidatorException { 420 421 if (responderURL != null) { 422 try { 423 return new URI(responderURL); 424 } catch (URISyntaxException e) { 425 throw new CertPathValidatorException(e); 426 } 427 } 428 429 // Examine the certificate's AuthorityInfoAccess extension 430 AuthorityInfoAccessExtension aia = 431 currCertImpl.getAuthorityInfoAccessExtension(); 432 if (aia == null) { 433 throw new CertPathValidatorException( 434 "Must specify the location of an OCSP Responder"); 435 } 436 437 List<AccessDescription> descriptions = aia.getAccessDescriptions(); 438 for (AccessDescription description : descriptions) { 439 if (description.getAccessMethod().equals((Object) 440 AccessDescription.Ad_OCSP_Id)) { 441 442 GeneralName generalName = description.getAccessLocation(); 443 if (generalName.getType() == GeneralNameInterface.NAME_URI) { 444 URIName uri = (URIName) generalName.getName(); 445 return uri.getURI(); 446 } 447 } 448 } 449 450 throw new CertPathValidatorException( 451 "Cannot find the location of the OCSP Responder"); 452 } 453 454 /* 455 * Retrieves the values of the OCSP security properties. 456 */ 457 private static String[] getOCSPProperties() { 458 final String[] properties = new String[4]; 459 460 AccessController.doPrivileged( 461 new PrivilegedAction<Void>() { 462 public Void run() { 463 properties[0] = Security.getProperty(OCSP_URL_PROP); 464 properties[1] = 465 Security.getProperty(OCSP_CERT_SUBJECT_PROP); 466 properties[2] = 467 Security.getProperty(OCSP_CERT_ISSUER_PROP); 468 properties[3] = 469 Security.getProperty(OCSP_CERT_NUMBER_PROP); 470 return null; 471 } 472 }); 473 474 return properties; 475 } 476 477 /* 478 * Removes any non-hexadecimal characters from a string. 479 */ 480 private static String stripOutSeparators(String value) { 481 char[] chars = value.toCharArray(); 482 StringBuilder hexNumber = new StringBuilder(); 483 for (int i = 0; i < chars.length; i++) { 484 if (HEX_DIGITS.indexOf(chars[i]) != -1) { 485 hexNumber.append(chars[i]); 486 } 487 } 488 return hexNumber.toString(); 489 } 490 491 /* 492 * Returns the subject key identifier for the supplied certificate, or null 493 */ 494 static byte[] getKeyId(X509Certificate cert) { 495 X509CertImpl certImpl = null; 496 byte[] certSubjectKeyId = null; 497 498 try { 499 certImpl = X509CertImpl.toImpl(cert); 500 certSubjectKeyId = certImpl.getSubjectKeyIdentifier(); 501 502 if (certSubjectKeyId == null) { 503 if (DEBUG != null) { 504 DEBUG.println("No subject key identifier (SKID) in the " + 505 "certificate (Subject: " + 506 cert.getSubjectX500Principal() + ")"); 507 } 508 } 509 510 } catch (CertificateException e) { 511 // Ignore certificate 512 if (DEBUG != null) { 513 DEBUG.println("Error parsing X.509 certificate (Subject: " + 514 cert.getSubjectX500Principal() + ") " + e); 515 } 516 } 517 518 return certSubjectKeyId; 519 } 520 }