public void testEmptyPath() throws Exception { CertificateFactory cf = CertificateFactory.getInstance("X.509", "BC"); X509Certificate rootCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(CertPathTest.rootCertBin)); List list = new ArrayList(); list.add(rootCert); CollectionCertStoreParameters ccsp = new CollectionCertStoreParameters(list); CertStore store = CertStore.getInstance("Collection", ccsp, "BC"); List certchain = new ArrayList(); CertPath cp = CertificateFactory.getInstance("X.509", "BC").generateCertPath(certchain); Set trust = new HashSet(); trust.add(new TrustAnchor(rootCert, null)); CertPathValidator cpv = CertPathValidator.getInstance("PKIX", "BC"); PKIXParameters param = new PKIXParameters(trust); param.addCertStore(store); MyChecker checker = new MyChecker(); param.addCertPathChecker(checker); try { cpv.validate(cp, param); } catch (CertPathValidatorException e) { if (!"Certification path is empty.".equals(e.getMessage())) { fail("message mismatch"); } } }
/* * 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); } } }