/* 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/. */

#ifndef NSSPKI_H
#include "nsspki.h"
#endif /* NSSPKI_H */

#ifndef PKIT_H
#include "pkit.h"
#endif /* PKIT_H */

#ifndef PKIM_H
#include "pkim.h"
#endif /* PKIM_H */

#ifndef DEV_H
#include "dev.h"
#endif /* DEV_H */

#include "pkistore.h"

#include "pki3hack.h"
#include "pk11func.h"
#include "hasht.h"

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

extern const NSSError NSS_ERROR_NOT_FOUND;

/* Creates a certificate from a base object */
NSS_IMPLEMENT NSSCertificate *
nssCertificate_Create(
    nssPKIObject *object)
{
    PRStatus status;
    NSSCertificate *rvCert;
    nssArenaMark *mark;
    NSSArena *arena = object->arena;
    PR_ASSERT(object->instances != NULL && object->numInstances > 0);
    PR_ASSERT(object->lockType == nssPKIMonitor);
    mark = nssArena_Mark(arena);
    rvCert = nss_ZNEW(arena, NSSCertificate);
    if (!rvCert) {
        return (NSSCertificate *)NULL;
    }
    rvCert->object = *object;
    /* XXX should choose instance based on some criteria */
    status = nssCryptokiCertificate_GetAttributes(object->instances[0],
                                                  NULL, /* XXX sessionOpt */
                                                  arena,
                                                  &rvCert->type,
                                                  &rvCert->id,
                                                  &rvCert->encoding,
                                                  &rvCert->issuer,
                                                  &rvCert->serial,
                                                  &rvCert->subject);
    if (status != PR_SUCCESS ||
        !rvCert->encoding.data ||
        !rvCert->encoding.size ||
        !rvCert->issuer.data ||
        !rvCert->issuer.size ||
        !rvCert->serial.data ||
        !rvCert->serial.size) {
        if (mark)
            nssArena_Release(arena, mark);
        return (NSSCertificate *)NULL;
    }
    if (mark)
        nssArena_Unmark(arena, mark);
    return rvCert;
}

NSS_IMPLEMENT NSSCertificate *
nssCertificate_AddRef(
    NSSCertificate *c)
{
    if (c) {
        nssPKIObject_AddRef(&c->object);
    }
    return c;
}

