/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * pkix_validate.c
 *
 * Top level validateChain function
 *
 */

#include "pkix_validate.h"
#include "pkix_pl_common.h"

/* --Private-Functions-------------------------------------------- */

/*
 * FUNCTION: pkix_AddToVerifyLog
 * DESCRIPTION:
 *
 *  This function returns immediately if the address for the VerifyNode tree
 *  pointed to by "pVerifyTree" is NULL. Otherwise it creates a new VerifyNode
 *  from the Cert pointed to by "cert" and the Error pointed to by "error",
 *  and inserts it at the depth in the VerifyNode tree determined by "depth". A
 *  depth of zero means that this function creates the root node of a new tree.
 *
 *  Note: this function does not include the means of choosing among branches
 *  of a tree. It is intended for non-branching trees, that is, where each
 *  parent node has only a single child node.
 *
 * PARAMETERS:
 *  "cert"
 *      The address of the Cert to be included in the new VerifyNode. Must be
 *      non-NULL.
 *  "depth"
 *      The UInt32 value of the depth.
 *  "error"
 *      The address of the Error to be included in the new VerifyNode.
 *  "pVerifyTree"
 *      The address of the VerifyNode tree into which the created VerifyNode
 *      is to be inserted. The node is not created if VerifyTree is NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a Validate Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_AddToVerifyLog(
        PKIX_PL_Cert *cert,
        PKIX_UInt32 depth,
        PKIX_Error *error,
        PKIX_VerifyNode **pVerifyTree,
        void *plContext)
{

        PKIX_VerifyNode *verifyNode = NULL;

        PKIX_ENTER(VALIDATE, "pkix_AddToVerifyLog");
        PKIX_NULLCHECK_ONE(cert);

        if (pVerifyTree) { /* nothing to do if no address given for log */

                PKIX_CHECK(pkix_VerifyNode_Create
                        (cert, depth, error, &verifyNode, plContext),
                        PKIX_VERIFYNODECREATEFAILED);

                if (depth == 0) {
                        /* We just created the root node */
                        *pVerifyTree = verifyNode;
                } else {
                        PKIX_CHECK(pkix_VerifyNode_AddToChain
                                (*pVerifyTree, verifyNode, plContext),
                                PKIX_VERIFYNODEADDTOCHAINFAILED);
                }
        }

cleanup:

        PKIX_RETURN(VALIDATE);

}

/*
 * FUNCTION: pkix_CheckCert
 * DESCRIPTION:
 *
 *  Checks whether the Cert pointed to by "cert" successfully validates
 *  using the List of CertChainCheckers pointed to by "checkers". If the
 *  certificate does not validate, an Error pointer is returned.
 *
 *  This function should be called initially with the UInt32 pointed to by
 *  "pCheckerIndex" containing zero, and the pointer at "pNBIOContext"
 *  containing NULL. If a checker does non-blocking I/O, this function will
 *  return with the index of that checker stored at "pCheckerIndex" and a
 *  platform-dependent non-blocking I/O context stored at "pNBIOContext".
 *  A subsequent call to this function with those values intact will allow the
 *  checking to resume where it left off. This should be repeated until the
 *  function returns with NULL stored at "pNBIOContext".
 *
 * PARAMETERS:
 *  "cert"
 *      Address of Cert to validate. Must be non-NULL.
 *  "checkers"
 *      List of CertChainCheckers which must each validate the certificate.
 *      Must be non-NULL.
 *  "checkedExtOIDs"
 *      List of PKIX_PL_OID that has been processed. If called from building
 *      chain, it is the list of critical extension OIDs that has been
 *      processed prior to validation. May be NULL.
 *  "pCheckerIndex"
 *      Address at which is stored the the index, within the List "checkers",
 *      of a checker whose processing was interrupted by non-blocking I/O.
 *      Must be non-NULL.
 *  "pNBIOContext"
 *      Address at which is stored platform-specific non-blocking I/O context.
 *      Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a Validate Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_CheckCert(
        PKIX_PL_Cert *cert,
        PKIX_List *checkers,
        PKIX_List *checkedExtOIDsList,
        PKIX_UInt32 *pCheckerIndex,
        void **pNBIOContext,
        void *plContext)
{
        PKIX_CertChainChecker_CheckCallback checkerCheck = NULL;
        PKIX_CertChainChecker *checker = NULL;
        PKIX_List *unresCritExtOIDs = NULL;
        PKIX_UInt32 numCheckers;
        PKIX_UInt32 numUnresCritExtOIDs = 0;
        PKIX_UInt32 checkerIndex = 0;
        void *nbioContext = NULL;

        PKIX_ENTER(VALIDATE, "pkix_CheckCert");
        PKIX_NULLCHECK_FOUR(cert, checkers, pCheckerIndex, pNBIOContext);

        nbioContext = *pNBIOContext;
        *pNBIOContext = NULL; /* prepare for case of error exit */

        PKIX_CHECK(PKIX_PL_Cert_GetCriticalExtensionOIDs
                    (cert, &unresCritExtOIDs, plContext),
                    PKIX_CERTGETCRITICALEXTENSIONOIDSFAILED);

        PKIX_CHECK(PKIX_List_GetLength(checkers, &numCheckers, plContext),
                    PKIX_LISTGETLENGTHFAILED);

        for (checkerIndex = *pCheckerIndex;
                checkerIndex < numCheckers;
                checkerIndex++) {

                PKIX_CHECK(PKIX_List_GetItem
                        (checkers,
                        checkerIndex,
                        (PKIX_PL_Object **)&checker,
                        plContext),
                        PKIX_LISTGETITEMFAILED);

                PKIX_CHECK(PKIX_CertChainChecker_GetCheckCallback
                        (checker, &checkerCheck, plContext),
                        PKIX_CERTCHAINCHECKERGETCHECKCALLBACKFAILED);

                PKIX_CHECK(checkerCheck(checker, cert, unresCritExtOIDs,
                                        &nbioContext,  plContext),
                           PKIX_CERTCHAINCHECKERCHECKFAILED);

                if (nbioContext != NULL) {
                        *pCheckerIndex = checkerIndex;
                        *pNBIOContext = nbioContext;
                        goto cleanup;
                }

                PKIX_DECREF(checker);
        }

        if (unresCritExtOIDs){

#ifdef PKIX_VALIDATEDEBUG
                {
                        PKIX_PL_String *oidString = NULL;
                        PKIX_UInt32 length;
                        char *oidAscii = NULL;
                        PKIX_TOSTRING(unresCritExtOIDs, &oidString, plContext,
                                PKIX_LISTTOSTRINGFAILED);
                        PKIX_CHECK(PKIX_PL_String_GetEncoded
                                (oidString,
                                PKIX_ESCASCII,
                                (void **) &oidAscii,
                                &length,
                                plContext),
                                PKIX_STRINGGETENCODEDFAILED);
                        PKIX_VALIDATE_DEBUG_ARG
                                ("unrecognized critical extension OIDs:"
                                " %s\n", oidAscii);
                        PKIX_DECREF(oidString);
                        PKIX_PL_Free(oidAscii, plContext);
                }
#endif

                if (checkedExtOIDsList != NULL) {
                 /* Take out OID's that had been processed, if any */
                        PKIX_CHECK(pkix_List_RemoveItems
                                (unresCritExtOIDs,
                                checkedExtOIDsList,
                                plContext),
                                PKIX_LISTREMOVEITEMSFAILED);
                }

                PKIX_CHECK(PKIX_List_GetLength
                        (unresCritExtOIDs, &numUnresCritExtOIDs, plContext),
                        PKIX_LISTGETLENGTHFAILED);

                if (numUnresCritExtOIDs != 0){
                        PKIX_ERROR(PKIX_UNRECOGNIZEDCRITICALEXTENSION);
                }

        }

