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