NSS_IMPLEMENT PRStatus
nssCertificate_Destroy(
    NSSCertificate *c)
{
    nssCertificateStoreTrace lockTrace = { NULL, NULL, PR_FALSE, PR_FALSE };
    nssCertificateStoreTrace unlockTrace = { NULL, NULL, PR_FALSE, PR_FALSE };

    if (c) {
        PRUint32 i;
        nssDecodedCert *dc = c->decoding;
        NSSTrustDomain *td = STAN_GetDefaultTrustDomain();
        NSSCryptoContext *cc = c->object.cryptoContext;

        PR_ASSERT(c->object.refCount > 0);

        /* --- LOCK storage --- */
        if (cc) {
            nssCertificateStore_Lock(cc->certStore, &lockTrace);
        } else {
            nssTrustDomain_LockCertCache(td);
        }
        if (PR_ATOMIC_DECREMENT(&c->object.refCount) == 0) {
            /* --- remove cert and UNLOCK storage --- */
            if (cc) {
                nssCertificateStore_RemoveCertLOCKED(cc->certStore, c);
                nssCertificateStore_Unlock(cc->certStore, &lockTrace,
                                           &unlockTrace);
            } else {
                nssTrustDomain_RemoveCertFromCacheLOCKED(td, c);
                nssTrustDomain_UnlockCertCache(td);
            }
            /* free cert data */
            for (i = 0; i < c->object.numInstances; i++) {
                nssCryptokiObject_Destroy(c->object.instances[i]);
            }
            nssPKIObject_DestroyLock(&c->object);
            nssArena_Destroy(c->object.arena);
            nssDecodedCert_Destroy(dc);
        } else {
            /* --- UNLOCK storage --- */
            if (cc) {
                nssCertificateStore_Unlock(cc->certStore,
                                           &lockTrace,
                                           &unlockTrace);
            } else {
                nssTrustDomain_UnlockCertCache(td);
            }
        }
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
NSSCertificate_Destroy(NSSCertificate *c)
{
    return nssCertificate_Destroy(c);
}

NSS_IMPLEMENT NSSDER *
nssCertificate_GetEncoding(NSSCertificate *c)
{
    if (c->encoding.size > 0 && c->encoding.data) {
        return &c->encoding;
    } else {
        return (NSSDER *)NULL;
    }
}

NSS_IMPLEMENT NSSDER *
nssCertificate_GetIssuer(NSSCertificate *c)
{
    if (c->issuer.size > 0 && c->issuer.data) {
        return &c->issuer;
    } else {
        return (NSSDER *)NULL;
    }
}

NSS_IMPLEMENT NSSDER *
nssCertificate_GetSerialNumber(NSSCertificate *c)
{
    if (c->serial.size > 0 && c->serial.data) {
        return &c->serial;
    } else {
        return (NSSDER *)NULL;
    }
}

NSS_IMPLEMENT NSSDER *
nssCertificate_GetSubject(NSSCertificate *c)
{
    if (c->subject.size > 0 && c->subject.data) {
        return &c->subject;
    } else {
        return (NSSDER *)NULL;
    }
}

/* Returns a copy, Caller must free using nss_ZFreeIf */
NSS_IMPLEMENT NSSUTF8 *
nssCertificate_GetNickname(
    NSSCertificate *c,
    NSSToken *tokenOpt)
{
    return nssPKIObject_GetNicknameForToken(&c->object, tokenOpt);
}

NSS_IMPLEMENT NSSASCII7 *
nssCertificate_GetEmailAddress(NSSCertificate *c)
{
    return c->email;
}

NSS_IMPLEMENT PRStatus
NSSCertificate_DeleteStoredObject(
    NSSCertificate *c,
    NSSCallback *uhh)
{
    return nssPKIObject_DeleteStoredObject(&c->object, uhh, PR_TRUE);
}

NSS_IMPLEMENT PRStatus
NSSCertificate_Validate(
    NSSCertificate *c,
    NSSTime *timeOpt, /* NULL for "now" */
    NSSUsage *usage,
    NSSPolicies *policiesOpt /* NULL for none */
)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return PR_FAILURE;
}

NSS_IMPLEMENT void ** /* void *[] */
NSSCertificate_ValidateCompletely(
    NSSCertificate *c,
    NSSTime *timeOpt, /* NULL for "now" */
    NSSUsage *usage,
    NSSPolicies *policiesOpt, /* NULL for none */
    void **rvOpt,             /* NULL for allocate */
    PRUint32 rvLimit,         /* zero for no limit */
    NSSArena *arenaOpt        /* NULL for heap */
)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT PRStatus
NSSCertificate_ValidateAndDiscoverUsagesAndPolicies(
    NSSCertificate *c,
    NSSTime **notBeforeOutOpt,
    NSSTime **notAfterOutOpt,
    void *allowedUsages,
    void *disallowedUsages,
    void *allowedPolicies,
    void *disallowedPolicies,
    /* more args.. work on this fgmr */
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return PR_FAILURE;
}

NSS_IMPLEMENT NSSDER *
NSSCertificate_Encode(
    NSSCertificate *c,
    NSSDER *rvOpt,
    NSSArena *arenaOpt)
{
    /* Item, DER, BER are all typedefs now... */
    return nssItem_Duplicate((NSSItem *)&c->encoding, arenaOpt, rvOpt);
}

NSS_IMPLEMENT nssDecodedCert *
nssCertificate_GetDecoding(
    NSSCertificate *c)
{
    nssDecodedCert *deco = NULL;
    if (c->type == NSSCertificateType_PKIX) {
        (void)STAN_GetCERTCertificate(c);
    }
    nssPKIObject_Lock(&c->object);
    if (!c->decoding) {
        deco = nssDecodedCert_Create(NULL, &c->encoding, c->type);
        PORT_Assert(!c->decoding);
        c->decoding = deco;
    } else {
        deco = c->decoding;
    }
    nssPKIObject_Unlock(&c->object);
    return deco;
}

static NSSCertificate **
filter_subject_certs_for_id(
    NSSCertificate **subjectCerts,
    void *id)
{
    NSSCertificate **si;
    nssDecodedCert *dcp;
    int nextOpenSlot = 0;
    int i;
    nssCertIDMatch matchLevel = nssCertIDMatch_Unknown;
    nssCertIDMatch match;

    /* walk the subject certs */
    for (si = subjectCerts; *si; si++) {
        dcp = nssCertificate_GetDecoding(*si);
        if (!dcp) {
            NSSCertificate_Destroy(*si);
            continue;
        }
        match = dcp->matchIdentifier(dcp, id);
        switch (match) {
            case nssCertIDMatch_Yes:
                if (matchLevel == nssCertIDMatch_Unknown) {
                    /* we have non-definitive matches, forget them */
                    for (i = 0; i < nextOpenSlot; i++) {
                        NSSCertificate_Destroy(subjectCerts[i]);
                        subjectCerts[i] = NULL;
                    }
                    nextOpenSlot = 0;
                    /* only keep definitive matches from now on */
                    matchLevel = nssCertIDMatch_Yes;
                }
                /* keep the cert */
                subjectCerts[nextOpenSlot++] = *si;
                break;
            case nssCertIDMatch_Unknown:
                if (matchLevel == nssCertIDMatch_Unknown) {
                    /* only have non-definitive matches so far, keep it */
                    subjectCerts[nextOpenSlot++] = *si;
                    break;
                }
            /* else fall through, we have a definitive match already */
            case nssCertIDMatch_No:
            default:
                NSSCertificate_Destroy(*si);
                *si = NULL;
        }
    }
    subjectCerts[nextOpenSlot] = NULL;
    return subjectCerts;
}

static NSSCertificate **
filter_certs_for_valid_issuers(NSSCertificate **certs)
{
    NSSCertificate **cp;
    nssDecodedCert *dcp;
    int nextOpenSlot = 0;

    for (cp = certs; *cp; cp++) {
        dcp = nssCertificate_GetDecoding(*cp);
        if (dcp && dcp->isValidIssuer(dcp)) {
            certs[nextOpenSlot++] = *cp;
        } else {
            NSSCertificate_Destroy(*cp);
        }
    }
    certs[nextOpenSlot] = NULL;
    return certs;
}

static NSSCertificate *
find_cert_issuer(
    NSSCertificate *c,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSTrustDomain *td,
    NSSCryptoContext *cc)
{
    NSSArena *arena;
    NSSCertificate **certs = NULL;
    NSSCertificate **ccIssuers = NULL;
    NSSCertificate **tdIssuers = NULL;
    NSSCertificate *issuer = NULL;

    if (!cc)
        cc = c->object.cryptoContext;
    if (!td)
        td = NSSCertificate_GetTrustDomain(c);
    arena = nssArena_Create();
    if (!arena) {
        return (NSSCertificate *)NULL;
    }
    if (cc) {
        ccIssuers = nssCryptoContext_FindCertificatesBySubject(cc,
                                                               &c->issuer,
                                                               NULL,
                                                               0,
                                                               arena);
    }
    if (td)
        tdIssuers = nssTrustDomain_FindCertificatesBySubject(td,
                                                             &c->issuer,
                                                             NULL,
                                                             0,
                                                             arena);
    certs = nssCertificateArray_Join(ccIssuers, tdIssuers);
    if (certs) {
        nssDecodedCert *dc = NULL;
        void *issuerID = NULL;
        dc = nssCertificate_GetDecoding(c);
        if (dc) {
            issuerID = dc->getIssuerIdentifier(dc);
        }
        /* XXX review based on CERT_FindCertIssuer
         * this function is not using the authCertIssuer field as a fallback
         * if authority key id does not exist
         */
        if (issuerID) {
            certs = filter_subject_certs_for_id(certs, issuerID);
        }
        certs = filter_certs_for_valid_issuers(certs);
        issuer = nssCertificateArray_FindBestCertificate(certs,
                                                         timeOpt,
                                                         usage,
                                                         policiesOpt);
        nssCertificateArray_Destroy(certs);
    }
    nssArena_Destroy(arena);
    return issuer;
}

/* This function returns the built chain, as far as it gets,
** even if/when it fails to find an issuer, and returns PR_FAILURE
*/
NSS_IMPLEMENT NSSCertificate **
nssCertificate_BuildChain(
    NSSCertificate *c,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCertificate **rvOpt,
    PRUint32 rvLimit,
    NSSArena *arenaOpt,
    PRStatus *statusOpt,
    NSSTrustDomain *td,
    NSSCryptoContext *cc)
{
    NSSCertificate **rvChain = NULL;
    NSSUsage issuerUsage = *usage;
    nssPKIObjectCollection *collection = NULL;
    PRUint32 rvCount = 0;
    PRStatus st;
    PRStatus ret = PR_SUCCESS;

    if (!c || !cc ||
        (!td && (td = NSSCertificate_GetTrustDomain(c)) == NULL)) {
        goto loser;
    }
    /* bump the usage up to CA level */
    issuerUsage.nss3lookingForCA = PR_TRUE;
    collection = nssCertificateCollection_Create(td, NULL);
    if (!collection)
        goto loser;
    st = nssPKIObjectCollection_AddObject(collection, (nssPKIObject *)c);
    if (st != PR_SUCCESS)
        goto loser;
    for (rvCount = 1; (!rvLimit || rvCount < rvLimit); ++rvCount) {
        CERTCertificate *cCert = STAN_GetCERTCertificate(c);
        if (cCert->isRoot) {
            /* not including the issuer of the self-signed cert, which is,
             * of course, itself
             */
            break;
        }
        c = find_cert_issuer(c, timeOpt, &issuerUsage, policiesOpt, td, cc);
        if (!c) {
            ret = PR_FAILURE;
            break;
        }
        st = nssPKIObjectCollection_AddObject(collection, (nssPKIObject *)c);
        nssCertificate_Destroy(c); /* collection has it */
        if (st != PR_SUCCESS)
            goto loser;
    }
    rvChain = nssPKIObjectCollection_GetCertificates(collection,
                                                     rvOpt,
                                                     rvLimit,
                                                     arenaOpt);
    if (rvChain) {
        nssPKIObjectCollection_Destroy(collection);
        if (statusOpt)
            *statusOpt = ret;
        if (ret != PR_SUCCESS)
            nss_SetError(NSS_ERROR_CERTIFICATE_ISSUER_NOT_FOUND);
        return rvChain;
    }

loser:
    if (collection)
        nssPKIObjectCollection_Destroy(collection);
    if (statusOpt)
        *statusOpt = PR_FAILURE;
    nss_SetError(NSS_ERROR_CERTIFICATE_ISSUER_NOT_FOUND);
    return rvChain;
}

NSS_IMPLEMENT NSSCertificate **
NSSCertificate_BuildChain(
    NSSCertificate *c,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCertificate **rvOpt,
    PRUint32 rvLimit, /* zero for no limit */
    NSSArena *arenaOpt,
    PRStatus *statusOpt,
    NSSTrustDomain *td,
    NSSCryptoContext *cc)
{
    return nssCertificate_BuildChain(c, timeOpt, usage, policiesOpt,
                                     rvOpt, rvLimit, arenaOpt, statusOpt,
                                     td, cc);
}

NSS_IMPLEMENT NSSCryptoContext *
nssCertificate_GetCryptoContext(NSSCertificate *c)
{
    return c->object.cryptoContext;
}

NSS_IMPLEMENT NSSTrustDomain *
nssCertificate_GetTrustDomain(NSSCertificate *c)
{
    return c->object.trustDomain;
}

NSS_IMPLEMENT NSSTrustDomain *
NSSCertificate_GetTrustDomain(NSSCertificate *c)
{
    return nssCertificate_GetTrustDomain(c);
}

NSS_IMPLEMENT NSSToken *
NSSCertificate_GetToken(
    NSSCertificate *c,
    PRStatus *statusOpt)
{
    return (NSSToken *)NULL;
}

NSS_IMPLEMENT NSSSlot *
NSSCertificate_GetSlot(
    NSSCertificate *c,
    PRStatus *statusOpt)
{
    return (NSSSlot *)NULL;
}

NSS_IMPLEMENT NSSModule *
NSSCertificate_GetModule(
    NSSCertificate *c,
    PRStatus *statusOpt)
{
    return (NSSModule *)NULL;
}

NSS_IMPLEMENT NSSItem *
NSSCertificate_Encrypt(
    NSSCertificate *c,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *data,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT PRStatus
NSSCertificate_Verify(
    NSSCertificate *c,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *data,
    NSSItem *signature,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return PR_FAILURE;
}

NSS_IMPLEMENT NSSItem *
NSSCertificate_VerifyRecover(
    NSSCertificate *c,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *signature,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSItem *
NSSCertificate_WrapSymmetricKey(
    NSSCertificate *c,
    NSSAlgorithmAndParameters *apOpt,
    NSSSymmetricKey *keyToWrap,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSCryptoContext *
NSSCertificate_CreateCryptoContext(
    NSSCertificate *c,
    NSSAlgorithmAndParameters *apOpt,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSPublicKey *
NSSCertificate_GetPublicKey(
    NSSCertificate *c)
{
#if 0
    CK_ATTRIBUTE pubktemplate[] = {
    { CKA_CLASS,   NULL, 0 },
    { CKA_ID,      NULL, 0 },
    { CKA_SUBJECT, NULL, 0 }
    };
    PRStatus nssrv;
    CK_ULONG count = sizeof(pubktemplate) / sizeof(pubktemplate[0]);
    NSS_CK_SET_ATTRIBUTE_ITEM(pubktemplate, 0, &g_ck_class_pubkey);
    if (c->id.size > 0) {
    /* CKA_ID */
    NSS_CK_ITEM_TO_ATTRIBUTE(&c->id, &pubktemplate[1]);
    } else {
    /* failure, yes? */
    return (NSSPublicKey *)NULL;
    }
    if (c->subject.size > 0) {
    /* CKA_SUBJECT */
    NSS_CK_ITEM_TO_ATTRIBUTE(&c->subject, &pubktemplate[2]);
    } else {
    /* failure, yes? */
    return (NSSPublicKey *)NULL;
    }
    /* Try the cert's token first */
    if (c->token) {
    nssrv = nssToken_FindObjectByTemplate(c->token, pubktemplate, count);
    }
#endif
    /* Try all other key tokens */
    return (NSSPublicKey *)NULL;
}

NSS_IMPLEMENT NSSPrivateKey *
NSSCertificate_FindPrivateKey(
    NSSCertificate *c,
    NSSCallback *uhh)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT PRBool
NSSCertificate_IsPrivateKeyAvailable(
    NSSCertificate *c,
    NSSCallback *uhh,
    PRStatus *statusOpt)
{
    PRBool isUser = PR_FALSE;
    nssCryptokiObject **ip;
    nssCryptokiObject **instances = nssPKIObject_GetInstances(&c->object);
    if (!instances) {
        return PR_FALSE;
    }
    for (ip = instances; *ip; ip++) {
        nssCryptokiObject *instance = *ip;
        if (nssToken_IsPrivateKeyAvailable(instance->token, c, instance)) {
            isUser = PR_TRUE;
        }
    }
    nssCryptokiObjectArray_Destroy(instances);
    return isUser;
}

/* sort the subject cert list from newest to oldest */
PRIntn
nssCertificate_SubjectListSort(
    void *v1,
    void *v2)
{
    NSSCertificate *c1 = (NSSCertificate *)v1;
    NSSCertificate *c2 = (NSSCertificate *)v2;
    nssDecodedCert *dc1 = nssCertificate_GetDecoding(c1);
    nssDecodedCert *dc2 = nssCertificate_GetDecoding(c2);
    if (!dc1) {
        return dc2 ? 1 : 0;
    } else if (!dc2) {
        return -1;
    } else {
        return dc1->isNewerThan(dc1, dc2) ? -1 : 1;
    }
}

NSS_IMPLEMENT PRBool
NSSUserCertificate_IsStillPresent(
    NSSUserCertificate *uc,
    PRStatus *statusOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return PR_FALSE;
}

NSS_IMPLEMENT NSSItem *
NSSUserCertificate_Decrypt(
    NSSUserCertificate *uc,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *data,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSItem *
NSSUserCertificate_Sign(
    NSSUserCertificate *uc,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *data,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSItem *
NSSUserCertificate_SignRecover(
    NSSUserCertificate *uc,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *data,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSSymmetricKey *
NSSUserCertificate_UnwrapSymmetricKey(
    NSSUserCertificate *uc,
    NSSAlgorithmAndParameters *apOpt,
    NSSItem *wrappedKey,
    NSSTime *timeOpt,
    NSSUsage *usage,
    NSSPolicies *policiesOpt,
    NSSCallback *uhh,
    NSSItem *rvOpt,
    NSSArena *arenaOpt)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT NSSSymmetricKey *
NSSUserCertificate_DeriveSymmetricKey(
    NSSUserCertificate *uc, /* provides private key */
    NSSCertificate *c,      /* provides public key */
    NSSAlgorithmAndParameters *apOpt,
    NSSOID *target,
    PRUint32 keySizeOpt, /* zero for best allowed */
    NSSOperations operations,
    NSSCallback *uhh)
{
    nss_SetError(NSS_ERROR_NOT_FOUND);
    return NULL;
}

NSS_IMPLEMENT nssSMIMEProfile *
nssSMIMEProfile_Create(
    NSSCertificate *cert,
    NSSItem *profileTime,
    NSSItem *profileData)
{
    NSSArena *arena;
    nssSMIMEProfile *rvProfile;
    nssPKIObject *object;
    NSSTrustDomain *td = nssCertificate_GetTrustDomain(cert);
    NSSCryptoContext *cc = nssCertificate_GetCryptoContext(cert);
    arena = nssArena_Create();
    if (!arena) {
        return NULL;
    }
    object = nssPKIObject_Create(arena, NULL, td, cc, nssPKILock);
    if (!object) {
        goto loser;
    }
    rvProfile = nss_ZNEW(arena, nssSMIMEProfile);
    if (!rvProfile) {
        goto loser;
    }
    rvProfile->object = *object;
    rvProfile->certificate = cert;
    rvProfile->email = nssUTF8_Duplicate(cert->email, arena);
    rvProfile->subject = nssItem_Duplicate(&cert->subject, arena, NULL);
    if (profileTime) {
        rvProfile->profileTime = nssItem_Duplicate(profileTime, arena, NULL);
    }
    if (profileData) {
        rvProfile->profileData = nssItem_Duplicate(profileData, arena, NULL);
    }
    return rvProfile;
loser:
    if (object)
        nssPKIObject_Destroy(object);
    else if (arena)
        nssArena_Destroy(arena);
    return (nssSMIMEProfile *)NULL;
}

/* execute a callback function on all members of a cert list */
NSS_EXTERN PRStatus
nssCertificateList_DoCallback(
    nssList *certList,
    PRStatus (*callback)(NSSCertificate *c, void *arg),
    void *arg)
{
    nssListIterator *certs;
    NSSCertificate *cert;
    certs = nssList_CreateIterator(certList);
    if (!certs) {
        return PR_FAILURE;
    }
    for (cert = (NSSCertificate *)nssListIterator_Start(certs);
         cert != (NSSCertificate *)NULL;
         cert = (NSSCertificate *)nssListIterator_Next(certs)) {
        (void)(*callback)(cert, arg);
    }
    nssListIterator_Finish(certs);
    nssListIterator_Destroy(certs);
    return PR_SUCCESS;
}

static PRStatus
add_ref_callback(NSSCertificate *c, void *a)
{
    nssCertificate_AddRef(c);
    return PR_SUCCESS;
}

NSS_IMPLEMENT void
nssCertificateList_AddReferences(
    nssList *certList)
{
    (void)nssCertificateList_DoCallback(certList, add_ref_callback, NULL);
}

/*
 * Is this trust record safe to apply to all certs of the same issuer/SN
 * independent of the cert matching the hash. This is only true is the trust
 * is unknown or distrusted. In general this feature is only useful to
 * explicitly distrusting certs. It is not safe to use to trust certs, so
 * only allow unknown and untrusted trust types.
 */
PRBool
nssTrust_IsSafeToIgnoreCertHash(nssTrustLevel serverAuth,
                                nssTrustLevel clientAuth, nssTrustLevel codeSigning,
                                nssTrustLevel email, PRBool stepup)
{
    /* step up is a trust type, if it's on, we must have a hash for the cert */
    if (stepup) {
        return PR_FALSE;
    }
    if ((serverAuth != nssTrustLevel_Unknown) &&
        (serverAuth != nssTrustLevel_NotTrusted)) {
        return PR_FALSE;
    }
    if ((clientAuth != nssTrustLevel_Unknown) &&
        (clientAuth != nssTrustLevel_NotTrusted)) {
        return PR_FALSE;
    }
    if ((codeSigning != nssTrustLevel_Unknown) &&
        (codeSigning != nssTrustLevel_NotTrusted)) {
        return PR_FALSE;
    }
    if ((email != nssTrustLevel_Unknown) &&
        (email != nssTrustLevel_NotTrusted)) {
        return PR_FALSE;
    }
    /* record only has Unknown and Untrusted entries, ok to accept without a
     * hash */
    return PR_TRUE;
}

/* verify that hash and mechanism matches certifcate. This function
 * uses hashCache and hashCacheMech from the caller to allow us to
 * use previous calculated hash values if they are the same */
static PRBool
nssTrust_isValidHash(const NSSItem *hash, CK_MECHANISM_TYPE hashMech,
                     NSSItem *cert, NSSItem *hashCache,
                     CK_MECHANISM_TYPE *hashCacheMech)
{
    if ((hashMech == CKM_INVALID_MECHANISM) || (hash->size == 0)) {
        return PR_FALSE;
    }
    if (*hashCacheMech != hashMech) {
        /* the cache doesn't have the correct mech, get a
         * new hash for the cert */
        hashCache->size = HASH_LENGTH_MAX;
        PRStatus ret = NSSAlgorithm_DigestBuf(hashMech, cert, hashCache);
        if (ret != PR_SUCCESS) {
            *hashCacheMech = CKM_INVALID_MECHANISM;
            return PR_FALSE;
        }
        *hashCacheMech = hashMech;
    }
    return ((hash->size == hashCache->size) && (PORT_Memcmp(hash->data,
                                                            hashCache->data,
                                                            hash->size) == 0));
}

NSS_IMPLEMENT NSSTrust *
nssTrust_Create(
    nssPKIObject *object,
    NSSItem *certData)
{
    PRStatus status;
    PRUint32 i;
    PRUint32 lastTrustOrder, myTrustOrder;
    unsigned char hashCacheBuf[HASH_LENGTH_MAX];
    unsigned char hashAttrBuf[HASH_LENGTH_MAX];
    NSSItem hashAttr = { hashAttrBuf, sizeof(hashAttrBuf) };
    NSSItem hashCache = { hashCacheBuf, sizeof(hashCacheBuf) };
    NSSTrust *rvt;
    CK_MECHANISM_TYPE hashMech = CKM_INVALID_MECHANISM;
    CK_MECHANISM_TYPE hashCacheMech = CKM_INVALID_MECHANISM;
    nssCryptokiObject *instance;
    nssTrustLevel serverAuth, clientAuth, codeSigning, emailProtection;
    PRBool stepUp;

    lastTrustOrder = 1 << 16; /* just make it big */
    PR_ASSERT(object->instances != NULL && object->numInstances > 0);
    rvt = nss_ZNEW(object->arena, NSSTrust);
    if (!rvt) {
        return (NSSTrust *)NULL;
    }
    rvt->object = *object;

    /* trust has to peek into the base object members */
    nssPKIObject_Lock(object);
    for (i = 0; i < object->numInstances; i++) {
        hashAttr.size = sizeof(hashAttrBuf); /* reset to allocated size */
        instance = object->instances[i];
        myTrustOrder = nssToken_GetTrustOrder(instance->token);
        status = nssCryptokiTrust_GetAttributes(instance, NULL,
                                                &hashAttr,
                                                &hashMech,
                                                &serverAuth,
                                                &clientAuth,
                                                &codeSigning,
                                                &emailProtection,
                                                &stepUp);
        if (status != PR_SUCCESS) {
            nssPKIObject_Unlock(object);
            return (NSSTrust *)NULL;
        }
        /* if no hash is specified, then trust applies to all certs with
         * this issuer/SN. NOTE: This is only true for entries that
         * have distrust and unknown record */
        if (!(
                /* we continue if there is no hash, and the trust type is
                 * safe to accept without a hash ... or ... */
                (((hashAttr.size == 0) || (hashMech == CKM_INVALID_MECHANISM)) &&
                 nssTrust_IsSafeToIgnoreCertHash(serverAuth, clientAuth,
                                                 codeSigning, emailProtection,
                                                 stepUp)) ||
                /* we have a hash of the correct size, and it matches */
                nssTrust_isValidHash(&hashAttr, hashMech, certData,
                                     &hashCache, &hashCacheMech))) {

            nssPKIObject_Unlock(object);
            return (NSSTrust *)NULL;
        }
        if (rvt->serverAuth == nssTrustLevel_Unknown ||
            myTrustOrder < lastTrustOrder) {
            rvt->serverAuth = serverAuth;
        }
        if (rvt->clientAuth == nssTrustLevel_Unknown ||
            myTrustOrder < lastTrustOrder) {
            rvt->clientAuth = clientAuth;
        }
        if (rvt->emailProtection == nssTrustLevel_Unknown ||
            myTrustOrder < lastTrustOrder) {
            rvt->emailProtection = emailProtection;
        }
        if (rvt->codeSigning == nssTrustLevel_Unknown ||
            myTrustOrder < lastTrustOrder) {
            rvt->codeSigning = codeSigning;
        }
        rvt->stepUpApproved = stepUp;
        lastTrustOrder = myTrustOrder;
    }
    nssPKIObject_Unlock(object);
    return rvt;
}

NSS_IMPLEMENT NSSTrust *
nssTrust_AddRef(NSSTrust *trust)
{
    if (trust) {
        nssPKIObject_AddRef(&trust->object);
    }
    return trust;
}

NSS_IMPLEMENT PRStatus
nssTrust_Destroy(NSSTrust *trust)
{
    if (trust) {
        (void)nssPKIObject_Destroy(&trust->object);
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT nssSMIMEProfile *
nssSMIMEProfile_AddRef(nssSMIMEProfile *profile)
{
    if (profile) {
        nssPKIObject_AddRef(&profile->object);
    }
    return profile;
}

NSS_IMPLEMENT PRStatus
nssSMIMEProfile_Destroy(nssSMIMEProfile *profile)
{
    if (profile) {
        (void)nssPKIObject_Destroy(&profile->object);
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT NSSCRL *
nssCRL_Create(nssPKIObject *object)
{
    PRStatus status;
    NSSCRL *rvCRL;
    NSSArena *arena = object->arena;
    PR_ASSERT(object->instances != NULL && object->numInstances > 0);
    rvCRL = nss_ZNEW(arena, NSSCRL);
    if (!rvCRL) {
        return (NSSCRL *)NULL;
    }
    rvCRL->object = *object;
    /* XXX should choose instance based on some criteria */
    status = nssCryptokiCRL_GetAttributes(object->instances[0],
                                          NULL, /* XXX sessionOpt */
                                          arena,
                                          &rvCRL->encoding,
                                          NULL, /* subject */
                                          NULL, /* class */
                                          &rvCRL->url,
                                          &rvCRL->isKRL);
    if (status != PR_SUCCESS) {
        if (!arena) {
            nssPKIObject_Destroy((nssPKIObject *)rvCRL);
        }
        return (NSSCRL *)NULL;
    }
    return rvCRL;
}

NSS_IMPLEMENT NSSCRL *
nssCRL_AddRef(NSSCRL *crl)
{
    if (crl) {
        nssPKIObject_AddRef(&crl->object);
    }
    return crl;
}

NSS_IMPLEMENT PRStatus
nssCRL_Destroy(NSSCRL *crl)
{
    if (crl) {
        (void)nssPKIObject_Destroy(&crl->object);
    }
    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
nssCRL_DeleteStoredObject(
    NSSCRL *crl,
    NSSCallback *uhh)
{
    return nssPKIObject_DeleteStoredObject(&crl->object, uhh, PR_TRUE);
}

NSS_IMPLEMENT NSSDER *
nssCRL_GetEncoding(NSSCRL *crl)
{
    if (crl && crl->encoding.data != NULL && crl->encoding.size > 0) {
        return &crl->encoding;
    } else {
        return (NSSDER *)NULL;
    }
}