cleanup:

        PKIX_DECREF(checker);
        PKIX_DECREF(unresCritExtOIDs);

        PKIX_RETURN(VALIDATE);

}

/*
 * FUNCTION: pkix_InitializeCheckers
 * DESCRIPTION:
 *
 *  Creates several checkers and initializes them with values derived from the
 *  TrustAnchor pointed to by "anchor", the ProcessingParams pointed to by
 *  "procParams", and the number of Certs in the Chain, represented by
 *  "numCerts". The List of checkers is stored at "pCheckers".
 *
 * PARAMETERS:
 *  "anchor"
 *      Address of TrustAnchor used to initialize the SignatureChecker and
 *      NameChainingChecker. Must be non-NULL.
 *  "procParams"
 *      Address of ProcessingParams used to initialize the ExpirationChecker
 *      and TargetCertChecker. Must be non-NULL.
 *  "numCerts"
 *      Number of certificates in the CertChain.
 *  "pCheckers"
 *      Address where object pointer will be stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a Validate Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_InitializeCheckers(
        PKIX_TrustAnchor *anchor,
        PKIX_ProcessingParams *procParams,
        PKIX_UInt32 numCerts,
        PKIX_List **pCheckers,
        void *plContext)
{
        PKIX_CertChainChecker *targetCertChecker = NULL;
        PKIX_CertChainChecker *expirationChecker = NULL;
        PKIX_CertChainChecker *nameChainingChecker = NULL;
        PKIX_CertChainChecker *nameConstraintsChecker = NULL;
        PKIX_CertChainChecker *basicConstraintsChecker = NULL;
        PKIX_CertChainChecker *policyChecker = NULL;
        PKIX_CertChainChecker *sigChecker = NULL;
        PKIX_CertChainChecker *defaultCrlChecker = NULL;
        PKIX_CertChainChecker *userChecker = NULL;
        PKIX_PL_X500Name *trustedCAName = NULL;
        PKIX_PL_PublicKey *trustedPubKey = NULL;
        PKIX_List *checkers = NULL;
        PKIX_PL_Date *testDate = NULL;
        PKIX_CertSelector *certSelector = NULL;
        PKIX_PL_Cert *trustedCert = NULL;
        PKIX_PL_CertNameConstraints *trustedNC = NULL;
        PKIX_List *initialPolicies = NULL;
        PKIX_Boolean policyQualifiersRejected = PKIX_FALSE;
        PKIX_Boolean initialPolicyMappingInhibit = PKIX_FALSE;
        PKIX_Boolean initialAnyPolicyInhibit = PKIX_FALSE;
        PKIX_Boolean initialExplicitPolicy = PKIX_FALSE;
        PKIX_List *userCheckersList = NULL;
        PKIX_List *certStores = NULL;
        PKIX_UInt32 numCertCheckers = 0;
        PKIX_UInt32 i;

        PKIX_ENTER(VALIDATE, "pkix_InitializeCheckers");
        PKIX_NULLCHECK_THREE(anchor, procParams, pCheckers);
        PKIX_CHECK(PKIX_List_Create(&checkers, plContext),
                    PKIX_LISTCREATEFAILED);

        /*
         * The TrustAnchor may have been created using CreateWithCert
         * (in which case GetCAPublicKey and GetCAName will return NULL)
         * or may have been created using CreateWithNameKeyPair (in which
         * case GetTrustedCert will return NULL. So we call GetTrustedCert
         * and populate trustedPubKey and trustedCAName accordingly.
         */

        PKIX_CHECK(PKIX_TrustAnchor_GetTrustedCert
                (anchor, &trustedCert, plContext),
                    PKIX_TRUSTANCHORGETTRUSTEDCERTFAILED);

        if (trustedCert){
                PKIX_CHECK(PKIX_PL_Cert_GetSubjectPublicKey
                            (trustedCert, &trustedPubKey, plContext),
                            PKIX_CERTGETSUBJECTPUBLICKEYFAILED);

                PKIX_CHECK(PKIX_PL_Cert_GetSubject
                            (trustedCert, &trustedCAName, plContext),
                            PKIX_CERTGETSUBJECTFAILED);
        } else {
                PKIX_CHECK(PKIX_TrustAnchor_GetCAPublicKey
                            (anchor, &trustedPubKey, plContext),
                            PKIX_TRUSTANCHORGETCAPUBLICKEYFAILED);

                PKIX_CHECK(PKIX_TrustAnchor_GetCAName
                            (anchor, &trustedCAName, plContext),
                            PKIX_TRUSTANCHORGETCANAMEFAILED);
        }

        PKIX_NULLCHECK_TWO(trustedPubKey, trustedCAName);

        PKIX_CHECK(PKIX_TrustAnchor_GetNameConstraints
                (anchor, &trustedNC, plContext),
                PKIX_TRUSTANCHORGETNAMECONSTRAINTSFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetTargetCertConstraints
                (procParams, &certSelector, plContext),
                PKIX_PROCESSINGPARAMSGETTARGETCERTCONSTRAINTSFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetDate
                (procParams, &testDate, plContext),
                PKIX_PROCESSINGPARAMSGETDATEFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetInitialPolicies
                (procParams, &initialPolicies, plContext),
                PKIX_PROCESSINGPARAMSGETINITIALPOLICIESFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetPolicyQualifiersRejected
                (procParams, &policyQualifiersRejected, plContext),
                PKIX_PROCESSINGPARAMSGETPOLICYQUALIFIERSREJECTEDFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_IsPolicyMappingInhibited
                (procParams, &initialPolicyMappingInhibit, plContext),
                PKIX_PROCESSINGPARAMSISPOLICYMAPPINGINHIBITEDFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_IsAnyPolicyInhibited
                (procParams, &initialAnyPolicyInhibit, plContext),
                PKIX_PROCESSINGPARAMSISANYPOLICYINHIBITEDFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_IsExplicitPolicyRequired
                (procParams, &initialExplicitPolicy, plContext),
                PKIX_PROCESSINGPARAMSISEXPLICITPOLICYREQUIREDFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetCertStores
                (procParams, &certStores, plContext),
                PKIX_PROCESSINGPARAMSGETCERTSTORESFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetCertChainCheckers
                (procParams, &userCheckersList, plContext),
                PKIX_PROCESSINGPARAMSGETCERTCHAINCHECKERSFAILED);

        /* now, initialize all the checkers */
        PKIX_CHECK(pkix_TargetCertChecker_Initialize
                (certSelector, numCerts, &targetCertChecker, plContext),
                PKIX_TARGETCERTCHECKERINITIALIZEFAILED);

        PKIX_CHECK(pkix_ExpirationChecker_Initialize
                (testDate, &expirationChecker, plContext),
                PKIX_EXPIRATIONCHECKERINITIALIZEFAILED);

        PKIX_CHECK(pkix_NameChainingChecker_Initialize
                (trustedCAName, &nameChainingChecker, plContext),
                PKIX_NAMECHAININGCHECKERINITIALIZEFAILED);

        PKIX_CHECK(pkix_NameConstraintsChecker_Initialize
                (trustedNC, numCerts, &nameConstraintsChecker, plContext),
                PKIX_NAMECONSTRAINTSCHECKERINITIALIZEFAILED);

        PKIX_CHECK(pkix_BasicConstraintsChecker_Initialize
                (numCerts, &basicConstraintsChecker, plContext),
                PKIX_BASICCONSTRAINTSCHECKERINITIALIZEFAILED);

        PKIX_CHECK(pkix_PolicyChecker_Initialize
                (initialPolicies,
                policyQualifiersRejected,
                initialPolicyMappingInhibit,
                initialExplicitPolicy,
                initialAnyPolicyInhibit,
                numCerts,
                &policyChecker,
                plContext),
                PKIX_POLICYCHECKERINITIALIZEFAILED);

        PKIX_CHECK(pkix_SignatureChecker_Initialize
                    (trustedPubKey, numCerts, &sigChecker, plContext),
                    PKIX_SIGNATURECHECKERINITIALIZEFAILED);

        if (userCheckersList != NULL) {

                PKIX_CHECK(PKIX_List_GetLength
                    (userCheckersList, &numCertCheckers, plContext),
                    PKIX_LISTGETLENGTHFAILED);

                for (i = 0; i < numCertCheckers; i++) {

                        PKIX_CHECK(PKIX_List_GetItem
                            (userCheckersList,
                            i,
                            (PKIX_PL_Object **) &userChecker,
                            plContext),
                            PKIX_LISTGETITEMFAILED);

                        PKIX_CHECK(PKIX_List_AppendItem
                            (checkers,
                            (PKIX_PL_Object *)userChecker,
                            plContext),
                            PKIX_LISTAPPENDITEMFAILED);

                        PKIX_DECREF(userChecker);
                }
        }

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)targetCertChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)expirationChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)nameChainingChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)nameConstraintsChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)basicConstraintsChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)policyChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
            (checkers, (PKIX_PL_Object *)sigChecker, plContext),
            PKIX_LISTAPPENDITEMFAILED);

        *pCheckers = checkers;

