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 }