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 }