cleanup:

        if (PKIX_ERROR_RECEIVED){
                PKIX_DECREF(checkers);
        }

        PKIX_DECREF(certSelector);
        PKIX_DECREF(testDate);
        PKIX_DECREF(initialPolicies);
        PKIX_DECREF(targetCertChecker);
        PKIX_DECREF(expirationChecker);
        PKIX_DECREF(nameChainingChecker);
        PKIX_DECREF(nameConstraintsChecker);
        PKIX_DECREF(basicConstraintsChecker);
        PKIX_DECREF(policyChecker);
        PKIX_DECREF(sigChecker);
        PKIX_DECREF(trustedCAName);
        PKIX_DECREF(trustedPubKey);
        PKIX_DECREF(trustedNC);
        PKIX_DECREF(trustedCert);
        PKIX_DECREF(defaultCrlChecker);
        PKIX_DECREF(userCheckersList);
        PKIX_DECREF(certStores);
        PKIX_DECREF(userChecker);

        PKIX_RETURN(VALIDATE);
}

/*
 * FUNCTION: pkix_RetrieveOutputs
 * DESCRIPTION:
 *
 *  This function queries the respective states of the List of checkers in
 *  "checkers" to to obtain the final public key from the SignatureChecker
 *  and the policy tree from the PolicyChecker, storing those values at
 *  "pFinalSubjPubKey" and "pPolicyTree", respectively.
 *
 * PARAMETERS:
 *  "checkers"
 *      Address of List of checkers to be queried. Must be non-NULL.
 *  "pFinalSubjPubKey"
 *      Address where final public key will be stored. Must be non-NULL.
 *  "pPolicyTree"
 *      Address where policy tree will be stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a Validate Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_RetrieveOutputs(
        PKIX_List *checkers,
        PKIX_PL_PublicKey **pFinalSubjPubKey,
        PKIX_PolicyNode **pPolicyTree,
        void *plContext)
{
        PKIX_PL_PublicKey *finalSubjPubKey = NULL;
        PKIX_PolicyNode *validPolicyTree = NULL;
        PKIX_CertChainChecker *checker = NULL;
        PKIX_PL_Object *state = NULL;
        PKIX_UInt32 numCheckers = 0;
        PKIX_UInt32 type;
        PKIX_Int32 j;

        PKIX_ENTER(VALIDATE, "pkix_RetrieveOutputs");

        PKIX_NULLCHECK_TWO(checkers, pPolicyTree);

        /*
         * To optimize the search, we guess that the sigChecker is
         * last in the tree and is preceded by the policyChecker. We
         * search toward the front of the chain. Remember that List
         * items are indexed 0..(numItems - 1).
         */

        PKIX_CHECK(PKIX_List_GetLength(checkers, &numCheckers, plContext),
                PKIX_LISTGETLENGTHFAILED);

        for (j = numCheckers - 1; j >= 0; j--){
                PKIX_CHECK(PKIX_List_GetItem
                        (checkers, j, (PKIX_PL_Object **)&checker, plContext),
                        PKIX_LISTGETITEMFAILED);

                PKIX_CHECK(PKIX_CertChainChecker_GetCertChainCheckerState
                        (checker, &state, plContext),
                        PKIX_CERTCHAINCHECKERGETCERTCHAINCHECKERSTATEFAILED);

                /* user defined checker may have no state */
                if (state != NULL) {

                    PKIX_CHECK(PKIX_PL_Object_GetType(state, &type, plContext),
                            PKIX_OBJECTGETTYPEFAILED);

                    if (type == PKIX_SIGNATURECHECKERSTATE_TYPE){
                        /* final pubKey will include any inherited DSA params */
                        finalSubjPubKey =
                            ((pkix_SignatureCheckerState *)state)->
                                prevPublicKey;
                        PKIX_INCREF(finalSubjPubKey);
                        *pFinalSubjPubKey = finalSubjPubKey;
                    }

                    if (type == PKIX_CERTPOLICYCHECKERSTATE_TYPE) {
                        validPolicyTree =
                            ((PKIX_PolicyCheckerState *)state)->validPolicyTree;
                        break;
                    }
                }

                PKIX_DECREF(checker);
                PKIX_DECREF(state);
        }

        PKIX_INCREF(validPolicyTree);
        *pPolicyTree = validPolicyTree;

