897 lines
35 KiB
Java
897 lines
35 KiB
Java
/*
|
|
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.security.provider.certpath;
|
|
|
|
import java.io.IOException;
|
|
import java.security.GeneralSecurityException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.PublicKey;
|
|
import java.security.cert.CertificateException;
|
|
import java.security.cert.CertPathValidatorException;
|
|
import java.security.cert.PKIXReason;
|
|
import java.security.cert.CertStore;
|
|
import java.security.cert.CertStoreException;
|
|
import java.security.cert.PKIXBuilderParameters;
|
|
import java.security.cert.PKIXCertPathChecker;
|
|
import java.security.cert.TrustAnchor;
|
|
import java.security.cert.X509Certificate;
|
|
import java.security.cert.X509CertSelector;
|
|
import java.util.*;
|
|
import javax.security.auth.x500.X500Principal;
|
|
|
|
import sun.security.provider.certpath.PKIX.BuilderParams;
|
|
import sun.security.util.Debug;
|
|
import sun.security.x509.AccessDescription;
|
|
import sun.security.x509.AuthorityInfoAccessExtension;
|
|
import sun.security.x509.AuthorityKeyIdentifierExtension;
|
|
import static sun.security.x509.PKIXExtensions.*;
|
|
import sun.security.x509.X500Name;
|
|
import sun.security.x509.X509CertImpl;
|
|
|
|
/**
|
|
* This class represents a forward builder, which is able to retrieve
|
|
* matching certificates from CertStores and verify a particular certificate
|
|
* against a ForwardState.
|
|
*
|
|
* @since 1.4
|
|
* @author Yassir Elley
|
|
* @author Sean Mullan
|
|
*/
|
|
class ForwardBuilder extends Builder {
|
|
|
|
private static final Debug debug = Debug.getInstance("certpath");
|
|
private final Set<X509Certificate> trustedCerts;
|
|
private final Set<X500Principal> trustedSubjectDNs;
|
|
private final Set<TrustAnchor> trustAnchors;
|
|
private X509CertSelector eeSelector;
|
|
private AdaptableX509CertSelector caSelector;
|
|
private X509CertSelector caTargetSelector;
|
|
TrustAnchor trustAnchor;
|
|
private boolean searchAllCertStores = true;
|
|
|
|
/**
|
|
* Initialize the builder with the input parameters.
|
|
*
|
|
* @param params the parameter set used to build a certification path
|
|
*/
|
|
ForwardBuilder(BuilderParams buildParams, boolean searchAllCertStores) {
|
|
super(buildParams);
|
|
|
|
// populate sets of trusted certificates and subject DNs
|
|
trustAnchors = buildParams.trustAnchors();
|
|
trustedCerts = new HashSet<X509Certificate>(trustAnchors.size());
|
|
trustedSubjectDNs = new HashSet<X500Principal>(trustAnchors.size());
|
|
for (TrustAnchor anchor : trustAnchors) {
|
|
X509Certificate trustedCert = anchor.getTrustedCert();
|
|
if (trustedCert != null) {
|
|
trustedCerts.add(trustedCert);
|
|
trustedSubjectDNs.add(trustedCert.getSubjectX500Principal());
|
|
} else {
|
|
trustedSubjectDNs.add(anchor.getCA());
|
|
}
|
|
}
|
|
this.searchAllCertStores = searchAllCertStores;
|
|
}
|
|
|
|
/**
|
|
* Retrieves all certs from the specified CertStores that satisfy the
|
|
* requirements specified in the parameters and the current
|
|
* PKIX state (name constraints, policy constraints, etc).
|
|
*
|
|
* @param currentState the current state.
|
|
* Must be an instance of <code>ForwardState</code>
|
|
* @param certStores list of CertStores
|
|
*/
|
|
@Override
|
|
Collection<X509Certificate> getMatchingCerts(State currentState,
|
|
List<CertStore> certStores)
|
|
throws CertStoreException, CertificateException, IOException
|
|
{
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.getMatchingCerts()...");
|
|
}
|
|
|
|
ForwardState currState = (ForwardState) currentState;
|
|
|
|
/*
|
|
* We store certs in a Set because we don't want duplicates.
|
|
* As each cert is added, it is sorted based on the PKIXCertComparator
|
|
* algorithm.
|
|
*/
|
|
Comparator<X509Certificate> comparator =
|
|
new PKIXCertComparator(trustedSubjectDNs, currState.cert);
|
|
Set<X509Certificate> certs = new TreeSet<>(comparator);
|
|
|
|
/*
|
|
* Only look for EE certs if search has just started.
|
|
*/
|
|
if (currState.isInitial()) {
|
|
getMatchingEECerts(currState, certStores, certs);
|
|
}
|
|
getMatchingCACerts(currState, certStores, certs);
|
|
|
|
return certs;
|
|
}
|
|
|
|
/*
|
|
* Retrieves all end-entity certificates which satisfy constraints
|
|
* and requirements specified in the parameters and PKIX state.
|
|
*/
|
|
private void getMatchingEECerts(ForwardState currentState,
|
|
List<CertStore> certStores,
|
|
Collection<X509Certificate> eeCerts)
|
|
throws IOException
|
|
{
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.getMatchingEECerts()...");
|
|
}
|
|
/*
|
|
* Compose a certificate matching rule to filter out
|
|
* certs which don't satisfy constraints
|
|
*
|
|
* First, retrieve clone of current target cert constraints,
|
|
* and then add more selection criteria based on current validation
|
|
* state. Since selector never changes, cache local copy & reuse.
|
|
*/
|
|
if (eeSelector == null) {
|
|
eeSelector = (X509CertSelector) targetCertConstraints.clone();
|
|
|
|
/*
|
|
* Match on certificate validity date
|
|
*/
|
|
eeSelector.setCertificateValid(buildParams.date());
|
|
|
|
/*
|
|
* Policy processing optimizations
|
|
*/
|
|
if (buildParams.explicitPolicyRequired()) {
|
|
eeSelector.setPolicy(getMatchingPolicies());
|
|
}
|
|
/*
|
|
* Require EE certs
|
|
*/
|
|
eeSelector.setBasicConstraints(-2);
|
|
}
|
|
|
|
/* Retrieve matching EE certs from CertStores */
|
|
addMatchingCerts(eeSelector, certStores, eeCerts, searchAllCertStores);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all CA certificates which satisfy constraints
|
|
* and requirements specified in the parameters and PKIX state.
|
|
*/
|
|
private void getMatchingCACerts(ForwardState currentState,
|
|
List<CertStore> certStores,
|
|
Collection<X509Certificate> caCerts)
|
|
throws IOException
|
|
{
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.getMatchingCACerts()...");
|
|
}
|
|
int initialSize = caCerts.size();
|
|
|
|
/*
|
|
* Compose a CertSelector to filter out
|
|
* certs which do not satisfy requirements.
|
|
*/
|
|
X509CertSelector sel = null;
|
|
|
|
if (currentState.isInitial()) {
|
|
if (targetCertConstraints.getBasicConstraints() == -2) {
|
|
// no need to continue: this means we never can match a CA cert
|
|
return;
|
|
}
|
|
|
|
/* This means a CA is the target, so match on same stuff as
|
|
* getMatchingEECerts
|
|
*/
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.getMatchingCACerts(): " +
|
|
"the target is a CA");
|
|
}
|
|
|
|
if (caTargetSelector == null) {
|
|
caTargetSelector =
|
|
(X509CertSelector) targetCertConstraints.clone();
|
|
|
|
/*
|
|
* Since we don't check the validity period of trusted
|
|
* certificates, please don't set the certificate valid
|
|
* criterion unless the trusted certificate matching is
|
|
* completed.
|
|
*/
|
|
|
|
/*
|
|
* Policy processing optimizations
|
|
*/
|
|
if (buildParams.explicitPolicyRequired())
|
|
caTargetSelector.setPolicy(getMatchingPolicies());
|
|
}
|
|
|
|
sel = caTargetSelector;
|
|
} else {
|
|
|
|
if (caSelector == null) {
|
|
caSelector = new AdaptableX509CertSelector();
|
|
|
|
/*
|
|
* Since we don't check the validity period of trusted
|
|
* certificates, please don't set the certificate valid
|
|
* criterion unless the trusted certificate matching is
|
|
* completed.
|
|
*/
|
|
|
|
/*
|
|
* Policy processing optimizations
|
|
*/
|
|
if (buildParams.explicitPolicyRequired())
|
|
caSelector.setPolicy(getMatchingPolicies());
|
|
}
|
|
|
|
/*
|
|
* Match on subject (issuer of previous cert)
|
|
*/
|
|
caSelector.setSubject(currentState.issuerDN);
|
|
|
|
/*
|
|
* check the validity period
|
|
*/
|
|
caSelector.setValidityPeriod(currentState.cert.getNotBefore(),
|
|
currentState.cert.getNotAfter());
|
|
|
|
sel = caSelector;
|
|
}
|
|
|
|
/*
|
|
* For compatibility, conservatively, we don't check the path
|
|
* length constraint of trusted anchors. Please don't set the
|
|
* basic constraints criterion unless the trusted certificate
|
|
* matching is completed.
|
|
*/
|
|
sel.setBasicConstraints(-1);
|
|
|
|
for (X509Certificate trustedCert : trustedCerts) {
|
|
if (sel.match(trustedCert)) {
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.getMatchingCACerts: " +
|
|
"found matching trust anchor." +
|
|
"\n SN: " +
|
|
Debug.toHexString(trustedCert.getSerialNumber()) +
|
|
"\n Subject: " +
|
|
trustedCert.getSubjectX500Principal() +
|
|
"\n Issuer: " +
|
|
trustedCert.getIssuerX500Principal());
|
|
}
|
|
caCerts.add(trustedCert);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The trusted certificate matching is completed. We need to match
|
|
* on certificate validity date.
|
|
*/
|
|
sel.setCertificateValid(buildParams.date());
|
|
|
|
/*
|
|
* Require CA certs with a pathLenConstraint that allows
|
|
* at least as many CA certs that have already been traversed
|
|
*/
|
|
sel.setBasicConstraints(currentState.traversedCACerts);
|
|
|
|
/*
|
|
* If we have already traversed as many CA certs as the maxPathLength
|
|
* will allow us to, then we don't bother looking through these
|
|
* certificate pairs. If maxPathLength has a value of -1, this
|
|
* means it is unconstrained, so we always look through the
|
|
* certificate pairs.
|
|
*/
|
|
if (currentState.isInitial() ||
|
|
(buildParams.maxPathLength() == -1) ||
|
|
(buildParams.maxPathLength() > currentState.traversedCACerts))
|
|
{
|
|
if (addMatchingCerts(sel, certStores,
|
|
caCerts, searchAllCertStores)
|
|
&& !searchAllCertStores) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!currentState.isInitial() && Builder.USE_AIA) {
|
|
// check for AuthorityInformationAccess extension
|
|
AuthorityInfoAccessExtension aiaExt =
|
|
currentState.cert.getAuthorityInfoAccessExtension();
|
|
if (aiaExt != null) {
|
|
getCerts(aiaExt, caCerts);
|
|
}
|
|
}
|
|
|
|
if (debug != null) {
|
|
int numCerts = caCerts.size() - initialSize;
|
|
debug.println("ForwardBuilder.getMatchingCACerts: found " +
|
|
numCerts + " CA certs");
|
|
}
|
|
}
|
|
|
|
// Thread-local gate to prevent recursive provider lookups
|
|
private static ThreadLocal<Object> gate = new ThreadLocal<>();
|
|
|
|
/**
|
|
* Download certificates from the given AIA and add them to the
|
|
* specified Collection.
|
|
*/
|
|
// cs.getCertificates(caSelector) returns a collection of X509Certificate's
|
|
// because of the selector, so the cast is safe
|
|
@SuppressWarnings("unchecked")
|
|
private boolean getCerts(AuthorityInfoAccessExtension aiaExt,
|
|
Collection<X509Certificate> certs)
|
|
{
|
|
if (Builder.USE_AIA == false) {
|
|
return false;
|
|
}
|
|
|
|
List<AccessDescription> adList = aiaExt.getAccessDescriptions();
|
|
if (adList == null || adList.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
if (gate.get() != null) {
|
|
// Avoid recursive fetching of certificates
|
|
if (debug != null) {
|
|
debug.println("Recursive fetching of certs via the AIA " +
|
|
"extension detected");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gate.set(gate);
|
|
try {
|
|
boolean add = false;
|
|
for (AccessDescription ad : adList) {
|
|
CertStore cs = URICertStore.getInstance(ad);
|
|
if (cs != null) {
|
|
try {
|
|
if (certs.addAll((Collection<X509Certificate>)
|
|
cs.getCertificates(caSelector))) {
|
|
add = true;
|
|
if (!searchAllCertStores) {
|
|
return true;
|
|
}
|
|
}
|
|
} catch (CertStoreException cse) {
|
|
if (debug != null) {
|
|
debug.println("exception getting certs from CertStore:");
|
|
cse.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return add;
|
|
} finally {
|
|
gate.set(null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This inner class compares 2 PKIX certificates according to which
|
|
* should be tried first when building a path from the target.
|
|
* The preference order is as follows:
|
|
*
|
|
* Given trusted certificate(s):
|
|
* Subject:ou=D,ou=C,o=B,c=A
|
|
*
|
|
* Preference order for current cert:
|
|
*
|
|
* 1) The key identifier of an AKID extension (if present) in the
|
|
* previous certificate matches the key identifier in the SKID extension
|
|
*
|
|
* 2) Issuer matches a trusted subject
|
|
* Issuer: ou=D,ou=C,o=B,c=A
|
|
*
|
|
* 3) Issuer is a descendant of a trusted subject (in order of
|
|
* number of links to the trusted subject)
|
|
* a) Issuer: ou=E,ou=D,ou=C,o=B,c=A [links=1]
|
|
* b) Issuer: ou=F,ou=E,ou=D,ou=C,ou=B,c=A [links=2]
|
|
*
|
|
* 4) Issuer is an ancestor of a trusted subject (in order of number of
|
|
* links to the trusted subject)
|
|
* a) Issuer: ou=C,o=B,c=A [links=1]
|
|
* b) Issuer: o=B,c=A [links=2]
|
|
*
|
|
* 5) Issuer is in the same namespace as a trusted subject (in order of
|
|
* number of links to the trusted subject)
|
|
* a) Issuer: ou=G,ou=C,o=B,c=A [links=2]
|
|
* b) Issuer: ou=H,o=B,c=A [links=3]
|
|
*
|
|
* 6) Issuer is an ancestor of certificate subject (in order of number
|
|
* of links to the certificate subject)
|
|
* a) Issuer: ou=K,o=J,c=A
|
|
* Subject: ou=L,ou=K,o=J,c=A
|
|
* b) Issuer: o=J,c=A
|
|
* Subject: ou=L,ou=K,0=J,c=A
|
|
*
|
|
* 7) Any other certificates
|
|
*/
|
|
static class PKIXCertComparator implements Comparator<X509Certificate> {
|
|
|
|
static final String METHOD_NME = "PKIXCertComparator.compare()";
|
|
|
|
private final Set<X500Principal> trustedSubjectDNs;
|
|
private final X509CertSelector certSkidSelector;
|
|
|
|
PKIXCertComparator(Set<X500Principal> trustedSubjectDNs,
|
|
X509CertImpl previousCert) throws IOException {
|
|
this.trustedSubjectDNs = trustedSubjectDNs;
|
|
this.certSkidSelector = getSelector(previousCert);
|
|
}
|
|
|
|
/**
|
|
* Returns an X509CertSelector for matching on the authority key
|
|
* identifier, or null if not applicable.
|
|
*/
|
|
private X509CertSelector getSelector(X509CertImpl previousCert)
|
|
throws IOException {
|
|
if (previousCert != null) {
|
|
AuthorityKeyIdentifierExtension akidExt =
|
|
previousCert.getAuthorityKeyIdentifierExtension();
|
|
if (akidExt != null) {
|
|
byte[] skid = akidExt.getEncodedKeyIdentifier();
|
|
if (skid != null) {
|
|
X509CertSelector selector = new X509CertSelector();
|
|
selector.setSubjectKeyIdentifier(skid);
|
|
return selector;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @param oCert1 First X509Certificate to be compared
|
|
* @param oCert2 Second X509Certificate to be compared
|
|
* @return -1 if oCert1 is preferable to oCert2, or
|
|
* if oCert1 and oCert2 are equally preferable (in this
|
|
* case it doesn't matter which is preferable, but we don't
|
|
* return 0 because the comparator would behave strangely
|
|
* when used in a SortedSet).
|
|
* 1 if oCert2 is preferable to oCert1
|
|
* 0 if oCert1.equals(oCert2). We only return 0 if the
|
|
* certs are equal so that this comparator behaves
|
|
* correctly when used in a SortedSet.
|
|
* @throws ClassCastException if either argument is not of type
|
|
* X509Certificate
|
|
*/
|
|
@Override
|
|
public int compare(X509Certificate oCert1, X509Certificate oCert2) {
|
|
|
|
// if certs are the same, return 0
|
|
if (oCert1.equals(oCert2)) return 0;
|
|
|
|
// If akid/skid match then it is preferable
|
|
if (certSkidSelector != null) {
|
|
if (certSkidSelector.match(oCert1)) {
|
|
return -1;
|
|
}
|
|
if (certSkidSelector.match(oCert2)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
X500Principal cIssuer1 = oCert1.getIssuerX500Principal();
|
|
X500Principal cIssuer2 = oCert2.getIssuerX500Principal();
|
|
X500Name cIssuer1Name = X500Name.asX500Name(cIssuer1);
|
|
X500Name cIssuer2Name = X500Name.asX500Name(cIssuer2);
|
|
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " o1 Issuer: " + cIssuer1);
|
|
debug.println(METHOD_NME + " o2 Issuer: " + cIssuer2);
|
|
}
|
|
|
|
/* If one cert's issuer matches a trusted subject, then it is
|
|
* preferable.
|
|
*/
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " MATCH TRUSTED SUBJECT TEST...");
|
|
}
|
|
|
|
boolean m1 = trustedSubjectDNs.contains(cIssuer1);
|
|
boolean m2 = trustedSubjectDNs.contains(cIssuer2);
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " m1: " + m1);
|
|
debug.println(METHOD_NME + " m2: " + m2);
|
|
}
|
|
if (m1 && m2) {
|
|
return -1;
|
|
} else if (m1) {
|
|
return -1;
|
|
} else if (m2) {
|
|
return 1;
|
|
}
|
|
|
|
/* If one cert's issuer is a naming descendant of a trusted subject,
|
|
* then it is preferable, in order of increasing naming distance.
|
|
*/
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " NAMING DESCENDANT TEST...");
|
|
}
|
|
for (X500Principal tSubject : trustedSubjectDNs) {
|
|
X500Name tSubjectName = X500Name.asX500Name(tSubject);
|
|
int distanceTto1 =
|
|
Builder.distance(tSubjectName, cIssuer1Name, -1);
|
|
int distanceTto2 =
|
|
Builder.distance(tSubjectName, cIssuer2Name, -1);
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME +" distanceTto1: " + distanceTto1);
|
|
debug.println(METHOD_NME +" distanceTto2: " + distanceTto2);
|
|
}
|
|
if (distanceTto1 > 0 || distanceTto2 > 0) {
|
|
if (distanceTto1 == distanceTto2) {
|
|
return -1;
|
|
} else if (distanceTto1 > 0 && distanceTto2 <= 0) {
|
|
return -1;
|
|
} else if (distanceTto1 <= 0 && distanceTto2 > 0) {
|
|
return 1;
|
|
} else if (distanceTto1 < distanceTto2) {
|
|
return -1;
|
|
} else { // distanceTto1 > distanceTto2
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If one cert's issuer is a naming ancestor of a trusted subject,
|
|
* then it is preferable, in order of increasing naming distance.
|
|
*/
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " NAMING ANCESTOR TEST...");
|
|
}
|
|
for (X500Principal tSubject : trustedSubjectDNs) {
|
|
X500Name tSubjectName = X500Name.asX500Name(tSubject);
|
|
|
|
int distanceTto1 = Builder.distance
|
|
(tSubjectName, cIssuer1Name, Integer.MAX_VALUE);
|
|
int distanceTto2 = Builder.distance
|
|
(tSubjectName, cIssuer2Name, Integer.MAX_VALUE);
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME +" distanceTto1: " + distanceTto1);
|
|
debug.println(METHOD_NME +" distanceTto2: " + distanceTto2);
|
|
}
|
|
if (distanceTto1 < 0 || distanceTto2 < 0) {
|
|
if (distanceTto1 == distanceTto2) {
|
|
return -1;
|
|
} else if (distanceTto1 < 0 && distanceTto2 >= 0) {
|
|
return -1;
|
|
} else if (distanceTto1 >= 0 && distanceTto2 < 0) {
|
|
return 1;
|
|
} else if (distanceTto1 > distanceTto2) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If one cert's issuer is in the same namespace as a trusted
|
|
* subject, then it is preferable, in order of increasing naming
|
|
* distance.
|
|
*/
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME +" SAME NAMESPACE AS TRUSTED TEST...");
|
|
}
|
|
for (X500Principal tSubject : trustedSubjectDNs) {
|
|
X500Name tSubjectName = X500Name.asX500Name(tSubject);
|
|
X500Name tAo1 = tSubjectName.commonAncestor(cIssuer1Name);
|
|
X500Name tAo2 = tSubjectName.commonAncestor(cIssuer2Name);
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME +" tAo1: " + String.valueOf(tAo1));
|
|
debug.println(METHOD_NME +" tAo2: " + String.valueOf(tAo2));
|
|
}
|
|
if (tAo1 != null || tAo2 != null) {
|
|
if (tAo1 != null && tAo2 != null) {
|
|
int hopsTto1 = Builder.hops
|
|
(tSubjectName, cIssuer1Name, Integer.MAX_VALUE);
|
|
int hopsTto2 = Builder.hops
|
|
(tSubjectName, cIssuer2Name, Integer.MAX_VALUE);
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME +" hopsTto1: " + hopsTto1);
|
|
debug.println(METHOD_NME +" hopsTto2: " + hopsTto2);
|
|
}
|
|
if (hopsTto1 == hopsTto2) {
|
|
} else if (hopsTto1 > hopsTto2) {
|
|
return 1;
|
|
} else { // hopsTto1 < hopsTto2
|
|
return -1;
|
|
}
|
|
} else if (tAo1 == null) {
|
|
return 1;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* If one cert's issuer is an ancestor of that cert's subject,
|
|
* then it is preferable, in order of increasing naming distance.
|
|
*/
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME+" CERT ISSUER/SUBJECT COMPARISON TEST...");
|
|
}
|
|
X500Principal cSubject1 = oCert1.getSubjectX500Principal();
|
|
X500Principal cSubject2 = oCert2.getSubjectX500Principal();
|
|
X500Name cSubject1Name = X500Name.asX500Name(cSubject1);
|
|
X500Name cSubject2Name = X500Name.asX500Name(cSubject2);
|
|
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " o1 Subject: " + cSubject1);
|
|
debug.println(METHOD_NME + " o2 Subject: " + cSubject2);
|
|
}
|
|
int distanceStoI1 = Builder.distance
|
|
(cSubject1Name, cIssuer1Name, Integer.MAX_VALUE);
|
|
int distanceStoI2 = Builder.distance
|
|
(cSubject2Name, cIssuer2Name, Integer.MAX_VALUE);
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " distanceStoI1: " + distanceStoI1);
|
|
debug.println(METHOD_NME + " distanceStoI2: " + distanceStoI2);
|
|
}
|
|
if (distanceStoI2 > distanceStoI1) {
|
|
return -1;
|
|
} else if (distanceStoI2 < distanceStoI1) {
|
|
return 1;
|
|
}
|
|
|
|
/* Otherwise, certs are equally preferable.
|
|
*/
|
|
if (debug != null) {
|
|
debug.println(METHOD_NME + " no tests matched; RETURN 0");
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies a matching certificate.
|
|
*
|
|
* This method executes the validation steps in the PKIX path
|
|
* validation algorithm <draft-ietf-pkix-new-part1-08.txt> which were
|
|
* not satisfied by the selection criteria used by getCertificates()
|
|
* to find the certs and only the steps that can be executed in a
|
|
* forward direction (target to trust anchor). Those steps that can
|
|
* only be executed in a reverse direction are deferred until the
|
|
* complete path has been built.
|
|
*
|
|
* Trust anchor certs are not validated.
|
|
*
|
|
* If the last certificate is being verified (the one whose subject
|
|
* matches the target subject, then steps in 6.1.4 of the PKIX
|
|
* Certification Path Validation algorithm are NOT executed,
|
|
* regardless of whether or not the last cert is an end-entity
|
|
* cert or not. This allows callers to certify CA certs as
|
|
* well as EE certs.
|
|
*
|
|
* @param cert the certificate to be verified
|
|
* @param currentState the current state against which the cert is verified
|
|
* @param certPathList the certPathList generated thus far
|
|
*/
|
|
@Override
|
|
void verifyCert(X509Certificate cert, State currentState,
|
|
List<X509Certificate> certPathList)
|
|
throws GeneralSecurityException
|
|
{
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.verifyCert(SN: "
|
|
+ Debug.toHexString(cert.getSerialNumber())
|
|
+ "\n Issuer: " + cert.getIssuerX500Principal() + ")"
|
|
+ "\n Subject: " + cert.getSubjectX500Principal() + ")");
|
|
}
|
|
|
|
ForwardState currState = (ForwardState)currentState;
|
|
|
|
// Don't bother to verify untrusted certificate more.
|
|
currState.untrustedChecker.check(cert, Collections.<String>emptySet());
|
|
|
|
/* check if trusted cert */
|
|
boolean isTrustedCert = trustedCerts.contains(cert);
|
|
|
|
/* we don't perform any validation of the trusted cert */
|
|
if (!isTrustedCert) {
|
|
/*
|
|
* Check CRITICAL private extensions for user checkers that
|
|
* support forward checking (forwardCheckers) and remove
|
|
* ones we know how to check.
|
|
*/
|
|
Set<String> unresCritExts = cert.getCriticalExtensionOIDs();
|
|
if (unresCritExts == null) {
|
|
unresCritExts = Collections.<String>emptySet();
|
|
}
|
|
for (PKIXCertPathChecker checker : currState.forwardCheckers) {
|
|
checker.check(cert, unresCritExts);
|
|
}
|
|
|
|
/*
|
|
* Remove extensions from user checkers that don't support
|
|
* forward checking. After this step, we will have removed
|
|
* all extensions that all user checkers are capable of
|
|
* processing.
|
|
*/
|
|
for (PKIXCertPathChecker checker : buildParams.certPathCheckers()) {
|
|
if (!checker.isForwardCheckingSupported()) {
|
|
Set<String> supportedExts = checker.getSupportedExtensions();
|
|
if (supportedExts != null) {
|
|
unresCritExts.removeAll(supportedExts);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look at the remaining extensions and remove any ones we know how
|
|
* to check. If there are any left, throw an exception!
|
|
*/
|
|
if (!unresCritExts.isEmpty()) {
|
|
unresCritExts.remove(BasicConstraints_Id.toString());
|
|
unresCritExts.remove(NameConstraints_Id.toString());
|
|
unresCritExts.remove(CertificatePolicies_Id.toString());
|
|
unresCritExts.remove(PolicyMappings_Id.toString());
|
|
unresCritExts.remove(PolicyConstraints_Id.toString());
|
|
unresCritExts.remove(InhibitAnyPolicy_Id.toString());
|
|
unresCritExts.remove(SubjectAlternativeName_Id.toString());
|
|
unresCritExts.remove(KeyUsage_Id.toString());
|
|
unresCritExts.remove(ExtendedKeyUsage_Id.toString());
|
|
|
|
if (!unresCritExts.isEmpty())
|
|
throw new CertPathValidatorException
|
|
("Unrecognized critical extension(s)", null, null, -1,
|
|
PKIXReason.UNRECOGNIZED_CRIT_EXT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* if this is the target certificate (init=true), then we are
|
|
* not able to do any more verification, so just return
|
|
*/
|
|
if (currState.isInitial()) {
|
|
return;
|
|
}
|
|
|
|
/* we don't perform any validation of the trusted cert */
|
|
if (!isTrustedCert) {
|
|
/* Make sure this is a CA cert */
|
|
if (cert.getBasicConstraints() == -1) {
|
|
throw new CertificateException("cert is NOT a CA cert");
|
|
}
|
|
|
|
/*
|
|
* Check keyUsage extension
|
|
*/
|
|
KeyChecker.verifyCAKeyUsage(cert);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verifies whether the input certificate completes the path.
|
|
* First checks the cert against each trust anchor that was specified,
|
|
* in order, and returns true if the cert matches the trust anchor
|
|
* specified as a certificate or has the same key and subject of an anchor
|
|
* specified as a trusted {pubkey, caname} pair.
|
|
* If no match has been found, does a second check of the cert against
|
|
* anchors specified as a trusted {pubkey, caname} pair to see if the cert
|
|
* was issued by that anchor.
|
|
* Returns false if none of the trust anchors are valid for this cert.
|
|
*
|
|
* @param cert the certificate to test
|
|
* @return a boolean value indicating whether the cert completes the path.
|
|
*/
|
|
@Override
|
|
boolean isPathCompleted(X509Certificate cert) {
|
|
List<TrustAnchor> otherAnchors = new ArrayList<>();
|
|
// first, check if cert is already trusted
|
|
for (TrustAnchor anchor : trustAnchors) {
|
|
if (anchor.getTrustedCert() != null) {
|
|
if (cert.equals(anchor.getTrustedCert())) {
|
|
this.trustAnchor = anchor;
|
|
return true;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
X500Principal principal = anchor.getCA();
|
|
PublicKey publicKey = anchor.getCAPublicKey();
|
|
|
|
if (principal != null && publicKey != null &&
|
|
principal.equals(cert.getSubjectX500Principal())) {
|
|
if (publicKey.equals(cert.getPublicKey())) {
|
|
// the cert itself is a trust anchor
|
|
this.trustAnchor = anchor;
|
|
return true;
|
|
}
|
|
// else, it is a self-issued certificate of the anchor
|
|
}
|
|
otherAnchors.add(anchor);
|
|
}
|
|
// next, check if cert is issued by anchor specified by key/name
|
|
for (TrustAnchor anchor : otherAnchors) {
|
|
X500Principal principal = anchor.getCA();
|
|
PublicKey publicKey = anchor.getCAPublicKey();
|
|
// Check subject/issuer name chaining
|
|
if (principal == null ||
|
|
!principal.equals(cert.getIssuerX500Principal())) {
|
|
continue;
|
|
}
|
|
|
|
// skip anchor if it contains a DSA key with no DSA params
|
|
if (PKIX.isDSAPublicKeyWithoutParams(publicKey)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Check signature
|
|
*/
|
|
try {
|
|
cert.verify(publicKey, buildParams.sigProvider());
|
|
} catch (InvalidKeyException ike) {
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.isPathCompleted() invalid "
|
|
+ "DSA key found");
|
|
}
|
|
continue;
|
|
} catch (GeneralSecurityException e){
|
|
if (debug != null) {
|
|
debug.println("ForwardBuilder.isPathCompleted() " +
|
|
"unexpected exception");
|
|
e.printStackTrace();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
this.trustAnchor = anchor;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Adds the certificate to the certPathList
|
|
*
|
|
* @param cert the certificate to be added
|
|
* @param certPathList the certification path list
|
|
*/
|
|
@Override
|
|
void addCertToPath(X509Certificate cert,
|
|
LinkedList<X509Certificate> certPathList)
|
|
{
|
|
certPathList.addFirst(cert);
|
|
}
|
|
|
|
/** Removes final certificate from the certPathList
|
|
*
|
|
* @param certPathList the certification path list
|
|
*/
|
|
@Override
|
|
void removeFinalCertFromPath(LinkedList<X509Certificate> certPathList) {
|
|
certPathList.removeFirst();
|
|
}
|
|
}
|