582 lines
24 KiB
Java
582 lines
24 KiB
Java
/*
|
|
* Copyright (c) 2000, 2015, 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.InvalidAlgorithmParameterException;
|
|
import java.security.PublicKey;
|
|
import java.security.cert.*;
|
|
import java.security.cert.CertPathValidatorException.BasicReason;
|
|
import java.security.cert.PKIXReason;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.LinkedList;
|
|
import java.util.Set;
|
|
import javax.security.auth.x500.X500Principal;
|
|
|
|
import sun.security.provider.certpath.PKIX.BuilderParams;
|
|
import static sun.security.x509.PKIXExtensions.*;
|
|
import sun.security.util.Debug;
|
|
|
|
/**
|
|
* This class builds certification paths in the forward direction.
|
|
*
|
|
* <p> If successful, it returns a certification path which has successfully
|
|
* satisfied all the constraints and requirements specified in the
|
|
* PKIXBuilderParameters object and has been validated according to the PKIX
|
|
* path validation algorithm defined in RFC 3280.
|
|
*
|
|
* <p> This implementation uses a depth-first search approach to finding
|
|
* certification paths. If it comes to a point in which it cannot find
|
|
* any more certificates leading to the target OR the path length is too long
|
|
* it backtracks to previous paths until the target has been found or
|
|
* all possible paths have been exhausted.
|
|
*
|
|
* <p> This implementation is not thread-safe.
|
|
*
|
|
* @since 1.4
|
|
* @author Sean Mullan
|
|
* @author Yassir Elley
|
|
*/
|
|
public final class SunCertPathBuilder extends CertPathBuilderSpi {
|
|
|
|
private static final Debug debug = Debug.getInstance("certpath");
|
|
|
|
/*
|
|
* private objects shared by methods
|
|
*/
|
|
private BuilderParams buildParams;
|
|
private CertificateFactory cf;
|
|
private boolean pathCompleted = false;
|
|
private PolicyNode policyTreeResult;
|
|
private TrustAnchor trustAnchor;
|
|
private PublicKey finalPublicKey;
|
|
|
|
/**
|
|
* Create an instance of <code>SunCertPathBuilder</code>.
|
|
*
|
|
* @throws CertPathBuilderException if an error occurs
|
|
*/
|
|
public SunCertPathBuilder() throws CertPathBuilderException {
|
|
try {
|
|
cf = CertificateFactory.getInstance("X.509");
|
|
} catch (CertificateException e) {
|
|
throw new CertPathBuilderException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public CertPathChecker engineGetRevocationChecker() {
|
|
return new RevocationChecker();
|
|
}
|
|
|
|
/**
|
|
* Attempts to build a certification path using the Sun build
|
|
* algorithm from a trusted anchor(s) to a target subject, which must both
|
|
* be specified in the input parameter set. This method will
|
|
* attempt to build in the forward direction: from the target to the CA.
|
|
*
|
|
* <p>The certification path that is constructed is validated
|
|
* according to the PKIX specification.
|
|
*
|
|
* @param params the parameter set for building a path. Must be an instance
|
|
* of <code>PKIXBuilderParameters</code>.
|
|
* @return a certification path builder result.
|
|
* @exception CertPathBuilderException Exception thrown if builder is
|
|
* unable to build a complete certification path from the trusted anchor(s)
|
|
* to the target subject.
|
|
* @throws InvalidAlgorithmParameterException if the given parameters are
|
|
* inappropriate for this certification path builder.
|
|
*/
|
|
@Override
|
|
public CertPathBuilderResult engineBuild(CertPathParameters params)
|
|
throws CertPathBuilderException, InvalidAlgorithmParameterException {
|
|
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.engineBuild(" + params + ")");
|
|
}
|
|
|
|
buildParams = PKIX.checkBuilderParams(params);
|
|
return build();
|
|
}
|
|
|
|
private PKIXCertPathBuilderResult build() throws CertPathBuilderException {
|
|
List<List<Vertex>> adjList = new ArrayList<>();
|
|
PKIXCertPathBuilderResult result = buildCertPath(false, adjList);
|
|
if (result == null) {
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.engineBuild: 2nd pass; " +
|
|
"try building again searching all certstores");
|
|
}
|
|
// try again
|
|
adjList.clear();
|
|
result = buildCertPath(true, adjList);
|
|
if (result == null) {
|
|
throw new SunCertPathBuilderException("unable to find valid "
|
|
+ "certification path to requested target",
|
|
new AdjacencyList(adjList));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private PKIXCertPathBuilderResult buildCertPath(boolean searchAllCertStores,
|
|
List<List<Vertex>> adjList)
|
|
throws CertPathBuilderException
|
|
{
|
|
// Init shared variables and build certification path
|
|
pathCompleted = false;
|
|
trustAnchor = null;
|
|
finalPublicKey = null;
|
|
policyTreeResult = null;
|
|
LinkedList<X509Certificate> certPathList = new LinkedList<>();
|
|
try {
|
|
buildForward(adjList, certPathList, searchAllCertStores);
|
|
} catch (GeneralSecurityException | IOException e) {
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.engineBuild() exception in "
|
|
+ "build");
|
|
e.printStackTrace();
|
|
}
|
|
throw new SunCertPathBuilderException("unable to find valid "
|
|
+ "certification path to requested target", e,
|
|
new AdjacencyList(adjList));
|
|
}
|
|
|
|
// construct SunCertPathBuilderResult
|
|
try {
|
|
if (pathCompleted) {
|
|
if (debug != null)
|
|
debug.println("SunCertPathBuilder.engineBuild() "
|
|
+ "pathCompleted");
|
|
|
|
// we must return a certpath which has the target
|
|
// as the first cert in the certpath - i.e. reverse
|
|
// the certPathList
|
|
Collections.reverse(certPathList);
|
|
|
|
return new SunCertPathBuilderResult(
|
|
cf.generateCertPath(certPathList), trustAnchor,
|
|
policyTreeResult, finalPublicKey,
|
|
new AdjacencyList(adjList));
|
|
}
|
|
} catch (CertificateException e) {
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.engineBuild() exception "
|
|
+ "in wrap-up");
|
|
e.printStackTrace();
|
|
}
|
|
throw new SunCertPathBuilderException("unable to find valid "
|
|
+ "certification path to requested target", e,
|
|
new AdjacencyList(adjList));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* Private build forward method.
|
|
*/
|
|
private void buildForward(List<List<Vertex>> adjacencyList,
|
|
LinkedList<X509Certificate> certPathList,
|
|
boolean searchAllCertStores)
|
|
throws GeneralSecurityException, IOException
|
|
{
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.buildForward()...");
|
|
}
|
|
|
|
/* Initialize current state */
|
|
ForwardState currentState = new ForwardState();
|
|
currentState.initState(buildParams.certPathCheckers());
|
|
|
|
/* Initialize adjacency list */
|
|
adjacencyList.clear();
|
|
adjacencyList.add(new LinkedList<Vertex>());
|
|
|
|
// Android-removed: Android doesn't use this mechanism for checking untrusted certificates
|
|
// currentState.untrustedChecker = new UntrustedChecker();
|
|
|
|
depthFirstSearchForward(buildParams.targetSubject(), currentState,
|
|
new ForwardBuilder(buildParams,
|
|
searchAllCertStores),
|
|
adjacencyList, certPathList);
|
|
}
|
|
|
|
/*
|
|
* This method performs a depth first search for a certification
|
|
* path while building forward which meets the requirements set in
|
|
* the parameters object.
|
|
* It uses an adjacency list to store all certificates which were
|
|
* tried (i.e. at one time added to the path - they may not end up in
|
|
* the final path if backtracking occurs). This information can
|
|
* be used later to debug or demo the build.
|
|
*
|
|
* See "Data Structure and Algorithms, by Aho, Hopcroft, and Ullman"
|
|
* for an explanation of the DFS algorithm.
|
|
*
|
|
* @param dN the distinguished name being currently searched for certs
|
|
* @param currentState the current PKIX validation state
|
|
*/
|
|
private void depthFirstSearchForward(X500Principal dN,
|
|
ForwardState currentState,
|
|
ForwardBuilder builder,
|
|
List<List<Vertex>> adjList,
|
|
LinkedList<X509Certificate> cpList)
|
|
throws GeneralSecurityException, IOException
|
|
{
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.depthFirstSearchForward(" + dN
|
|
+ ", " + currentState.toString() + ")");
|
|
}
|
|
|
|
/*
|
|
* Find all the certificates issued to dN which
|
|
* satisfy the PKIX certification path constraints.
|
|
*/
|
|
Collection<X509Certificate> certs =
|
|
builder.getMatchingCerts(currentState, buildParams.certStores());
|
|
List<Vertex> vertices = addVertices(certs, adjList);
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.depthFirstSearchForward(): "
|
|
+ "certs.size=" + vertices.size());
|
|
}
|
|
|
|
/*
|
|
* For each cert in the collection, verify anything
|
|
* that hasn't been checked yet (signature, revocation, etc)
|
|
* and check for loops. Call depthFirstSearchForward()
|
|
* recursively for each good cert.
|
|
*/
|
|
|
|
vertices:
|
|
for (Vertex vertex : vertices) {
|
|
/**
|
|
* Restore state to currentState each time through the loop.
|
|
* This is important because some of the user-defined
|
|
* checkers modify the state, which MUST be restored if
|
|
* the cert eventually fails to lead to the target and
|
|
* the next matching cert is tried.
|
|
*/
|
|
ForwardState nextState = (ForwardState) currentState.clone();
|
|
X509Certificate cert = vertex.getCertificate();
|
|
|
|
try {
|
|
builder.verifyCert(cert, nextState, cpList);
|
|
} catch (GeneralSecurityException gse) {
|
|
if (debug != null) {
|
|
debug.println("SunCertPathBuilder.depthFirstSearchForward()"
|
|
+ ": validation failed: " + gse);
|
|
gse.printStackTrace();
|
|
}
|
|
vertex.setThrowable(gse);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Certificate is good.
|
|
* If cert completes the path,
|
|
* process userCheckers that don't support forward checking
|
|
* and process policies over whole path
|
|
* and backtrack appropriately if there is a failure
|
|
* else if cert does not complete the path,
|
|
* add it to the path
|
|
*/
|
|
if (builder.isPathCompleted(cert)) {
|
|
|
|
if (debug != null)
|
|
debug.println("SunCertPathBuilder.depthFirstSearchForward()"
|
|
+ ": commencing final verification");
|
|
|
|
List<X509Certificate> appendedCerts = new ArrayList<>(cpList);
|
|
|
|
/*
|
|
* if the trust anchor selected is specified as a trusted
|
|
* public key rather than a trusted cert, then verify this
|
|
* cert (which is signed by the trusted public key), but
|
|
* don't add it yet to the cpList
|
|
*/
|
|
if (builder.trustAnchor.getTrustedCert() == null) {
|
|
appendedCerts.add(0, cert);
|
|
}
|
|
|
|
Set<String> initExpPolSet =
|
|
Collections.singleton(PolicyChecker.ANY_POLICY);
|
|
|
|
PolicyNodeImpl rootNode = new PolicyNodeImpl(null,
|
|
PolicyChecker.ANY_POLICY, null, false, initExpPolSet, false);
|
|
|
|
List<PKIXCertPathChecker> checkers = new ArrayList<>();
|
|
PolicyChecker policyChecker
|
|
= new PolicyChecker(buildParams.initialPolicies(),
|
|
appendedCerts.size(),
|
|
buildParams.explicitPolicyRequired(),
|
|
buildParams.policyMappingInhibited(),
|
|
buildParams.anyPolicyInhibited(),
|
|
buildParams.policyQualifiersRejected(),
|
|
rootNode);
|
|
checkers.add(policyChecker);
|
|
|
|
// add the algorithm checker
|
|
checkers.add(new AlgorithmChecker(builder.trustAnchor));
|
|
|
|
BasicChecker basicChecker = null;
|
|
if (nextState.keyParamsNeeded()) {
|
|
PublicKey rootKey = cert.getPublicKey();
|
|
if (builder.trustAnchor.getTrustedCert() == null) {
|
|
rootKey = builder.trustAnchor.getCAPublicKey();
|
|
if (debug != null)
|
|
debug.println(
|
|
"SunCertPathBuilder.depthFirstSearchForward " +
|
|
"using buildParams public key: " +
|
|
rootKey.toString());
|
|
}
|
|
TrustAnchor anchor = new TrustAnchor
|
|
(cert.getSubjectX500Principal(), rootKey, null);
|
|
|
|
// add the basic checker
|
|
basicChecker = new BasicChecker(anchor, buildParams.date(),
|
|
buildParams.sigProvider(),
|
|
true);
|
|
checkers.add(basicChecker);
|
|
}
|
|
|
|
buildParams.setCertPath(cf.generateCertPath(appendedCerts));
|
|
|
|
boolean revCheckerAdded = false;
|
|
List<PKIXCertPathChecker> ckrs = buildParams.certPathCheckers();
|
|
for (PKIXCertPathChecker ckr : ckrs) {
|
|
if (ckr instanceof PKIXRevocationChecker) {
|
|
if (revCheckerAdded) {
|
|
throw new CertPathValidatorException(
|
|
"Only one PKIXRevocationChecker can be specified");
|
|
}
|
|
revCheckerAdded = true;
|
|
// if it's our own, initialize it
|
|
if (ckr instanceof RevocationChecker) {
|
|
((RevocationChecker)ckr).init(builder.trustAnchor,
|
|
buildParams);
|
|
}
|
|
}
|
|
}
|
|
// only add a RevocationChecker if revocation is enabled and
|
|
// a PKIXRevocationChecker has not already been added
|
|
if (buildParams.revocationEnabled() && !revCheckerAdded) {
|
|
checkers.add(new RevocationChecker(builder.trustAnchor,
|
|
buildParams));
|
|
}
|
|
|
|
checkers.addAll(ckrs);
|
|
|
|
// Why we don't need BasicChecker and RevocationChecker
|
|
// if nextState.keyParamsNeeded() is false?
|
|
|
|
for (int i = 0; i < appendedCerts.size(); i++) {
|
|
X509Certificate currCert = appendedCerts.get(i);
|
|
if (debug != null)
|
|
debug.println("current subject = "
|
|
+ currCert.getSubjectX500Principal());
|
|
Set<String> unresCritExts =
|
|
currCert.getCriticalExtensionOIDs();
|
|
if (unresCritExts == null) {
|
|
unresCritExts = Collections.<String>emptySet();
|
|
}
|
|
|
|
for (PKIXCertPathChecker currChecker : checkers) {
|
|
if (!currChecker.isForwardCheckingSupported()) {
|
|
if (i == 0) {
|
|
currChecker.init(false);
|
|
|
|
// The user specified
|
|
// AlgorithmChecker may not be
|
|
// able to set the trust anchor until now.
|
|
if (currChecker instanceof AlgorithmChecker) {
|
|
((AlgorithmChecker)currChecker).
|
|
trySetTrustAnchor(builder.trustAnchor);
|
|
}
|
|
}
|
|
|
|
try {
|
|
currChecker.check(currCert, unresCritExts);
|
|
} catch (CertPathValidatorException cpve) {
|
|
if (debug != null)
|
|
debug.println
|
|
("SunCertPathBuilder.depthFirstSearchForward(): " +
|
|
"final verification failed: " + cpve);
|
|
// If the target cert itself is revoked, we
|
|
// cannot trust it. We can bail out here.
|
|
if (buildParams.targetCertConstraints().match(currCert)
|
|
&& cpve.getReason() == BasicReason.REVOKED) {
|
|
throw cpve;
|
|
}
|
|
vertex.setThrowable(cpve);
|
|
continue vertices;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Remove extensions from user checkers that 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> suppExts =
|
|
checker.getSupportedExtensions();
|
|
if (suppExts != null) {
|
|
unresCritExts.removeAll(suppExts);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (debug != null)
|
|
debug.println("SunCertPathBuilder.depthFirstSearchForward()"
|
|
+ ": final verification succeeded - path completed!");
|
|
pathCompleted = true;
|
|
|
|
/*
|
|
* if the user specified a trusted public key rather than
|
|
* trusted certs, then add this cert (which is signed by
|
|
* the trusted public key) to the cpList
|
|
*/
|
|
if (builder.trustAnchor.getTrustedCert() == null)
|
|
builder.addCertToPath(cert, cpList);
|
|
// Save the trust anchor
|
|
this.trustAnchor = builder.trustAnchor;
|
|
|
|
/*
|
|
* Extract and save the final target public key
|
|
*/
|
|
if (basicChecker != null) {
|
|
finalPublicKey = basicChecker.getPublicKey();
|
|
} else {
|
|
Certificate finalCert;
|
|
if (cpList.isEmpty()) {
|
|
finalCert = builder.trustAnchor.getTrustedCert();
|
|
} else {
|
|
finalCert = cpList.getLast();
|
|
}
|
|
finalPublicKey = finalCert.getPublicKey();
|
|
}
|
|
|
|
policyTreeResult = policyChecker.getPolicyTree();
|
|
return;
|
|
} else {
|
|
builder.addCertToPath(cert, cpList);
|
|
}
|
|
|
|
/* Update the PKIX state */
|
|
nextState.updateState(cert);
|
|
|
|
/*
|
|
* Append an entry for cert in adjacency list and
|
|
* set index for current vertex.
|
|
*/
|
|
adjList.add(new LinkedList<Vertex>());
|
|
vertex.setIndex(adjList.size() - 1);
|
|
|
|
/* recursively search for matching certs at next dN */
|
|
depthFirstSearchForward(cert.getIssuerX500Principal(), nextState,
|
|
builder, adjList, cpList);
|
|
|
|
/*
|
|
* If path has been completed, return ASAP!
|
|
*/
|
|
if (pathCompleted) {
|
|
return;
|
|
} else {
|
|
/*
|
|
* If we get here, it means we have searched all possible
|
|
* certs issued by the dN w/o finding any matching certs.
|
|
* This means we have to backtrack to the previous cert in
|
|
* the path and try some other paths.
|
|
*/
|
|
if (debug != null)
|
|
debug.println("SunCertPathBuilder.depthFirstSearchForward()"
|
|
+ ": backtracking");
|
|
builder.removeFinalCertFromPath(cpList);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Adds a collection of matching certificates to the
|
|
* adjacency list.
|
|
*/
|
|
private static List<Vertex> addVertices(Collection<X509Certificate> certs,
|
|
List<List<Vertex>> adjList)
|
|
{
|
|
List<Vertex> l = adjList.get(adjList.size() - 1);
|
|
|
|
for (X509Certificate cert : certs) {
|
|
Vertex v = new Vertex(cert);
|
|
l.add(v);
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
/**
|
|
* Returns true if trust anchor certificate matches specified
|
|
* certificate constraints.
|
|
*/
|
|
private static boolean anchorIsTarget(TrustAnchor anchor,
|
|
CertSelector sel)
|
|
{
|
|
X509Certificate anchorCert = anchor.getTrustedCert();
|
|
if (anchorCert != null) {
|
|
return sel.match(anchorCert);
|
|
}
|
|
return false;
|
|
}
|
|
}
|