cleanup:

        PKIX_DECREF(checker);
        PKIX_DECREF(state);

        PKIX_RETURN(VALIDATE);

}

/*
 * FUNCTION: pkix_CheckChain
 * DESCRIPTION:
 *
 *  Checks whether the List of Certs pointed to by "certs", containing
 *  "numCerts" entries, successfully validates using each CertChainChecker in
 *  the List pointed to by "checkers" and has not been revoked, according to any
 *  of the Revocation Checkers in the List pointed to by "revChecker". Checkers
 *  are expected to remove from "removeCheckedExtOIDs" and extensions that they
 *  process. Indices to the certChain and the checkerChain are obtained and
 *  returned in "pCertCheckedIndex" and "pCheckerIndex", respectively. These
 *  should be set to zero prior to the initial call, but may be changed (and
 *  must be supplied on subsequent calls) if processing is suspended for non-
 *  blocking I/O. Each time a Cert passes from being validated by one of the
 *  CertChainCheckers to being checked by a Revocation Checker, the Boolean
 *  stored at "pRevChecking" is changed from FALSE to TRUE. If the Cert is
 *  rejected by a Revocation Checker, its reason code is returned at
 *  "pReasonCode. If the List of Certs successfully validates, the public key i
 *  the final certificate is obtained and stored at "pFinalSubjPubKey" and the
 *  validPolicyTree, which could be NULL, is stored at pPolicyTree. If the List
 *  of Certs fails to validate, an Error pointer is returned.
 *
 *  If "pVerifyTree" is non-NULL, a chain of VerifyNodes is created which
 *  tracks the results of the validation. That is, either each node in the
 *  chain has a NULL Error component, or the last node contains an Error
 *  which indicates why the validation failed.
 *
 *  The number of Certs in the List, represented by "numCerts", is used to
 *  determine which Cert is the final Cert.
 *
 * PARAMETERS:
 *  "certs"
 *      Address of List of Certs to validate. Must be non-NULL.
 *  "numCerts"
 *      Number of certificates in the List of certificates.
 *  "checkers"
 *      List of CertChainCheckers which must each validate the List of
 *      certificates. Must be non-NULL.
 *  "revChecker"
 *      List of RevocationCheckers which must each not reject the List of
 *      certificates. May be empty, but must be non-NULL.
 *  "removeCheckedExtOIDs"
 *      List of PKIX_PL_OID that has been processed. If called from building
 *      chain, it is the list of critical extension OIDs that has been
 *      processed prior to validation. Extension OIDs that may be processed by
 *      user defined checker processes are also in the list. May be NULL.
 *  "procParams"
 *      Address of ProcessingParams used to initialize various checkers. Must
 *      be non-NULL.
 *  "pCertCheckedIndex"
 *      Address where Int32 index to the Cert chain is obtained and
 *      returned. Must be non-NULL.
 *  "pCheckerIndex"
 *      Address where Int32 index to the CheckerChain is obtained and
 *      returned. Must be non-NULL.
 *  "pRevChecking"
 *      Address where Boolean is obtained and returned, indicating, if FALSE,
 *      that CertChainCheckers are being called; or, if TRUE, that RevChecker
 *      are being called. Must be non-NULL.
 *  "pReasonCode"
 *      Address where UInt32 results of revocation checking are stored. Must be
 *      non-NULL.
 *  "pNBIOContext"
 *      Address where platform-dependent context is stored if checking is
 *      suspended for non-blocking I/O. Must be non-NULL.
 *  "pFinalSubjPubKey"
 *      Address where the final public key will be stored. Must be non-NULL.
 *  "pPolicyTree"
 *      Address where the final validPolicyTree is stored. Must be non-NULL.
 *  "pVerifyTree"
 *      Address where a VerifyTree is stored, if non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a Validate Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
PKIX_Error *
pkix_CheckChain(
        PKIX_List *certs,
        PKIX_UInt32 numCerts,
        PKIX_TrustAnchor *anchor,
        PKIX_List *checkers,
        PKIX_RevocationChecker *revChecker,
        PKIX_List *removeCheckedExtOIDs,
        PKIX_ProcessingParams *procParams,
        PKIX_UInt32 *pCertCheckedIndex,
        PKIX_UInt32 *pCheckerIndex,
        PKIX_Boolean *pRevChecking,
        PKIX_UInt32 *pReasonCode,
        void **pNBIOContext,
        PKIX_PL_PublicKey **pFinalSubjPubKey,
        PKIX_PolicyNode **pPolicyTree,
        PKIX_VerifyNode **pVerifyTree,
        void *plContext)
{
        PKIX_UInt32 j = 0;
        PKIX_Boolean revChecking = PKIX_FALSE;
        PKIX_Error *checkCertError = NULL;
        void *nbioContext = NULL;
        PKIX_PL_Cert *cert = NULL;
        PKIX_PL_Cert *issuer = NULL;
        PKIX_PL_NssContext *nssContext = NULL;
        CERTCertList *certList = NULL;
        const CERTChainVerifyCallback *chainVerifyCallback = NULL;
        CERTCertificate *nssCert = NULL;

        PKIX_ENTER(VALIDATE, "pkix_CheckChain");
        PKIX_NULLCHECK_FOUR(certs, checkers, revChecker, pCertCheckedIndex);
        PKIX_NULLCHECK_FOUR(pCheckerIndex, pRevChecking, pReasonCode, anchor);
        PKIX_NULLCHECK_THREE(pNBIOContext, pFinalSubjPubKey, pPolicyTree);

        nbioContext = *pNBIOContext;
        *pNBIOContext = NULL;
        revChecking = *pRevChecking;
        nssContext = (PKIX_PL_NssContext *)plContext;
        chainVerifyCallback = &nssContext->chainVerifyCallback;

        if (chainVerifyCallback->isChainValid != NULL) {
                PRBool chainOK = PR_FALSE; /*assume failure*/
                SECStatus rv;

                certList = CERT_NewCertList();
                if (certList == NULL) {
                        PKIX_ERROR_ALLOC_ERROR();
                }

                /* Add the trust anchor to the list */
                PKIX_CHECK(PKIX_TrustAnchor_GetTrustedCert
                        (anchor, &cert, plContext),
                        PKIX_TRUSTANCHORGETTRUSTEDCERTFAILED);

                PKIX_CHECK(
                        PKIX_PL_Cert_GetCERTCertificate(cert, &nssCert, plContext),
                        PKIX_CERTGETCERTCERTIFICATEFAILED);

                rv = CERT_AddCertToListHead(certList, nssCert);
                if (rv != SECSuccess) {
                        PKIX_ERROR_ALLOC_ERROR();
                }
                /* the certList takes ownership of nssCert on success */
                nssCert = NULL;
                PKIX_DECREF(cert);

                /* Add the rest of the chain to the list */
                for (j = *pCertCheckedIndex; j < numCerts; j++) {
                        PKIX_CHECK(PKIX_List_GetItem(
                                certs, j, (PKIX_PL_Object **)&cert, plContext),
                                PKIX_LISTGETITEMFAILED);

                        PKIX_CHECK(
                                PKIX_PL_Cert_GetCERTCertificate(cert, &nssCert, plContext),
                                PKIX_CERTGETCERTCERTIFICATEFAILED);

                        rv = CERT_AddCertToListHead(certList, nssCert);
                        if (rv != SECSuccess) {
                                PKIX_ERROR_ALLOC_ERROR();
                        }
                        /* the certList takes ownership of nssCert on success */
                        nssCert = NULL;
                        PKIX_DECREF(cert);
                }

                rv = (*chainVerifyCallback->isChainValid)
                     (chainVerifyCallback->isChainValidArg, certList, &chainOK);
                if (rv != SECSuccess) {
                       PKIX_ERROR_FATAL(PKIX_CHAINVERIFYCALLBACKFAILED);
                }

                if (!chainOK) {
                        PKIX_ERROR(PKIX_CHAINVERIFYCALLBACKFAILED);
                }

        }

        PKIX_CHECK(PKIX_TrustAnchor_GetTrustedCert
                (anchor, &cert, plContext),
                   PKIX_TRUSTANCHORGETTRUSTEDCERTFAILED);

        for (j = *pCertCheckedIndex; j < numCerts; j++) {

                PORT_Assert(cert);
                PKIX_DECREF(issuer);
                issuer = cert;
                cert = NULL;

                PKIX_CHECK(PKIX_List_GetItem(
                               certs, j, (PKIX_PL_Object **)&cert, plContext),
                           PKIX_LISTGETITEMFAILED);
                
                /* check if cert pointer is valid */
                PORT_Assert(cert);
                if (cert == NULL) {
                    continue;
                }

                if (revChecking == PKIX_FALSE) {

                        PKIX_CHECK(pkix_CheckCert
                                (cert,
                                checkers,
                                removeCheckedExtOIDs,
                                pCheckerIndex,
                                &nbioContext,
                                plContext),
                                PKIX_CHECKCERTFAILED);

                        if (nbioContext != NULL) {
                                *pCertCheckedIndex = j;
                                *pRevChecking = revChecking;
                                *pNBIOContext = nbioContext;
                                goto cleanup;
                        }

                        revChecking = PKIX_TRUE;
                        *pCheckerIndex = 0;
                }

                if (revChecking == PKIX_TRUE) {
                        PKIX_RevocationStatus revStatus;
                        pkixErrorResult =
                            PKIX_RevocationChecker_Check(
                                      cert, issuer, revChecker,
                                      procParams, PKIX_TRUE,
                                      (j == numCerts - 1) ? PKIX_TRUE : PKIX_FALSE,
                                      &revStatus, pReasonCode,
                                      &nbioContext, plContext);
                        if (nbioContext != NULL) {
                                *pCertCheckedIndex = j;
                                *pRevChecking = revChecking;
                                *pNBIOContext = nbioContext;
                                goto cleanup;
                        }
                        if (revStatus == PKIX_RevStatus_Revoked ||
                            pkixErrorResult) {
                            if (!pkixErrorResult) {
                                /* if pkixErrorResult is returned then
                                 * use it as it has a detailed revocation
                                 * error code. Otherwise create a new error */
                                PKIX_ERROR_CREATE(VALIDATE,
                                                  PKIX_CERTIFICATEREVOKED,
                                                  pkixErrorResult);
                            }
                            goto cleanup;
                        }
                        revChecking = PKIX_FALSE;
                        *pCheckerIndex = 0;
                }

                PKIX_CHECK(pkix_AddToVerifyLog
                        (cert, j, NULL, pVerifyTree, plContext),
                        PKIX_ADDTOVERIFYLOGFAILED);
        }

        PKIX_CHECK(pkix_RetrieveOutputs
                    (checkers, pFinalSubjPubKey, pPolicyTree, plContext),
                    PKIX_RETRIEVEOUTPUTSFAILED);

        *pNBIOContext = NULL;

