/**
   * Verifies a matching certificate.
   *
   * <p>This method executes any of the validation steps in the PKIX path validation algorithm which
   * were not satisfied via filtering out non-compliant certificates with certificate matching
   * rules.
   *
   * <p>If the last certificate is being verified (the one whose subject matches the target subject,
   * then the steps in Section 6.1.4 of the 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
   */
  void verifyCert(X509Certificate cert, State currState, List certPathList)
      throws GeneralSecurityException {
    if (debug != null)
      debug.println(
          "ReverseBuilder.verifyCert(SN: "
              + Debug.toHexString(cert.getSerialNumber())
              + "\n  Subject: "
              + cert.getSubjectX500Principal()
              + ")");

    ReverseState currentState = (ReverseState) currState;

    /* we don't perform any validation of the trusted cert */
    if (currentState.isInitial()) {
      return;
    }

    /*
     * check for looping - abort a loop if
     * ((we encounter the same certificate twice) AND
     * ((policyMappingInhibited = true) OR (no policy mapping
     * extensions can be found between the occurences of the same
     * certificate)))
     * in order to facilitate the check to see if there are
     * any policy mapping extensions found between the occurences
     * of the same certificate, we reverse the certpathlist first
     */
    if ((certPathList != null) && (!certPathList.isEmpty())) {
      List reverseCertList = new ArrayList();
      Iterator iter = certPathList.iterator();
      while (iter.hasNext()) {
        reverseCertList.add(0, iter.next());
      }

      Iterator cpListIter = reverseCertList.iterator();
      boolean policyMappingFound = false;
      while (cpListIter.hasNext()) {
        X509Certificate cpListCert = (X509Certificate) cpListIter.next();
        X509CertImpl cpListCertImpl = X509CertImpl.toImpl(cpListCert);
        PolicyMappingsExtension policyMappingsExt = cpListCertImpl.getPolicyMappingsExtension();
        if (policyMappingsExt != null) {
          policyMappingFound = true;
        }
        if (debug != null) debug.println("policyMappingFound = " + policyMappingFound);
        if (cert.equals(cpListCert)) {
          if ((buildParams.isPolicyMappingInhibited()) || (!policyMappingFound)) {
            if (debug != null) debug.println("loop detected!!");
            throw new CertPathValidatorException("loop detected");
          }
        }
      }
    }

    /* check if target cert */
    boolean finalCert = cert.getSubjectX500Principal().equals(targetSubjectDN);

    /* check if CA cert */
    boolean caCert = (cert.getBasicConstraints() != -1 ? true : false);

    /* if there are more certs to follow, verify certain constraints */
    if (!finalCert) {

      /* check if CA cert */
      if (!caCert) throw new CertPathValidatorException("cert is NOT a CA cert");

      /* If the certificate was not self-issued, verify that
       * remainingCerts is greater than zero
       */
      if ((currentState.remainingCACerts <= 0) && !X509CertImpl.isSelfIssued(cert)) {
        throw new CertPathValidatorException("pathLenConstraint violated, path too long");
      }

      /*
       * Check keyUsage extension (only if CA cert and not final cert)
       */
      KeyChecker.verifyCAKeyUsage(cert);

    } else {

      /*
       * If final cert, check that it satisfies specified target
       * constraints
       */
      if (targetCertSelector.match(cert) == false) {
        throw new CertPathValidatorException("target certificate " + "constraints check failed");
      }
    }

    /*
     * Check revocation.
     */
    if (buildParams.isRevocationEnabled()) {

      boolean crlSign = currentState.crlChecker.check(cert, currentState.pubKey, true);

      // if this cert can't vouch for the CRL on the next cert, and
      // if this wasn't the last cert in the chain, then we can't
      // keep going from here!
      // NOTE: if we ever add indirect/idp support, this will have
      // to change...
      if ((!crlSign) && (!finalCert))
        throw new CertPathValidatorException("cert can't vouch for crl");
    }

    /* Check name constraints if this is not a self-issued cert */
    if (finalCert || !X509CertImpl.isSelfIssued(cert)) {
      if (currentState.nc != null) {
        try {
          if (!currentState.nc.verify(cert)) {
            throw new CertPathValidatorException("name constraints check failed");
          }
        } catch (IOException ioe) {
          throw new CertPathValidatorException(ioe);
        }
      }
    }

    /*
     * Check policy
     */
    X509CertImpl certImpl = X509CertImpl.toImpl(cert);
    currentState.rootNode =
        PolicyChecker.processPolicies(
            currentState.certIndex,
            initPolicies,
            currentState.explicitPolicy,
            currentState.policyMapping,
            currentState.inhibitAnyPolicy,
            buildParams.getPolicyQualifiersRejected(),
            currentState.rootNode,
            certImpl,
            finalCert);

    /*
     * Check CRITICAL private extensions
     */
    Set unresolvedCritExts = cert.getCriticalExtensionOIDs();
    if (unresolvedCritExts == null) {
      unresolvedCritExts = Collections.EMPTY_SET;
    }
    Iterator i = currentState.userCheckers.iterator();
    while (i.hasNext()) {
      PKIXCertPathChecker checker = (PKIXCertPathChecker) i.next();
      checker.check(cert, unresolvedCritExts);
    }
    /*
     * Look at the remaining extensions and remove any ones we have
     * already checked. If there are any left, throw an exception!
     */
    if (!unresolvedCritExts.isEmpty()) {
      unresolvedCritExts.remove(PKIXExtensions.BasicConstraints_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.NameConstraints_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.CertificatePolicies_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.PolicyMappings_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.PolicyConstraints_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.InhibitAnyPolicy_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.SubjectAlternativeName_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.KeyUsage_Id.toString());
      unresolvedCritExts.remove(PKIXExtensions.ExtendedKeyUsage_Id.toString());

      if (!unresolvedCritExts.isEmpty())
        throw new CertificateException("Unrecognized critical extension(s)");
    }

    /*
     * Check signature.
     */
    if (buildParams.getSigProvider() != null) {
      cert.verify(currentState.pubKey, buildParams.getSigProvider());
    } else {
      cert.verify(currentState.pubKey);
    }
  }
  /*
   * 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);
      }
    }
  }