Path: blob/master/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java
41161 views
/*1* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.provider.certpath;2627import java.io.IOException;28import java.math.BigInteger;29import java.net.URI;30import java.net.URISyntaxException;31import java.security.AccessController;32import java.security.InvalidAlgorithmParameterException;33import java.security.NoSuchAlgorithmException;34import java.security.PrivilegedAction;35import java.security.PublicKey;36import java.security.Security;37import java.security.cert.CertPathValidatorException.BasicReason;38import java.security.cert.Extension;39import java.security.cert.*;40import java.util.*;41import javax.security.auth.x500.X500Principal;4243import static sun.security.provider.certpath.OCSP.*;44import static sun.security.provider.certpath.PKIX.*;45import sun.security.x509.*;46import static sun.security.x509.PKIXExtensions.*;47import sun.security.util.Debug;48import sun.security.util.KnownOIDs;4950class RevocationChecker extends PKIXRevocationChecker {5152private static final Debug debug = Debug.getInstance("certpath");5354private TrustAnchor anchor;55private ValidatorParams params;56private boolean onlyEE;57private boolean softFail;58private boolean crlDP;59private URI responderURI;60private X509Certificate responderCert;61private List<CertStore> certStores;62private Map<X509Certificate, byte[]> ocspResponses;63private List<Extension> ocspExtensions;64private final boolean legacy;65private LinkedList<CertPathValidatorException> softFailExceptions =66new LinkedList<>();6768// state variables69private OCSPResponse.IssuerInfo issuerInfo;70private PublicKey prevPubKey;71private boolean crlSignFlag;72private int certIndex;7374private enum Mode { PREFER_OCSP, PREFER_CRLS, ONLY_CRLS, ONLY_OCSP };75private Mode mode = Mode.PREFER_OCSP;7677private static class RevocationProperties {78boolean onlyEE;79boolean ocspEnabled;80boolean crlDPEnabled;81String ocspUrl;82String ocspSubject;83String ocspIssuer;84String ocspSerial;85boolean ocspNonce;86}87private RevocationProperties rp;88private static final int DEFAULT_NONCE_BYTES = 16;8990RevocationChecker() {91legacy = false;92}9394RevocationChecker(TrustAnchor anchor, ValidatorParams params)95throws CertPathValidatorException96{97legacy = true;98init(anchor, params);99}100101void init(TrustAnchor anchor, ValidatorParams params)102throws CertPathValidatorException103{104rp = getRevocationProperties();105URI uri = getOcspResponder();106responderURI = (uri == null) ? toURI(rp.ocspUrl) : uri;107X509Certificate cert = getOcspResponderCert();108responderCert = (cert == null)109? getResponderCert(rp, params.trustAnchors(),110params.certStores())111: cert;112Set<Option> options = getOptions();113for (Option option : options) {114switch (option) {115case ONLY_END_ENTITY:116case PREFER_CRLS:117case SOFT_FAIL:118case NO_FALLBACK:119break;120default:121throw new CertPathValidatorException(122"Unrecognized revocation parameter option: " + option);123}124}125softFail = options.contains(Option.SOFT_FAIL);126127// set mode, only end entity flag128if (legacy) {129mode = (rp.ocspEnabled) ? Mode.PREFER_OCSP : Mode.ONLY_CRLS;130onlyEE = rp.onlyEE;131} else {132if (options.contains(Option.NO_FALLBACK)) {133if (options.contains(Option.PREFER_CRLS)) {134mode = Mode.ONLY_CRLS;135} else {136mode = Mode.ONLY_OCSP;137}138} else if (options.contains(Option.PREFER_CRLS)) {139mode = Mode.PREFER_CRLS;140}141onlyEE = options.contains(Option.ONLY_END_ENTITY);142}143if (legacy) {144crlDP = rp.crlDPEnabled;145} else {146crlDP = true;147}148ocspResponses = getOcspResponses();149ocspExtensions = getOcspExtensions();150151this.anchor = anchor;152this.params = params;153this.certStores = new ArrayList<>(params.certStores());154try {155this.certStores.add(CertStore.getInstance("Collection",156new CollectionCertStoreParameters(params.certificates())));157} catch (InvalidAlgorithmParameterException |158NoSuchAlgorithmException e) {159// should never occur but not necessarily fatal, so log it,160// ignore and continue161if (debug != null) {162debug.println("RevocationChecker: " +163"error creating Collection CertStore: " + e);164}165}166}167168private static URI toURI(String uriString)169throws CertPathValidatorException170{171try {172if (uriString != null) {173return new URI(uriString);174}175return null;176} catch (URISyntaxException e) {177throw new CertPathValidatorException(178"cannot parse ocsp.responderURL property", e);179}180}181182@SuppressWarnings("removal")183private static RevocationProperties getRevocationProperties() {184return AccessController.doPrivileged(185new PrivilegedAction<RevocationProperties>() {186public RevocationProperties run() {187RevocationProperties rp = new RevocationProperties();188String onlyEE = Security.getProperty(189"com.sun.security.onlyCheckRevocationOfEECert");190rp.onlyEE = onlyEE != null191&& onlyEE.equalsIgnoreCase("true");192String ocspEnabled = Security.getProperty("ocsp.enable");193rp.ocspEnabled = ocspEnabled != null194&& ocspEnabled.equalsIgnoreCase("true");195rp.ocspUrl = Security.getProperty("ocsp.responderURL");196rp.ocspSubject197= Security.getProperty("ocsp.responderCertSubjectName");198rp.ocspIssuer199= Security.getProperty("ocsp.responderCertIssuerName");200rp.ocspSerial201= Security.getProperty("ocsp.responderCertSerialNumber");202rp.crlDPEnabled203= Boolean.getBoolean("com.sun.security.enableCRLDP");204rp.ocspNonce205= Boolean.getBoolean("jdk.security.certpath.ocspNonce");206return rp;207}208}209);210}211212private static X509Certificate getResponderCert(RevocationProperties rp,213Set<TrustAnchor> anchors,214List<CertStore> stores)215throws CertPathValidatorException216{217if (rp.ocspSubject != null) {218return getResponderCert(rp.ocspSubject, anchors, stores);219} else if (rp.ocspIssuer != null && rp.ocspSerial != null) {220return getResponderCert(rp.ocspIssuer, rp.ocspSerial,221anchors, stores);222} else if (rp.ocspIssuer != null || rp.ocspSerial != null) {223throw new CertPathValidatorException(224"Must specify both ocsp.responderCertIssuerName and " +225"ocsp.responderCertSerialNumber properties");226}227return null;228}229230private static X509Certificate getResponderCert(String subject,231Set<TrustAnchor> anchors,232List<CertStore> stores)233throws CertPathValidatorException234{235X509CertSelector sel = new X509CertSelector();236try {237sel.setSubject(new X500Principal(subject));238} catch (IllegalArgumentException e) {239throw new CertPathValidatorException(240"cannot parse ocsp.responderCertSubjectName property", e);241}242return getResponderCert(sel, anchors, stores);243}244245private static X509Certificate getResponderCert(String issuer,246String serial,247Set<TrustAnchor> anchors,248List<CertStore> stores)249throws CertPathValidatorException250{251X509CertSelector sel = new X509CertSelector();252try {253sel.setIssuer(new X500Principal(issuer));254} catch (IllegalArgumentException e) {255throw new CertPathValidatorException(256"cannot parse ocsp.responderCertIssuerName property", e);257}258try {259sel.setSerialNumber(new BigInteger(stripOutSeparators(serial), 16));260} catch (NumberFormatException e) {261throw new CertPathValidatorException(262"cannot parse ocsp.responderCertSerialNumber property", e);263}264return getResponderCert(sel, anchors, stores);265}266267private static X509Certificate getResponderCert(X509CertSelector sel,268Set<TrustAnchor> anchors,269List<CertStore> stores)270throws CertPathValidatorException271{272// first check TrustAnchors273for (TrustAnchor anchor : anchors) {274X509Certificate cert = anchor.getTrustedCert();275if (cert == null) {276continue;277}278if (sel.match(cert)) {279return cert;280}281}282// now check CertStores283for (CertStore store : stores) {284try {285Collection<? extends Certificate> certs =286store.getCertificates(sel);287if (!certs.isEmpty()) {288return (X509Certificate)certs.iterator().next();289}290} catch (CertStoreException e) {291// ignore and try next CertStore292if (debug != null) {293debug.println("CertStore exception:" + e);294}295continue;296}297}298throw new CertPathValidatorException(299"Cannot find the responder's certificate " +300"(set using the OCSP security properties).");301}302303@Override304public void init(boolean forward) throws CertPathValidatorException {305if (forward) {306throw new307CertPathValidatorException("forward checking not supported");308}309if (anchor != null) {310issuerInfo = new OCSPResponse.IssuerInfo(anchor);311prevPubKey = issuerInfo.getPublicKey();312313}314crlSignFlag = true;315if (params != null && params.certPath() != null) {316certIndex = params.certPath().getCertificates().size() - 1;317} else {318certIndex = -1;319}320softFailExceptions.clear();321}322323@Override324public boolean isForwardCheckingSupported() {325return false;326}327328@Override329public Set<String> getSupportedExtensions() {330return null;331}332333@Override334public List<CertPathValidatorException> getSoftFailExceptions() {335return Collections.unmodifiableList(softFailExceptions);336}337338@Override339public void check(Certificate cert, Collection<String> unresolvedCritExts)340throws CertPathValidatorException341{342check((X509Certificate)cert, unresolvedCritExts,343prevPubKey, crlSignFlag);344}345346private void check(X509Certificate xcert,347Collection<String> unresolvedCritExts,348PublicKey pubKey, boolean crlSignFlag)349throws CertPathValidatorException350{351if (debug != null) {352debug.println("RevocationChecker.check: checking cert" +353"\n SN: " + Debug.toHexString(xcert.getSerialNumber()) +354"\n Subject: " + xcert.getSubjectX500Principal() +355"\n Issuer: " + xcert.getIssuerX500Principal());356}357try {358if (onlyEE && xcert.getBasicConstraints() != -1) {359if (debug != null) {360debug.println("Skipping revocation check; cert is not " +361"an end entity cert");362}363return;364}365switch (mode) {366case PREFER_OCSP:367case ONLY_OCSP:368checkOCSP(xcert, unresolvedCritExts);369break;370case PREFER_CRLS:371case ONLY_CRLS:372checkCRLs(xcert, unresolvedCritExts, null,373pubKey, crlSignFlag);374break;375}376} catch (CertPathValidatorException e) {377if (e.getReason() == BasicReason.REVOKED) {378throw e;379}380boolean eSoftFail = isSoftFailException(e);381if (eSoftFail) {382if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) {383return;384}385} else {386if (mode == Mode.ONLY_OCSP || mode == Mode.ONLY_CRLS) {387throw e;388}389}390CertPathValidatorException cause = e;391// Otherwise, failover392if (debug != null) {393debug.println("RevocationChecker.check() " + e.getMessage());394debug.println("RevocationChecker.check() preparing to failover");395}396try {397switch (mode) {398case PREFER_OCSP:399checkCRLs(xcert, unresolvedCritExts, null,400pubKey, crlSignFlag);401break;402case PREFER_CRLS:403checkOCSP(xcert, unresolvedCritExts);404break;405}406} catch (CertPathValidatorException x) {407if (debug != null) {408debug.println("RevocationChecker.check() failover failed");409debug.println("RevocationChecker.check() " + x.getMessage());410}411if (x.getReason() == BasicReason.REVOKED) {412throw x;413}414if (!isSoftFailException(x)) {415cause.addSuppressed(x);416throw cause;417} else {418// only pass if both exceptions were soft failures419if (!eSoftFail) {420throw cause;421}422}423}424} finally {425updateState(xcert);426}427}428429private boolean isSoftFailException(CertPathValidatorException e) {430if (softFail &&431e.getReason() == BasicReason.UNDETERMINED_REVOCATION_STATUS)432{433// recreate exception with correct index434CertPathValidatorException e2 = new CertPathValidatorException(435e.getMessage(), e.getCause(), params.certPath(), certIndex,436e.getReason());437softFailExceptions.addFirst(e2);438return true;439}440return false;441}442443private void updateState(X509Certificate cert)444throws CertPathValidatorException445{446issuerInfo = new OCSPResponse.IssuerInfo(anchor, cert);447448// Make new public key if parameters are missing449PublicKey pubKey = cert.getPublicKey();450if (PKIX.isDSAPublicKeyWithoutParams(pubKey)) {451// pubKey needs to inherit DSA parameters from prev key452pubKey = BasicChecker.makeInheritedParamsKey(pubKey, prevPubKey);453}454prevPubKey = pubKey;455crlSignFlag = certCanSignCrl(cert);456if (certIndex > 0) {457certIndex--;458}459}460461// Maximum clock skew in milliseconds (15 minutes) allowed when checking462// validity of CRLs463private static final long MAX_CLOCK_SKEW = 900000;464private void checkCRLs(X509Certificate cert,465Collection<String> unresolvedCritExts,466Set<X509Certificate> stackedCerts,467PublicKey pubKey, boolean signFlag)468throws CertPathValidatorException469{470checkCRLs(cert, pubKey, null, signFlag, true,471stackedCerts, params.trustAnchors());472}473474static boolean isCausedByNetworkIssue(String type, CertStoreException cse) {475boolean result;476Throwable t = cse.getCause();477478switch (type) {479case "LDAP":480if (t != null) {481// These two exception classes are inside java.naming module482String cn = t.getClass().getName();483result = (cn.equals("javax.naming.ServiceUnavailableException") ||484cn.equals("javax.naming.CommunicationException"));485} else {486result = false;487}488break;489case "SSLServer":490result = (t != null && t instanceof IOException);491break;492case "URI":493result = (t != null && t instanceof IOException);494break;495default:496// we don't know about any other remote CertStore types497return false;498}499return result;500}501502private void checkCRLs(X509Certificate cert, PublicKey prevKey,503X509Certificate prevCert, boolean signFlag,504boolean allowSeparateKey,505Set<X509Certificate> stackedCerts,506Set<TrustAnchor> anchors)507throws CertPathValidatorException508{509if (debug != null) {510debug.println("RevocationChecker.checkCRLs()" +511" ---checking revocation status ...");512}513514// Reject circular dependencies - RFC 5280 is not explicit on how515// to handle this, but does suggest that they can be a security516// risk and can create unresolvable dependencies517if (stackedCerts != null && stackedCerts.contains(cert)) {518if (debug != null) {519debug.println("RevocationChecker.checkCRLs()" +520" circular dependency");521}522throw new CertPathValidatorException523("Could not determine revocation status", null, null, -1,524BasicReason.UNDETERMINED_REVOCATION_STATUS);525}526527Set<X509CRL> possibleCRLs = new HashSet<>();528Set<X509CRL> approvedCRLs = new HashSet<>();529X509CRLSelector sel = new X509CRLSelector();530sel.setCertificateChecking(cert);531CertPathHelper.setDateAndTime(sel, params.date(), MAX_CLOCK_SKEW);532533// First, check user-specified CertStores534CertPathValidatorException networkFailureException = null;535for (CertStore store : certStores) {536try {537for (CRL crl : store.getCRLs(sel)) {538possibleCRLs.add((X509CRL)crl);539}540} catch (CertStoreException e) {541if (debug != null) {542debug.println("RevocationChecker.checkCRLs() " +543"CertStoreException: " + e.getMessage());544}545if (networkFailureException == null &&546isCausedByNetworkIssue(store.getType(),e)) {547// save this exception, we may need to throw it later548networkFailureException = new CertPathValidatorException(549"Unable to determine revocation status due to " +550"network error", e, null, -1,551BasicReason.UNDETERMINED_REVOCATION_STATUS);552}553}554}555556if (debug != null) {557debug.println("RevocationChecker.checkCRLs() " +558"possible crls.size() = " + possibleCRLs.size());559}560boolean[] reasonsMask = new boolean[9];561if (!possibleCRLs.isEmpty()) {562// Now that we have a list of possible CRLs, see which ones can563// be approved564approvedCRLs.addAll(verifyPossibleCRLs(possibleCRLs, cert, prevKey,565signFlag, reasonsMask,566anchors));567}568569if (debug != null) {570debug.println("RevocationChecker.checkCRLs() " +571"approved crls.size() = " + approvedCRLs.size());572}573574// make sure that we have at least one CRL that _could_ cover575// the certificate in question and all reasons are covered576if (!approvedCRLs.isEmpty() &&577Arrays.equals(reasonsMask, ALL_REASONS))578{579checkApprovedCRLs(cert, approvedCRLs);580} else {581// Check Distribution Points582// all CRLs returned by the DP Fetcher have also been verified583try {584if (crlDP) {585approvedCRLs.addAll(DistributionPointFetcher.getCRLs(586sel, signFlag, prevKey, prevCert,587params.sigProvider(), certStores,588reasonsMask, anchors, null,589params.variant(), anchor));590}591} catch (CertStoreException e) {592if (e instanceof CertStoreTypeException) {593CertStoreTypeException cste = (CertStoreTypeException)e;594if (isCausedByNetworkIssue(cste.getType(), e)) {595throw new CertPathValidatorException(596"Unable to determine revocation status due to " +597"network error", e, null, -1,598BasicReason.UNDETERMINED_REVOCATION_STATUS);599}600}601throw new CertPathValidatorException(e);602}603if (!approvedCRLs.isEmpty() &&604Arrays.equals(reasonsMask, ALL_REASONS))605{606checkApprovedCRLs(cert, approvedCRLs);607} else {608if (allowSeparateKey) {609try {610verifyWithSeparateSigningKey(cert, prevKey, signFlag,611stackedCerts);612return;613} catch (CertPathValidatorException cpve) {614if (networkFailureException != null) {615// if a network issue previously prevented us from616// retrieving a CRL from one of the user-specified617// CertStores, throw it now so it can be handled618// appropriately619throw networkFailureException;620}621throw cpve;622}623} else {624if (networkFailureException != null) {625// if a network issue previously prevented us from626// retrieving a CRL from one of the user-specified627// CertStores, throw it now so it can be handled628// appropriately629throw networkFailureException;630}631throw new CertPathValidatorException(632"Could not determine revocation status", null, null, -1,633BasicReason.UNDETERMINED_REVOCATION_STATUS);634}635}636}637}638639private void checkApprovedCRLs(X509Certificate cert,640Set<X509CRL> approvedCRLs)641throws CertPathValidatorException642{643// See if the cert is in the set of approved crls.644if (debug != null) {645BigInteger sn = cert.getSerialNumber();646debug.println("RevocationChecker.checkApprovedCRLs() " +647"starting the final sweep...");648debug.println("RevocationChecker.checkApprovedCRLs()" +649" cert SN: " + sn.toString());650}651652CRLReason reasonCode = CRLReason.UNSPECIFIED;653X509CRLEntryImpl entry = null;654for (X509CRL crl : approvedCRLs) {655X509CRLEntry e = crl.getRevokedCertificate(cert);656if (e != null) {657try {658entry = X509CRLEntryImpl.toImpl(e);659} catch (CRLException ce) {660throw new CertPathValidatorException(ce);661}662if (debug != null) {663debug.println("RevocationChecker.checkApprovedCRLs()"664+ " CRL entry: " + entry.toString());665}666667/*668* Abort CRL validation and throw exception if there are any669* unrecognized critical CRL entry extensions (see section670* 5.3 of RFC 5280).671*/672Set<String> unresCritExts = entry.getCriticalExtensionOIDs();673if (unresCritExts != null && !unresCritExts.isEmpty()) {674/* remove any that we will process */675unresCritExts.remove(ReasonCode_Id.toString());676unresCritExts.remove(CertificateIssuer_Id.toString());677if (!unresCritExts.isEmpty()) {678throw new CertPathValidatorException(679"Unrecognized critical extension(s) in revoked " +680"CRL entry");681}682}683684reasonCode = entry.getRevocationReason();685if (reasonCode == null) {686reasonCode = CRLReason.UNSPECIFIED;687}688Date revocationDate = entry.getRevocationDate();689if (revocationDate.before(params.date())) {690Throwable t = new CertificateRevokedException(691revocationDate, reasonCode,692crl.getIssuerX500Principal(), entry.getExtensions());693throw new CertPathValidatorException(694t.getMessage(), t, null, -1, BasicReason.REVOKED);695}696}697}698}699700private void checkOCSP(X509Certificate cert,701Collection<String> unresolvedCritExts)702throws CertPathValidatorException703{704X509CertImpl currCert = null;705try {706currCert = X509CertImpl.toImpl(cert);707} catch (CertificateException ce) {708throw new CertPathValidatorException(ce);709}710711// The algorithm constraints of the OCSP trusted responder certificate712// does not need to be checked in this code. The constraints will be713// checked when the responder's certificate is validated.714715OCSPResponse response = null;716CertId certId = null;717try {718certId = new CertId(issuerInfo.getName(), issuerInfo.getPublicKey(),719currCert.getSerialNumberObject());720721byte[] nonce = null;722for (Extension ext : ocspExtensions) {723if (ext.getId().equals(KnownOIDs.OCSPNonceExt.value())) {724nonce = ext.getValue();725}726}727728// check if there is a cached OCSP response available729byte[] responseBytes = ocspResponses.get(cert);730if (responseBytes != null) {731if (debug != null) {732debug.println("Found cached OCSP response");733}734response = new OCSPResponse(responseBytes);735736// verify the response737response.verify(Collections.singletonList(certId), issuerInfo,738responderCert, params.date(), nonce, params.variant());739740} else {741URI responderURI = (this.responderURI != null)742? this.responderURI743: OCSP.getResponderURI(currCert);744if (responderURI == null) {745throw new CertPathValidatorException(746"Certificate does not specify OCSP responder", null,747null, -1);748}749750List<Extension> tmpExtensions = null;751if (rp.ocspNonce) {752if (nonce == null) {753try {754// create the 16-byte nonce by default755Extension nonceExt = new OCSPNonceExtension(DEFAULT_NONCE_BYTES);756757if (ocspExtensions.size() > 0) {758tmpExtensions = new ArrayList<Extension>(ocspExtensions);759tmpExtensions.add(nonceExt);760} else {761tmpExtensions = List.of(nonceExt);762}763764if (debug != null) {765debug.println("Default nonce has been created in the OCSP extensions");766}767} catch (IOException e) {768throw new CertPathValidatorException("Failed to create the default nonce " +769"in OCSP extensions", e);770}771} else {772throw new CertPathValidatorException("Application provided nonce cannot be " +773"used if the value of the jdk.security.certpath.ocspNonce system " +774"property is true");775}776} else {777if (nonce != null) {778if (debug != null) {779debug.println("Using application provided nonce");780}781}782}783784response = OCSP.check(Collections.singletonList(certId),785responderURI, issuerInfo, responderCert, null,786rp.ocspNonce ? tmpExtensions : ocspExtensions, params.variant());787}788} catch (IOException e) {789throw new CertPathValidatorException(790"Unable to determine revocation status due to network error",791e, null, -1, BasicReason.UNDETERMINED_REVOCATION_STATUS);792}793794RevocationStatus rs =795(RevocationStatus)response.getSingleResponse(certId);796RevocationStatus.CertStatus certStatus = rs.getCertStatus();797if (certStatus == RevocationStatus.CertStatus.REVOKED) {798Date revocationTime = rs.getRevocationTime();799if (revocationTime.before(params.date())) {800Throwable t = new CertificateRevokedException(801revocationTime, rs.getRevocationReason(),802response.getSignerCertificate().getSubjectX500Principal(),803rs.getSingleExtensions());804throw new CertPathValidatorException(t.getMessage(), t, null,805-1, BasicReason.REVOKED);806}807} else if (certStatus == RevocationStatus.CertStatus.UNKNOWN) {808throw new CertPathValidatorException(809"Certificate's revocation status is unknown", null,810params.certPath(), -1,811BasicReason.UNDETERMINED_REVOCATION_STATUS);812}813}814815/*816* Removes any non-hexadecimal characters from a string.817*/818private static String stripOutSeparators(String value) {819char[] chars = value.toCharArray();820StringBuilder hexNumber = new StringBuilder();821for (int i = 0; i < chars.length; i++) {822if (HexFormat.isHexDigit(chars[i])) {823hexNumber.append(chars[i]);824}825}826return hexNumber.toString();827}828829/**830* Checks that a cert can be used to verify a CRL.831*832* @param cert an X509Certificate to check833* @return a boolean specifying if the cert is allowed to vouch for the834* validity of a CRL835*/836static boolean certCanSignCrl(X509Certificate cert) {837// if the cert doesn't include the key usage ext, or838// the key usage ext asserts cRLSigning, return true,839// otherwise return false.840boolean[] keyUsage = cert.getKeyUsage();841if (keyUsage != null) {842return keyUsage[6];843}844return false;845}846847/**848* Internal method that verifies a set of possible_crls,849* and sees if each is approved, based on the cert.850*851* @param crls a set of possible CRLs to test for acceptability852* @param cert the certificate whose revocation status is being checked853* @param signFlag <code>true</code> if prevKey was trusted to sign CRLs854* @param prevKey the public key of the issuer of cert855* @param reasonsMask the reason code mask856* @param trustAnchors a <code>Set</code> of <code>TrustAnchor</code>s>857* @return a collection of approved crls (or an empty collection)858*/859private static final boolean[] ALL_REASONS =860{true, true, true, true, true, true, true, true, true};861private Collection<X509CRL> verifyPossibleCRLs(Set<X509CRL> crls,862X509Certificate cert,863PublicKey prevKey,864boolean signFlag,865boolean[] reasonsMask,866Set<TrustAnchor> anchors)867throws CertPathValidatorException868{869try {870X509CertImpl certImpl = X509CertImpl.toImpl(cert);871if (debug != null) {872debug.println("RevocationChecker.verifyPossibleCRLs: " +873"Checking CRLDPs for "874+ certImpl.getSubjectX500Principal());875}876CRLDistributionPointsExtension ext =877certImpl.getCRLDistributionPointsExtension();878List<DistributionPoint> points = null;879if (ext == null) {880// assume a DP with reasons and CRLIssuer fields omitted881// and a DP name of the cert issuer.882// TODO add issuerAltName too883X500Name certIssuer = (X500Name)certImpl.getIssuerDN();884DistributionPoint point = new DistributionPoint(885new GeneralNames().add(new GeneralName(certIssuer)),886null, null);887points = Collections.singletonList(point);888} else {889points = ext.get(CRLDistributionPointsExtension.POINTS);890}891Set<X509CRL> results = new HashSet<>();892for (DistributionPoint point : points) {893for (X509CRL crl : crls) {894if (DistributionPointFetcher.verifyCRL(895certImpl, point, crl, reasonsMask, signFlag,896prevKey, null, params.sigProvider(), anchors,897certStores, params.date(), params.variant(), anchor))898{899results.add(crl);900}901}902if (Arrays.equals(reasonsMask, ALL_REASONS))903break;904}905return results;906} catch (CertificateException | CRLException | IOException e) {907if (debug != null) {908debug.println("Exception while verifying CRL: "+e.getMessage());909e.printStackTrace();910}911return Collections.emptySet();912}913}914915/**916* We have a cert whose revocation status couldn't be verified by917* a CRL issued by the cert that issued the CRL. See if we can918* find a valid CRL issued by a separate key that can verify the919* revocation status of this certificate.920* <p>921* Note that this does not provide support for indirect CRLs,922* only CRLs signed with a different key (but the same issuer923* name) as the certificate being checked.924*925* @param cert the <code>X509Certificate</code> to be checked926* @param prevKey the <code>PublicKey</code> that failed927* @param signFlag <code>true</code> if that key was trusted to sign CRLs928* @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s929* whose revocation status depends on the930* non-revoked status of this cert. To avoid931* circular dependencies, we assume they're932* revoked while checking the revocation933* status of this cert.934* @throws CertPathValidatorException if the cert's revocation status935* cannot be verified successfully with another key936*/937private void verifyWithSeparateSigningKey(X509Certificate cert,938PublicKey prevKey,939boolean signFlag,940Set<X509Certificate> stackedCerts)941throws CertPathValidatorException942{943String msg = "revocation status";944if (debug != null) {945debug.println(946"RevocationChecker.verifyWithSeparateSigningKey()" +947" ---checking " + msg + "...");948}949950// Reject circular dependencies - RFC 5280 is not explicit on how951// to handle this, but does suggest that they can be a security952// risk and can create unresolvable dependencies953if ((stackedCerts != null) && stackedCerts.contains(cert)) {954if (debug != null) {955debug.println(956"RevocationChecker.verifyWithSeparateSigningKey()" +957" circular dependency");958}959throw new CertPathValidatorException960("Could not determine revocation status", null, null, -1,961BasicReason.UNDETERMINED_REVOCATION_STATUS);962}963964// Try to find another key that might be able to sign965// CRLs vouching for this cert.966// If prevKey wasn't trusted, maybe we just didn't have the right967// path to it. Don't rule that key out.968if (!signFlag) {969buildToNewKey(cert, null, stackedCerts);970} else {971buildToNewKey(cert, prevKey, stackedCerts);972}973}974975/**976* Tries to find a CertPath that establishes a key that can be977* used to verify the revocation status of a given certificate.978* Ignores keys that have previously been tried. Throws a979* CertPathValidatorException if no such key could be found.980*981* @param currCert the <code>X509Certificate</code> to be checked982* @param prevKey the <code>PublicKey</code> of the certificate whose key983* cannot be used to vouch for the CRL and should be ignored984* @param stackedCerts a <code>Set</code> of <code>X509Certificate</code>s>985* whose revocation status depends on the986* establishment of this path.987* @throws CertPathValidatorException on failure988*/989private static final boolean [] CRL_SIGN_USAGE =990{ false, false, false, false, false, false, true };991private void buildToNewKey(X509Certificate currCert,992PublicKey prevKey,993Set<X509Certificate> stackedCerts)994throws CertPathValidatorException995{996997if (debug != null) {998debug.println("RevocationChecker.buildToNewKey()" +999" starting work");1000}1001Set<PublicKey> badKeys = new HashSet<>();1002if (prevKey != null) {1003badKeys.add(prevKey);1004}1005X509CertSelector certSel = new RejectKeySelector(badKeys);1006certSel.setSubject(currCert.getIssuerX500Principal());1007certSel.setKeyUsage(CRL_SIGN_USAGE);10081009Set<TrustAnchor> newAnchors = anchor == null ?1010params.trustAnchors() :1011Collections.singleton(anchor);10121013PKIXBuilderParameters builderParams;1014try {1015builderParams = new PKIXBuilderParameters(newAnchors, certSel);1016} catch (InvalidAlgorithmParameterException iape) {1017throw new RuntimeException(iape); // should never occur1018}1019builderParams.setInitialPolicies(params.initialPolicies());1020builderParams.setCertStores(certStores);1021builderParams.setExplicitPolicyRequired1022(params.explicitPolicyRequired());1023builderParams.setPolicyMappingInhibited1024(params.policyMappingInhibited());1025builderParams.setAnyPolicyInhibited(params.anyPolicyInhibited());1026// Policy qualifiers must be rejected, since we don't have1027// any way to convey them back to the application.1028// That's the default, so no need to write code.1029builderParams.setDate(params.date());1030builderParams.setCertPathCheckers(params.certPathCheckers());1031builderParams.setSigProvider(params.sigProvider());10321033// Skip revocation during this build to detect circular1034// references. But check revocation afterwards, using the1035// key (or any other that works).1036builderParams.setRevocationEnabled(false);10371038// check for AuthorityInformationAccess extension1039if (Builder.USE_AIA == true) {1040X509CertImpl currCertImpl = null;1041try {1042currCertImpl = X509CertImpl.toImpl(currCert);1043} catch (CertificateException ce) {1044// ignore but log it1045if (debug != null) {1046debug.println("RevocationChecker.buildToNewKey: " +1047"error decoding cert: " + ce);1048}1049}1050AuthorityInfoAccessExtension aiaExt = null;1051if (currCertImpl != null) {1052aiaExt = currCertImpl.getAuthorityInfoAccessExtension();1053}1054if (aiaExt != null) {1055List<AccessDescription> adList = aiaExt.getAccessDescriptions();1056if (adList != null) {1057for (AccessDescription ad : adList) {1058CertStore cs = URICertStore.getInstance(ad);1059if (cs != null) {1060if (debug != null) {1061debug.println("adding AIAext CertStore");1062}1063builderParams.addCertStore(cs);1064}1065}1066}1067}1068}10691070CertPathBuilder builder = null;1071try {1072builder = CertPathBuilder.getInstance("PKIX");1073} catch (NoSuchAlgorithmException nsae) {1074throw new CertPathValidatorException(nsae);1075}1076while (true) {1077try {1078if (debug != null) {1079debug.println("RevocationChecker.buildToNewKey()" +1080" about to try build ...");1081}1082PKIXCertPathBuilderResult cpbr =1083(PKIXCertPathBuilderResult)builder.build(builderParams);10841085if (debug != null) {1086debug.println("RevocationChecker.buildToNewKey()" +1087" about to check revocation ...");1088}1089// Now check revocation of all certs in path, assuming that1090// the stackedCerts are revoked.1091if (stackedCerts == null) {1092stackedCerts = new HashSet<X509Certificate>();1093}1094stackedCerts.add(currCert);1095TrustAnchor ta = cpbr.getTrustAnchor();1096PublicKey prevKey2 = ta.getCAPublicKey();1097if (prevKey2 == null) {1098prevKey2 = ta.getTrustedCert().getPublicKey();1099}1100boolean signFlag = true;1101List<? extends Certificate> cpList =1102cpbr.getCertPath().getCertificates();1103try {1104for (int i = cpList.size() - 1; i >= 0; i--) {1105X509Certificate cert = (X509Certificate) cpList.get(i);11061107if (debug != null) {1108debug.println("RevocationChecker.buildToNewKey()"1109+ " index " + i + " checking "1110+ cert);1111}1112checkCRLs(cert, prevKey2, null, signFlag, true,1113stackedCerts, newAnchors);1114signFlag = certCanSignCrl(cert);1115prevKey2 = cert.getPublicKey();1116}1117} catch (CertPathValidatorException cpve) {1118// ignore it and try to get another key1119badKeys.add(cpbr.getPublicKey());1120continue;1121}11221123if (debug != null) {1124debug.println("RevocationChecker.buildToNewKey()" +1125" got key " + cpbr.getPublicKey());1126}1127// Now check revocation on the current cert using that key and1128// the corresponding certificate.1129// If it doesn't check out, try to find a different key.1130// And if we can't find a key, then return false.1131PublicKey newKey = cpbr.getPublicKey();1132X509Certificate newCert = cpList.isEmpty() ?1133null : (X509Certificate) cpList.get(0);1134try {1135checkCRLs(currCert, newKey, newCert,1136true, false, null, params.trustAnchors());1137// If that passed, the cert is OK!1138return;1139} catch (CertPathValidatorException cpve) {1140// If it is revoked, rethrow exception1141if (cpve.getReason() == BasicReason.REVOKED) {1142throw cpve;1143}1144// Otherwise, ignore the exception and1145// try to get another key.1146}1147badKeys.add(newKey);1148} catch (InvalidAlgorithmParameterException iape) {1149throw new CertPathValidatorException(iape);1150} catch (CertPathBuilderException cpbe) {1151throw new CertPathValidatorException1152("Could not determine revocation status", null, null,1153-1, BasicReason.UNDETERMINED_REVOCATION_STATUS);1154}1155}1156}11571158/*1159* This inner class extends the X509CertSelector to add an additional1160* check to make sure the subject public key isn't on a particular list.1161* This class is used by buildToNewKey() to make sure the builder doesn't1162* end up with a CertPath to a public key that has already been rejected.1163*/1164private static class RejectKeySelector extends X509CertSelector {1165private final Set<PublicKey> badKeySet;11661167/**1168* Creates a new <code>RejectKeySelector</code>.1169*1170* @param badPublicKeys a <code>Set</code> of1171* <code>PublicKey</code>s that1172* should be rejected (or <code>null</code>1173* if no such check should be done)1174*/1175RejectKeySelector(Set<PublicKey> badPublicKeys) {1176this.badKeySet = badPublicKeys;1177}11781179/**1180* Decides whether a <code>Certificate</code> should be selected.1181*1182* @param cert the <code>Certificate</code> to be checked1183* @return <code>true</code> if the <code>Certificate</code> should be1184* selected, <code>false</code> otherwise1185*/1186@Override1187public boolean match(Certificate cert) {1188if (!super.match(cert))1189return(false);11901191if (badKeySet.contains(cert.getPublicKey())) {1192if (debug != null)1193debug.println("RejectKeySelector.match: bad key");1194return false;1195}11961197if (debug != null)1198debug.println("RejectKeySelector.match: returning true");1199return true;1200}12011202/**1203* Return a printable representation of the <code>CertSelector</code>.1204*1205* @return a <code>String</code> describing the contents of the1206* <code>CertSelector</code>1207*/1208@Override1209public String toString() {1210StringBuilder sb = new StringBuilder();1211sb.append("RejectKeySelector: [\n");1212sb.append(super.toString());1213sb.append(badKeySet);1214sb.append("]");1215return sb.toString();1216}1217}1218}121912201221