cleanup:
        if (PKIX_ERROR_RECEIVED && cert) {
            checkCertError = pkixErrorResult;
            
            PKIX_CHECK_FATAL(
                pkix_AddToVerifyLog(cert, j, checkCertError, pVerifyTree,
                                    plContext),
                PKIX_ADDTOVERIFYLOGFAILED);
            pkixErrorResult = checkCertError;
            pkixErrorCode = pkixErrorResult->errCode;
            checkCertError = NULL;
        }

fatal:
        if (nssCert) {
                CERT_DestroyCertificate(nssCert);
        }

        if (certList) {
                CERT_DestroyCertList(certList);
        }

        PKIX_DECREF(checkCertError);
        PKIX_DECREF(cert);
        PKIX_DECREF(issuer);

        PKIX_RETURN(VALIDATE);
}

/*
 * FUNCTION: pkix_ExtractParameters
 * DESCRIPTION:
 *
 *  Extracts several parameters from the ValidateParams object pointed to by
 *  "valParams" and stores the CertChain at "pChain", the List of Certs at
 *  "pCerts", the number of Certs in the chain at "pNumCerts", the
 *  ProcessingParams object at "pProcParams", the List of TrustAnchors at
 *  "pAnchors", and the number of TrustAnchors at "pNumAnchors".
 *
 * PARAMETERS:
 *  "valParams"
 *      Address of ValidateParams from which the parameters are extracted.
 *      Must be non-NULL.
 *  "pCerts"
 *      Address where object pointer for List of Certs will be stored.
 *      Must be non-NULL.
 *  "pNumCerts"
 *      Address where number of Certs will be stored. Must be non-NULL.
 *  "pProcParams"
 *      Address where object pointer for ProcessingParams will be stored.
 *      Must be non-NULL.
 *  "pAnchors"
 *      Address where object pointer for List of Anchors will be stored.
 *      Must be non-NULL.
 *  "pNumAnchors"
 *      Address where number of Anchors will be stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a Validate Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_ExtractParameters(
        PKIX_ValidateParams *valParams,
        PKIX_List **pCerts,
        PKIX_UInt32 *pNumCerts,
        PKIX_ProcessingParams **pProcParams,
        PKIX_List **pAnchors,
        PKIX_UInt32 *pNumAnchors,
        void *plContext)
{
        PKIX_ENTER(VALIDATE, "pkix_ExtractParameters");
        PKIX_NULLCHECK_THREE(valParams, pCerts, pNumCerts);
        PKIX_NULLCHECK_THREE(pProcParams, pAnchors, pNumAnchors);

        /* extract relevant parameters from chain */
        PKIX_CHECK(PKIX_ValidateParams_GetCertChain
                (valParams, pCerts, plContext),
                PKIX_VALIDATEPARAMSGETCERTCHAINFAILED);

        PKIX_CHECK(PKIX_List_GetLength(*pCerts, pNumCerts, plContext),
                PKIX_LISTGETLENGTHFAILED);

        /* extract relevant parameters from procParams */
        PKIX_CHECK(PKIX_ValidateParams_GetProcessingParams
                (valParams, pProcParams, plContext),
                PKIX_VALIDATEPARAMSGETPROCESSINGPARAMSFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetTrustAnchors
                (*pProcParams, pAnchors, plContext),
                PKIX_PROCESSINGPARAMSGETTRUSTANCHORSFAILED);

        PKIX_CHECK(PKIX_List_GetLength(*pAnchors, pNumAnchors, plContext),
                PKIX_LISTGETLENGTHFAILED);

cleanup:

        PKIX_RETURN(VALIDATE);
}

/* --Public-Functions--------------------------------------------- */

/*
 * FUNCTION: PKIX_ValidateChain (see comments in pkix.h)
 */
PKIX_Error *
PKIX_ValidateChain(
        PKIX_ValidateParams *valParams,
        PKIX_ValidateResult **pResult,
        PKIX_VerifyNode **pVerifyTree,
        void *plContext)
{
        PKIX_Error *chainFailed = NULL;

        PKIX_ProcessingParams *procParams = NULL;
        PKIX_CertChainChecker *userChecker = NULL;
        PKIX_RevocationChecker *revChecker = NULL;
        PKIX_List *certs = NULL;
        PKIX_List *checkers = NULL;
        PKIX_List *anchors = NULL;
        PKIX_List *userCheckers = NULL;
        PKIX_List *userCheckerExtOIDs = NULL;
        PKIX_List *validateCheckedCritExtOIDsList = NULL;
        PKIX_TrustAnchor *anchor = NULL;
        PKIX_ValidateResult *valResult = NULL;
        PKIX_PL_PublicKey *finalPubKey = NULL;
        PKIX_PolicyNode *validPolicyTree = NULL;
        PKIX_Boolean supportForwarding = PKIX_FALSE;
        PKIX_Boolean revChecking = PKIX_FALSE;
        PKIX_UInt32 i, numCerts, numAnchors;
        PKIX_UInt32 numUserCheckers = 0;
        PKIX_UInt32 certCheckedIndex = 0;
        PKIX_UInt32 checkerIndex = 0;
        PKIX_UInt32 reasonCode = 0;
        void *nbioContext = NULL;

        PKIX_ENTER(VALIDATE, "PKIX_ValidateChain");
        PKIX_NULLCHECK_TWO(valParams, pResult);

        /* extract various parameters from valParams */
        PKIX_CHECK(pkix_ExtractParameters
                    (valParams,
                    &certs,
                    &numCerts,
                    &procParams,
                    &anchors,
                    &numAnchors,
                    plContext),
                    PKIX_EXTRACTPARAMETERSFAILED);

        /*
         * setup an extension OID list that user had defined for his checker
         * processing. User checker is not responsible for taking out OIDs
         * from unresolved critical extension list as the libpkix checker
         * is doing. Here we add those user checkers' OIDs to the removal
         * list to be taken out by CheckChain
         */
        PKIX_CHECK(PKIX_ProcessingParams_GetCertChainCheckers
                    (procParams, &userCheckers, plContext),
                    PKIX_PROCESSINGPARAMSGETCERTCHAINCHECKERSFAILED);

        if (userCheckers != NULL) {

                PKIX_CHECK(PKIX_List_Create
                    (&validateCheckedCritExtOIDsList,
                    plContext),
                    PKIX_LISTCREATEFAILED);

                PKIX_CHECK(PKIX_List_GetLength
                    (userCheckers, &numUserCheckers, plContext),
                    PKIX_LISTGETLENGTHFAILED);

                for (i = 0; i < numUserCheckers; i++) {

                    PKIX_CHECK(PKIX_List_GetItem
                        (userCheckers,
                        i,
                        (PKIX_PL_Object **) &userChecker,
                        plContext),
                        PKIX_LISTGETITEMFAILED);

                    PKIX_CHECK
                        (PKIX_CertChainChecker_IsForwardCheckingSupported
                        (userChecker, &supportForwarding, plContext),
                        PKIX_CERTCHAINCHECKERISFORWARDCHECKINGSUPPORTEDFAILED);

                    if (supportForwarding == PKIX_FALSE) {

                        PKIX_CHECK
                            (PKIX_CertChainChecker_GetSupportedExtensions
                            (userChecker, &userCheckerExtOIDs, plContext),
                            PKIX_CERTCHAINCHECKERGETSUPPORTEDEXTENSIONSFAILED);

                        if (userCheckerExtOIDs != NULL) {
                            PKIX_CHECK(pkix_List_AppendList
                                (validateCheckedCritExtOIDsList,
                                userCheckerExtOIDs,
                                plContext),
                                PKIX_LISTAPPENDLISTFAILED);
                        }
                    }

                    PKIX_DECREF(userCheckerExtOIDs);
                    PKIX_DECREF(userChecker);
                }
        }

        PKIX_CHECK(PKIX_ProcessingParams_GetRevocationChecker
                (procParams, &revChecker, plContext),
                PKIX_PROCESSINGPARAMSGETREVOCATIONCHECKERFAILED);

        /* try to validate the chain with each anchor */
        for (i = 0; i < numAnchors; i++){

                /* get trust anchor */
                PKIX_CHECK(PKIX_List_GetItem
                        (anchors, i, (PKIX_PL_Object **)&anchor, plContext),
                        PKIX_LISTGETITEMFAILED);

                /* initialize checkers using information from trust anchor */
                PKIX_CHECK(pkix_InitializeCheckers
                        (anchor, procParams, numCerts, &checkers, plContext),
                        PKIX_INITIALIZECHECKERSFAILED);

                /*
                 * Validate the chain using this trust anchor and these
                 * checkers. (WARNING: checkers that use non-blocking I/O
                 * are not currently supported.)
                 */
                certCheckedIndex = 0;
                checkerIndex = 0;
                revChecking = PKIX_FALSE;
                chainFailed = pkix_CheckChain
                        (certs,
                        numCerts,
                        anchor,
                        checkers,
                        revChecker,
                        validateCheckedCritExtOIDsList,
                        procParams,
                        &certCheckedIndex,
                        &checkerIndex,
                        &revChecking,
                        &reasonCode,
                        &nbioContext,
                        &finalPubKey,
                        &validPolicyTree,
                        pVerifyTree,
                        plContext);

                if (chainFailed) {

                        /* cert chain failed to validate */

                        PKIX_DECREF(chainFailed);
                        PKIX_DECREF(anchor);
                        PKIX_DECREF(checkers);
                        PKIX_DECREF(validPolicyTree);

                        /* if last anchor, we fail; else, we try next anchor */
                        if (i == (numAnchors - 1)) { /* last anchor */
                                PKIX_ERROR(PKIX_VALIDATECHAINFAILED);
                        }

                } else {

                        /* XXX Remove this assertion after 2014-12-31.
                         * See bug 946984. */
                        PORT_Assert(reasonCode == 0);

                        /* cert chain successfully validated! */
                        PKIX_CHECK(pkix_ValidateResult_Create
                                (finalPubKey,
                                anchor,
                                validPolicyTree,
                                &valResult,
                                plContext),
                                PKIX_VALIDATERESULTCREATEFAILED);

                        *pResult = valResult;

                        /* no need to try any more anchors in the loop */
                        goto cleanup;
                }
        }

cleanup:

        PKIX_DECREF(finalPubKey);
        PKIX_DECREF(certs);
        PKIX_DECREF(anchors);
        PKIX_DECREF(anchor);
        PKIX_DECREF(checkers);
        PKIX_DECREF(revChecker);
        PKIX_DECREF(validPolicyTree);
        PKIX_DECREF(chainFailed);
        PKIX_DECREF(procParams);
        PKIX_DECREF(userCheckers);
        PKIX_DECREF(validateCheckedCritExtOIDsList);

        PKIX_RETURN(VALIDATE);
}

/*
 * FUNCTION: pkix_Validate_BuildUserOIDs
 * DESCRIPTION:
 *
 *  This function creates a List of the OIDs that are processed by the user
 *  checkers in the List pointed to by "userCheckers", storing the resulting
 *  List at "pUserCritOIDs". If the List of userCheckers is NULL, the output
 *  List will be NULL. Otherwise the output List will be non-NULL, but may be
 *  empty.
 *
 * PARAMETERS:
 *  "userCheckers"
 *      The address of the List of userCheckers.
 *  "pUserCritOIDs"
 *      The address at which the List is stored. Must be non-NULL.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds.
 *  Returns a VALIDATE Error if the function fails in a non-fatal way.
 *  Returns a Fatal Error if the function fails in an unrecoverable way.
 */
static PKIX_Error *
pkix_Validate_BuildUserOIDs(
        PKIX_List *userCheckers,
        PKIX_List **pUserCritOIDs,
        void *plContext)
{
        PKIX_UInt32 numUserCheckers = 0;
        PKIX_UInt32 i = 0;
        PKIX_List *userCritOIDs = NULL;
        PKIX_List *userCheckerExtOIDs = NULL;
        PKIX_Boolean supportForwarding = PKIX_FALSE;
        PKIX_CertChainChecker *userChecker = NULL;

        PKIX_ENTER(VALIDATE, "pkix_Validate_BuildUserOIDs");
        PKIX_NULLCHECK_ONE(pUserCritOIDs);

        if (userCheckers != NULL) {
            PKIX_CHECK(PKIX_List_Create(&userCritOIDs, plContext),
                PKIX_LISTCREATEFAILED);

            PKIX_CHECK(PKIX_List_GetLength
                (userCheckers, &numUserCheckers, plContext),
                PKIX_LISTGETLENGTHFAILED);

            for (i = 0; i < numUserCheckers; i++) {
                PKIX_CHECK(PKIX_List_GetItem
                    (userCheckers,
                    i,
                    (PKIX_PL_Object **) &userChecker,
                    plContext),
                    PKIX_LISTGETITEMFAILED);

                PKIX_CHECK(PKIX_CertChainChecker_IsForwardCheckingSupported
                    (userChecker, &supportForwarding, plContext),
                    PKIX_CERTCHAINCHECKERISFORWARDCHECKINGSUPPORTEDFAILED);

                if (supportForwarding == PKIX_FALSE) {

                    PKIX_CHECK(PKIX_CertChainChecker_GetSupportedExtensions
                        (userChecker, &userCheckerExtOIDs, plContext),
                        PKIX_CERTCHAINCHECKERGETSUPPORTEDEXTENSIONSFAILED);

                    if (userCheckerExtOIDs != NULL) {
                        PKIX_CHECK(pkix_List_AppendList
                            (userCritOIDs, userCheckerExtOIDs, plContext),
                            PKIX_LISTAPPENDLISTFAILED);
                    }
                }

                PKIX_DECREF(userCheckerExtOIDs);
                PKIX_DECREF(userChecker);
            }
        }

        *pUserCritOIDs = userCritOIDs;

cleanup:

        if (PKIX_ERROR_RECEIVED){
                PKIX_DECREF(userCritOIDs);
        }

        PKIX_DECREF(userCheckerExtOIDs);
        PKIX_DECREF(userChecker);

        PKIX_RETURN(VALIDATE);
}

/*
 * FUNCTION: PKIX_ValidateChain_nb (see comments in pkix.h)
 */
PKIX_Error *
PKIX_ValidateChain_NB(
        PKIX_ValidateParams *valParams,
        PKIX_UInt32 *pCertIndex,
        PKIX_UInt32 *pAnchorIndex,
        PKIX_UInt32 *pCheckerIndex,
        PKIX_Boolean *pRevChecking,
        PKIX_List **pCheckers,
        void **pNBIOContext,
        PKIX_ValidateResult **pResult,
        PKIX_VerifyNode **pVerifyTree,
        void *plContext)
{
        PKIX_UInt32 numCerts = 0;
        PKIX_UInt32 numAnchors = 0;
        PKIX_UInt32 i = 0;
        PKIX_UInt32 certIndex = 0;
        PKIX_UInt32 anchorIndex = 0;
        PKIX_UInt32 checkerIndex = 0;
        PKIX_UInt32 reasonCode = 0;
        PKIX_Boolean revChecking = PKIX_FALSE;
        PKIX_List *certs = NULL;
        PKIX_List *anchors = NULL;
        PKIX_List *checkers = NULL;
        PKIX_List *userCheckers = NULL;
        PKIX_List *validateCheckedCritExtOIDsList = NULL;
        PKIX_TrustAnchor *anchor = NULL;
        PKIX_ValidateResult *valResult = NULL;
        PKIX_PL_PublicKey *finalPubKey = NULL;
        PKIX_PolicyNode *validPolicyTree = NULL;
        PKIX_ProcessingParams *procParams = NULL;
        PKIX_RevocationChecker *revChecker = NULL;
        PKIX_Error *chainFailed = NULL;
        void *nbioContext = NULL;

        PKIX_ENTER(VALIDATE, "PKIX_ValidateChain_NB");
        PKIX_NULLCHECK_FOUR
                (valParams, pCertIndex, pAnchorIndex, pCheckerIndex);
        PKIX_NULLCHECK_FOUR(pRevChecking, pCheckers, pNBIOContext, pResult);

        nbioContext = *pNBIOContext;
        *pNBIOContext = NULL;

        /* extract various parameters from valParams */
        PKIX_CHECK(pkix_ExtractParameters
                    (valParams,
                    &certs,
                    &numCerts,
                    &procParams,
                    &anchors,
                    &numAnchors,
                    plContext),
                    PKIX_EXTRACTPARAMETERSFAILED);

        /*
         * Create a List of the OIDs that will be processed by the user
         * checkers. User checkers are not responsible for removing OIDs from
         * the List of unresolved critical extensions, as libpkix checkers are.
         * So we add those user checkers' OIDs to the removal list to be taken
         * out by CheckChain.
         */
        PKIX_CHECK(PKIX_ProcessingParams_GetCertChainCheckers
                (procParams, &userCheckers, plContext),
                PKIX_PROCESSINGPARAMSGETCERTCHAINCHECKERSFAILED);

        PKIX_CHECK(pkix_Validate_BuildUserOIDs
                (userCheckers, &validateCheckedCritExtOIDsList, plContext),
                PKIX_VALIDATEBUILDUSEROIDSFAILED);

        PKIX_CHECK(PKIX_ProcessingParams_GetRevocationChecker
                (procParams, &revChecker, plContext),
                PKIX_PROCESSINGPARAMSGETREVOCATIONCHECKERFAILED);

        /* Are we resuming after a WOULDBLOCK return, or starting anew ? */
        if (nbioContext != NULL) {
                /* Resuming */
                certIndex = *pCertIndex;
                anchorIndex = *pAnchorIndex;
                checkerIndex = *pCheckerIndex;
                revChecking = *pRevChecking;
                checkers = *pCheckers;
                *pCheckers = NULL;
        }

        /* try to validate the chain with each anchor */
        for (i = anchorIndex; i < numAnchors; i++) {

                /* get trust anchor */
                PKIX_CHECK(PKIX_List_GetItem
                        (anchors, i, (PKIX_PL_Object **)&anchor, plContext),
                        PKIX_LISTGETITEMFAILED);

                /* initialize checkers using information from trust anchor */
                if (nbioContext == NULL) {
                        PKIX_CHECK(pkix_InitializeCheckers
                                (anchor,
                                procParams,
                                numCerts,
                                &checkers,
                                plContext),
                                PKIX_INITIALIZECHECKERSFAILED);
                }

                /*
                 * Validate the chain using this trust anchor and these
                 * checkers.
                 */
                chainFailed = pkix_CheckChain
                        (certs,
                        numCerts,
                        anchor,
                        checkers,
                        revChecker,
                        validateCheckedCritExtOIDsList,
                        procParams,
                        &certIndex,
                        &checkerIndex,
                        &revChecking,
                        &reasonCode,
                        &nbioContext,
                        &finalPubKey,
                        &validPolicyTree,
                        pVerifyTree,
                        plContext);

                if (nbioContext != NULL) {
                        *pCertIndex = certIndex;
                        *pAnchorIndex = anchorIndex;
                        *pCheckerIndex = checkerIndex;
                        *pRevChecking = revChecking;
                        PKIX_INCREF(checkers);
                        *pCheckers = checkers;
                        *pNBIOContext = nbioContext;
                        goto cleanup;
                }

                if (chainFailed) {

                        /* cert chain failed to validate */

                        PKIX_DECREF(chainFailed);
                        PKIX_DECREF(anchor);
                        PKIX_DECREF(checkers);
                        PKIX_DECREF(validPolicyTree);

                        /* if last anchor, we fail; else, we try next anchor */
                        if (i == (numAnchors - 1)) { /* last anchor */
                                PKIX_ERROR(PKIX_VALIDATECHAINFAILED);
                        }

                } else {

                        /* XXX Remove this assertion after 2014-12-31.
                         * See bug 946984. */
                        PORT_Assert(reasonCode == 0);

                        /* cert chain successfully validated! */
                        PKIX_CHECK(pkix_ValidateResult_Create
                                (finalPubKey,
                                anchor,
                                validPolicyTree,
                                &valResult,
                                plContext),
                                PKIX_VALIDATERESULTCREATEFAILED);

                        *pResult = valResult;

                        /* no need to try any more anchors in the loop */
                        goto cleanup;
                }
        }

cleanup:

        PKIX_DECREF(finalPubKey);
        PKIX_DECREF(certs);
        PKIX_DECREF(anchors);
        PKIX_DECREF(anchor);
        PKIX_DECREF(checkers);
        PKIX_DECREF(revChecker);
        PKIX_DECREF(validPolicyTree);
        PKIX_DECREF(chainFailed);
        PKIX_DECREF(procParams);
        PKIX_DECREF(userCheckers);
        PKIX_DECREF(validateCheckedCritExtOIDsList);

        PKIX_RETURN(VALIDATE);
}
