/* 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/. */
#include "secitem.h"
#include "pkcs11.h"
#include "lgdb.h"
#include "pcert.h"
#include "lowkeyi.h"
#include "blapi.h"
#include "secder.h"
#include "secasn1.h"

#include "keydbi.h"

/*
 * ******************** Object Creation Utilities ***************************
 */

/*
 * check the consistancy and initialize a Certificate Object
 */
static CK_RV
lg_createCertObject(SDB *sdb, CK_OBJECT_HANDLE *handle,
                    const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    SECItem derCert;
    NSSLOWCERTCertificate *cert;
    NSSLOWCERTCertTrust *trust = NULL;
    NSSLOWCERTCertTrust userTrust =
        { CERTDB_USER, CERTDB_USER, CERTDB_USER };
    NSSLOWCERTCertTrust defTrust =
        { CERTDB_TRUSTED_UNKNOWN,
          CERTDB_TRUSTED_UNKNOWN, CERTDB_TRUSTED_UNKNOWN };
    char *label = NULL;
    char *email = NULL;
    SECStatus rv;
    CK_RV crv;
    PRBool inDB = PR_TRUE;
    NSSLOWCERTCertDBHandle *certHandle = lg_getCertDB(sdb);
    NSSLOWKEYDBHandle *keyHandle = NULL;
    CK_CERTIFICATE_TYPE type;
    const CK_ATTRIBUTE *attribute;

    /* we can't store any certs private */
    if (lg_isTrue(CKA_PRIVATE, templ, count)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    /* We only support X.509 Certs for now */
    crv = lg_GetULongAttribute(CKA_CERTIFICATE_TYPE, templ, count, &type);
    if (crv != CKR_OK) {
        return crv;
    }

    if (type != CKC_X_509) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    /* X.509 Certificate */

    if (certHandle == NULL) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    /* get the der cert */
    attribute = lg_FindAttribute(CKA_VALUE, templ, count);
    if (!attribute) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    derCert.type = 0;
    derCert.data = (unsigned char *)attribute->pValue;
    derCert.len = attribute->ulValueLen;

    label = lg_getString(CKA_LABEL, templ, count);

    cert = nsslowcert_FindCertByDERCert(certHandle, &derCert);
    if (cert == NULL) {
        cert = nsslowcert_DecodeDERCertificate(&derCert, label);
        inDB = PR_FALSE;
    }
    if (cert == NULL) {
        if (label)
            PORT_Free(label);
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    keyHandle = lg_getKeyDB(sdb);
    if (keyHandle) {
        if (nsslowkey_KeyForCertExists(keyHandle, cert)) {
            trust = &userTrust;
        }
    }

    if (!inDB) {
        if (!trust)
            trust = &defTrust;
        rv = nsslowcert_AddPermCert(certHandle, cert, label, trust);
    } else {
        rv = trust ? nsslowcert_ChangeCertTrust(certHandle, cert, trust) : SECSuccess;
    }

    if (label)
        PORT_Free(label);

    if (rv != SECSuccess) {
        nsslowcert_DestroyCertificate(cert);
        return CKR_DEVICE_ERROR;
    }

    /*
     * Add a NULL S/MIME profile if necessary.
     */
    email = lg_getString(CKA_NSS_EMAIL, templ, count);
    if (email) {
        certDBEntrySMime *entry;

        entry = nsslowcert_ReadDBSMimeEntry(certHandle, email);
        if (!entry) {
            nsslowcert_SaveSMimeProfile(certHandle, email,
                                        &cert->derSubject, NULL, NULL);
        } else {
            nsslowcert_DestroyDBEntry((certDBEntry *)entry);
        }
        PORT_Free(email);
    }
    *handle = lg_mkHandle(sdb, &cert->certKey, LG_TOKEN_TYPE_CERT);
    nsslowcert_DestroyCertificate(cert);

    return CKR_OK;
}

unsigned int
lg_MapTrust(CK_TRUST trust, PRBool clientAuth)
{
    unsigned int trustCA = clientAuth ? CERTDB_TRUSTED_CLIENT_CA : CERTDB_TRUSTED_CA;
    switch (trust) {
        case CKT_NSS_TRUSTED:
            return CERTDB_TERMINAL_RECORD | CERTDB_TRUSTED;
        case CKT_NSS_TRUSTED_DELEGATOR:
            return CERTDB_VALID_CA | trustCA;
        case CKT_NSS_MUST_VERIFY_TRUST:
            return CERTDB_MUST_VERIFY;
        case CKT_NSS_NOT_TRUSTED:
            return CERTDB_TERMINAL_RECORD;
        case CKT_NSS_VALID_DELEGATOR: /* implies must verify */
            return CERTDB_VALID_CA;
        default:
            break;
    }
    return CERTDB_TRUSTED_UNKNOWN;
}

/*
 * check the consistancy and initialize a Trust Object
 */
static CK_RV
lg_createTrustObject(SDB *sdb, CK_OBJECT_HANDLE *handle,
                     const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    const CK_ATTRIBUTE *issuer = NULL;
    const CK_ATTRIBUTE *serial = NULL;
    NSSLOWCERTCertificate *cert = NULL;
    const CK_ATTRIBUTE *trust;
    CK_TRUST sslTrust = CKT_NSS_TRUST_UNKNOWN;
    CK_TRUST clientTrust = CKT_NSS_TRUST_UNKNOWN;
    CK_TRUST emailTrust = CKT_NSS_TRUST_UNKNOWN;
    CK_TRUST signTrust = CKT_NSS_TRUST_UNKNOWN;
    CK_BBOOL stepUp;
    NSSLOWCERTCertTrust dbTrust = { 0 };
    SECStatus rv;
    NSSLOWCERTCertDBHandle *certHandle = lg_getCertDB(sdb);
    NSSLOWCERTIssuerAndSN issuerSN;

    /* we can't store any certs private */
    if (lg_isTrue(CKA_PRIVATE, templ, count)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    if (certHandle == NULL) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    issuer = lg_FindAttribute(CKA_ISSUER, templ, count);
    serial = lg_FindAttribute(CKA_SERIAL_NUMBER, templ, count);

    if (issuer && serial) {
        issuerSN.derIssuer.data = (unsigned char *)issuer->pValue;
        issuerSN.derIssuer.len = issuer->ulValueLen;

        issuerSN.serialNumber.data = (unsigned char *)serial->pValue;
        issuerSN.serialNumber.len = serial->ulValueLen;

        cert = nsslowcert_FindCertByIssuerAndSN(certHandle, &issuerSN);
    }

    if (cert == NULL) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    lg_GetULongAttribute(CKA_TRUST_SERVER_AUTH, templ, count, &sslTrust);
    lg_GetULongAttribute(CKA_TRUST_CLIENT_AUTH, templ, count, &clientTrust);
    lg_GetULongAttribute(CKA_TRUST_EMAIL_PROTECTION, templ, count, &emailTrust);
    lg_GetULongAttribute(CKA_TRUST_CODE_SIGNING, templ, count, &signTrust);
    stepUp = CK_FALSE;
    trust = lg_FindAttribute(CKA_TRUST_STEP_UP_APPROVED, templ, count);
    if (trust) {
        if (trust->ulValueLen == sizeof(CK_BBOOL)) {
            stepUp = *(CK_BBOOL *)trust->pValue;
        }
    }

    /* preserve certain old fields */
    if (cert->trust) {
        dbTrust.sslFlags = cert->trust->sslFlags & CERTDB_PRESERVE_TRUST_BITS;
        dbTrust.emailFlags =
            cert->trust->emailFlags & CERTDB_PRESERVE_TRUST_BITS;
        dbTrust.objectSigningFlags =
            cert->trust->objectSigningFlags & CERTDB_PRESERVE_TRUST_BITS;
    }

    dbTrust.sslFlags |= lg_MapTrust(sslTrust, PR_FALSE);
    dbTrust.sslFlags |= lg_MapTrust(clientTrust, PR_TRUE);
    dbTrust.emailFlags |= lg_MapTrust(emailTrust, PR_FALSE);
    dbTrust.objectSigningFlags |= lg_MapTrust(signTrust, PR_FALSE);
    if (stepUp) {
        dbTrust.sslFlags |= CERTDB_GOVT_APPROVED_CA;
    }

    rv = nsslowcert_ChangeCertTrust(certHandle, cert, &dbTrust);
    *handle = lg_mkHandle(sdb, &cert->certKey, LG_TOKEN_TYPE_TRUST);
    nsslowcert_DestroyCertificate(cert);
    if (rv != SECSuccess) {
        return CKR_DEVICE_ERROR;
    }

    return CKR_OK;
}

/*
 * check the consistancy and initialize a Trust Object
 */
static CK_RV
lg_createSMimeObject(SDB *sdb, CK_OBJECT_HANDLE *handle,
                     const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    SECItem derSubj, rawProfile, rawTime, emailKey;
    SECItem *pRawProfile = NULL;
    SECItem *pRawTime = NULL;
    char *email = NULL;
    const CK_ATTRIBUTE *subject = NULL,
                       *profile = NULL,
                       *time = NULL;
    SECStatus rv;
    NSSLOWCERTCertDBHandle *certHandle;
    CK_RV ck_rv = CKR_OK;

    /* we can't store any certs private */
    if (lg_isTrue(CKA_PRIVATE, templ, count)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    certHandle = lg_getCertDB(sdb);
    if (certHandle == NULL) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    /* lookup SUBJECT */
    subject = lg_FindAttribute(CKA_SUBJECT, templ, count);
    PORT_Assert(subject);
    if (!subject) {
        ck_rv = CKR_ATTRIBUTE_VALUE_INVALID;
        goto loser;
    }

    derSubj.data = (unsigned char *)subject->pValue;
    derSubj.len = subject->ulValueLen;
    derSubj.type = 0;

    /* lookup VALUE */
    profile = lg_FindAttribute(CKA_VALUE, templ, count);
    if (profile) {
        rawProfile.data = (unsigned char *)profile->pValue;
        rawProfile.len = profile->ulValueLen;
        rawProfile.type = siBuffer;
        pRawProfile = &rawProfile;
    }

    /* lookup Time */
    time = lg_FindAttribute(CKA_NSS_SMIME_TIMESTAMP, templ, count);
    if (time) {
        rawTime.data = (unsigned char *)time->pValue;
        rawTime.len = time->ulValueLen;
        rawTime.type = siBuffer;
        pRawTime = &rawTime;
    }

    email = lg_getString(CKA_NSS_EMAIL, templ, count);
    if (!email) {
        ck_rv = CKR_ATTRIBUTE_VALUE_INVALID;
        goto loser;
    }

    /* Store S/MIME Profile by SUBJECT */
    rv = nsslowcert_SaveSMimeProfile(certHandle, email, &derSubj,
                                     pRawProfile, pRawTime);
    if (rv != SECSuccess) {
        ck_rv = CKR_DEVICE_ERROR;
        goto loser;
    }
    emailKey.data = (unsigned char *)email;
    emailKey.len = PORT_Strlen(email) + 1;

    *handle = lg_mkHandle(sdb, &emailKey, LG_TOKEN_TYPE_SMIME);

loser:
    if (email)
        PORT_Free(email);

    return ck_rv;
}

/*
 * check the consistancy and initialize a Trust Object
 */
static CK_RV
lg_createCrlObject(SDB *sdb, CK_OBJECT_HANDLE *handle,
                   const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    PRBool isKRL = PR_FALSE;
    SECItem derSubj, derCrl;
    char *url = NULL;
    const CK_ATTRIBUTE *subject, *crl;
    SECStatus rv;
    NSSLOWCERTCertDBHandle *certHandle;

    certHandle = lg_getCertDB(sdb);

    /* we can't store any private crls */
    if (lg_isTrue(CKA_PRIVATE, templ, count)) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    if (certHandle == NULL) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    /* lookup SUBJECT */
    subject = lg_FindAttribute(CKA_SUBJECT, templ, count);
    if (!subject) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    derSubj.data = (unsigned char *)subject->pValue;
    derSubj.len = subject->ulValueLen;

    /* lookup VALUE */
    crl = lg_FindAttribute(CKA_VALUE, templ, count);
    PORT_Assert(crl);
    if (!crl) {
        return CKR_ATTRIBUTE_VALUE_INVALID;
    }
    derCrl.data = (unsigned char *)crl->pValue;
    derCrl.len = crl->ulValueLen;

    url = lg_getString(CKA_NSS_URL, templ, count);
    isKRL = lg_isTrue(CKA_NSS_KRL, templ, count);

    /* Store CRL by SUBJECT */
    rv = nsslowcert_AddCrl(certHandle, &derCrl, &derSubj, url, isKRL);

    if (url) {
        PORT_Free(url);
    }
    if (rv != SECSuccess) {
        return CKR_DEVICE_ERROR;
    }

    /* if we overwrote the existing CRL, poison the handle entry so we get
     * a new object handle */
    (void)lg_poisonHandle(sdb, &derSubj,
                          isKRL ? LG_TOKEN_KRL_HANDLE : LG_TOKEN_TYPE_CRL);
    *handle = lg_mkHandle(sdb, &derSubj,
                          isKRL ? LG_TOKEN_KRL_HANDLE : LG_TOKEN_TYPE_CRL);

    return CKR_OK;
}

/*
 * check the consistancy and initialize a Public Key Object
 */
static CK_RV
lg_createPublicKeyObject(SDB *sdb, CK_KEY_TYPE key_type,
                         CK_OBJECT_HANDLE *handle, const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    CK_ATTRIBUTE_TYPE pubKeyAttr = CKA_VALUE;
    CK_RV crv = CKR_OK;
    NSSLOWKEYPrivateKey *priv;
    SECItem pubKeySpace = { siBuffer, NULL, 0 };
    SECItem *pubKey;
    SECItem pubKey2Space = { siBuffer, NULL, 0 };
    PLArenaPool *arena = NULL;
    NSSLOWKEYDBHandle *keyHandle = NULL;

    switch (key_type) {
        case CKK_RSA:
            pubKeyAttr = CKA_MODULUS;
            break;
        case CKK_EC:
            pubKeyAttr = CKA_EC_POINT;
            break;
        case CKK_DSA:
        case CKK_DH:
            break;
        default:
            return CKR_ATTRIBUTE_VALUE_INVALID;
    }

    pubKey = &pubKeySpace;
    crv = lg_Attribute2SSecItem(NULL, pubKeyAttr, templ, count, pubKey);
    if (crv != CKR_OK)
        return crv;

    if (key_type == CKK_EC) {
        SECStatus rv;
        /*
         * for ECC, use the decoded key first.
         */
        arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
        if (arena == NULL) {
            crv = CKR_HOST_MEMORY;
            goto done;
        }
        rv = SEC_QuickDERDecodeItem(arena, &pubKey2Space,
                                    SEC_ASN1_GET(SEC_OctetStringTemplate),
                                    pubKey);
        if (rv != SECSuccess) {
            /* decode didn't work, just try the pubKey */
            PORT_FreeArena(arena, PR_FALSE);
            arena = NULL;
        } else {
            /* try the decoded pub key first */
            pubKey = &pubKey2Space;
        }
    }

    PORT_Assert(pubKey->data);
    if (pubKey->data == NULL) {
        crv = CKR_ATTRIBUTE_VALUE_INVALID;
        goto done;
    }
    keyHandle = lg_getKeyDB(sdb);
    if (keyHandle == NULL) {
        crv = CKR_TOKEN_WRITE_PROTECTED;
        goto done;
    }
    if (keyHandle->version != 3) {
        unsigned char buf[SHA1_LENGTH];
        SHA1_HashBuf(buf, pubKey->data, pubKey->len);
        PORT_Memcpy(pubKey->data, buf, sizeof(buf));
        pubKey->len = sizeof(buf);
    }
    /* make sure the associated private key already exists */
    /* only works if we are logged in */
    priv = nsslowkey_FindKeyByPublicKey(keyHandle, pubKey, sdb /*password*/);
    if (priv == NULL && pubKey == &pubKey2Space) {
        /* no match on the decoded key, match the original pubkey */
        pubKey = &pubKeySpace;
        priv = nsslowkey_FindKeyByPublicKey(keyHandle, pubKey,
                                            sdb /*password*/);
    }
    if (priv == NULL) {
        /* the legacy database can only 'store' public keys which already
         * have their corresponding private keys in the database */
        crv = CKR_ATTRIBUTE_VALUE_INVALID;
        goto done;
    }
    lg_nsslowkey_DestroyPrivateKey(priv);
    crv = CKR_OK;

    *handle = lg_mkHandle(sdb, pubKey, LG_TOKEN_TYPE_PUB);

done:
    PORT_Free(pubKeySpace.data);
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    return crv;
}

/* make a private key from a verified object */
static NSSLOWKEYPrivateKey *
lg_mkPrivKey(SDB *sdb, const CK_ATTRIBUTE *templ, CK_ULONG count,
             CK_KEY_TYPE key_type, CK_RV *crvp)
{
    NSSLOWKEYPrivateKey *privKey;
    PLArenaPool *arena;
    CK_RV crv = CKR_OK;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        *crvp = CKR_HOST_MEMORY;
        return NULL;
    }

    privKey = (NSSLOWKEYPrivateKey *)
        PORT_ArenaZAlloc(arena, sizeof(NSSLOWKEYPrivateKey));
    if (privKey == NULL) {
        PORT_FreeArena(arena, PR_FALSE);
        *crvp = CKR_HOST_MEMORY;
        return NULL;
    }

    /* in future this would be a switch on key_type */
    privKey->arena = arena;
    switch (key_type) {
        case CKK_RSA:
            privKey->keyType = NSSLOWKEYRSAKey;
            crv = lg_Attribute2SSecItem(arena, CKA_MODULUS, templ, count,
                                        &privKey->u.rsa.modulus);
            if (crv != CKR_OK)
                break;
            crv = lg_Attribute2SSecItem(arena, CKA_PUBLIC_EXPONENT, templ, count,
                                        &privKey->u.rsa.publicExponent);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_PRIVATE_EXPONENT, templ, count,
                                       &privKey->u.rsa.privateExponent, sdb);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_PRIME_1, templ, count,
                                       &privKey->u.rsa.prime1, sdb);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_PRIME_2, templ, count,
                                       &privKey->u.rsa.prime2, sdb);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_EXPONENT_1, templ, count,
                                       &privKey->u.rsa.exponent1, sdb);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_EXPONENT_2, templ, count,
                                       &privKey->u.rsa.exponent2, sdb);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_COEFFICIENT, templ, count,
                                       &privKey->u.rsa.coefficient, sdb);
            if (crv != CKR_OK)
                break;
            rv = DER_SetUInteger(privKey->arena, &privKey->u.rsa.version,
                                 NSSLOWKEY_VERSION);
            if (rv != SECSuccess)
                crv = CKR_HOST_MEMORY;
            break;

        case CKK_DSA:
            privKey->keyType = NSSLOWKEYDSAKey;
            crv = lg_Attribute2SSecItem(arena, CKA_PRIME, templ, count,
                                        &privKey->u.dsa.params.prime);
            if (crv != CKR_OK)
                break;
            crv = lg_Attribute2SSecItem(arena, CKA_SUBPRIME, templ, count,
                                        &privKey->u.dsa.params.subPrime);
            if (crv != CKR_OK)
                break;
            crv = lg_Attribute2SSecItem(arena, CKA_BASE, templ, count,
                                        &privKey->u.dsa.params.base);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_VALUE, templ, count,
                                       &privKey->u.dsa.privateValue, sdb);
            if (crv != CKR_OK)
                break;
            if (lg_hasAttribute(CKA_NSS_DB, templ, count)) {
                crv = lg_Attribute2SSecItem(arena, CKA_NSS_DB, templ, count,
                                            &privKey->u.dsa.publicValue);
                /* privKey was zero'd so public value is already set to NULL, 0
                 * if we don't set it explicitly */
            }
            break;

        case CKK_DH:
            privKey->keyType = NSSLOWKEYDHKey;
            crv = lg_Attribute2SSecItem(arena, CKA_PRIME, templ, count,
                                        &privKey->u.dh.prime);
            if (crv != CKR_OK)
                break;
            crv = lg_Attribute2SSecItem(arena, CKA_BASE, templ, count,
                                        &privKey->u.dh.base);
            if (crv != CKR_OK)
                break;
            crv = lg_PrivAttr2SSecItem(arena, CKA_VALUE, templ, count,
                                       &privKey->u.dh.privateValue, sdb);
            if (crv != CKR_OK)
                break;
            if (lg_hasAttribute(CKA_NSS_DB, templ, count)) {
                crv = lg_Attribute2SSecItem(arena, CKA_NSS_DB, templ, count,
                                            &privKey->u.dh.publicValue);
                /* privKey was zero'd so public value is already set to NULL, 0
                 * if we don't set it explicitly */
            }
            break;

        case CKK_EC:
            privKey->keyType = NSSLOWKEYECKey;
            crv = lg_Attribute2SSecItem(arena, CKA_EC_PARAMS, templ, count,
                                        &privKey->u.ec.ecParams.DEREncoding);
            if (crv != CKR_OK)
                break;

            /* Fill out the rest of the ecParams structure
             * based on the encoded params
             */
            if (LGEC_FillParams(arena, &privKey->u.ec.ecParams.DEREncoding,
                                &privKey->u.ec.ecParams) != SECSuccess) {
                crv = CKR_DOMAIN_PARAMS_INVALID;
                break;
            }
            crv = lg_PrivAttr2SSecItem(arena, CKA_VALUE, templ, count,
                                       &privKey->u.ec.privateValue, sdb);
            if (crv != CKR_OK)
                break;
            if (lg_hasAttribute(CKA_NSS_DB, templ, count)) {
                crv = lg_Attribute2SSecItem(arena, CKA_NSS_DB, templ, count,
                                            &privKey->u.ec.publicValue);
                if (crv != CKR_OK)
                    break;
                /* privKey was zero'd so public value is already set to NULL, 0
                 * if we don't set it explicitly */
            }
            rv = DER_SetUInteger(privKey->arena, &privKey->u.ec.version,
                                 NSSLOWKEY_EC_PRIVATE_KEY_VERSION);
            if (rv != SECSuccess)
                crv = CKR_HOST_MEMORY;
            break;

        default:
            crv = CKR_KEY_TYPE_INCONSISTENT;
            break;
    }
    *crvp = crv;
    if (crv != CKR_OK) {
        PORT_FreeArena(arena, PR_FALSE);
        return NULL;
    }
    return privKey;
}

/*
 * check the consistancy and initialize a Private Key Object
 */
static CK_RV
lg_createPrivateKeyObject(SDB *sdb, CK_KEY_TYPE key_type,
                          CK_OBJECT_HANDLE *handle, const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    NSSLOWKEYPrivateKey *privKey;
    char *label;
    SECStatus rv = SECSuccess;
    CK_RV crv = CKR_DEVICE_ERROR;
    SECItem pubKey;
    NSSLOWKEYDBHandle *keyHandle = lg_getKeyDB(sdb);

    if (keyHandle == NULL) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    privKey = lg_mkPrivKey(sdb, templ, count, key_type, &crv);
    if (privKey == NULL)
        return crv;
    label = lg_getString(CKA_LABEL, templ, count);

    crv = lg_Attribute2SSecItem(NULL, CKA_NSS_DB, templ, count, &pubKey);
    if (crv != CKR_OK) {
        crv = CKR_TEMPLATE_INCOMPLETE;
        rv = SECFailure;
        goto fail;
    }
#ifdef notdef
    if (keyHandle->version != 3) {
        unsigned char buf[SHA1_LENGTH];
        SHA1_HashBuf(buf, pubKey.data, pubKey.len);
        PORT_Memcpy(pubKey.data, buf, sizeof(buf));
        pubKey.len = sizeof(buf);
    }
#endif
    /* get the key type */
    if (key_type == CKK_RSA) {
        rv = RSA_PrivateKeyCheck(&privKey->u.rsa);
        if (rv == SECFailure) {
            goto fail;
        }
    }
    rv = nsslowkey_StoreKeyByPublicKey(keyHandle, privKey, &pubKey,
                                       label, sdb /*->password*/);

fail:
    if (label)
        PORT_Free(label);
    *handle = lg_mkHandle(sdb, &pubKey, LG_TOKEN_TYPE_PRIV);
    if (pubKey.data)
        PORT_Free(pubKey.data);
    lg_nsslowkey_DestroyPrivateKey(privKey);
    if (rv != SECSuccess)
        return crv;

    return CKR_OK;
}

#define LG_KEY_MAX_RETRIES 10 /* don't hang if we are having problems with the rng */
#define LG_KEY_ID_SIZE 18     /* don't use either SHA1 or MD5 sizes */
/*
 * Secret keys must have a CKA_ID value to be stored in the database. This code
 * will generate one if there wasn't one already.
 */
static CK_RV
lg_GenerateSecretCKA_ID(NSSLOWKEYDBHandle *handle, SECItem *id, char *label)
{
    unsigned int retries;
    SECStatus rv = SECSuccess;
    CK_RV crv = CKR_OK;

    id->data = NULL;
    if (label) {
        id->data = (unsigned char *)PORT_Strdup(label);
        if (id->data == NULL) {
            return CKR_HOST_MEMORY;
        }
        id->len = PORT_Strlen(label) + 1;
        if (!nsslowkey_KeyForIDExists(handle, id)) {
            return CKR_OK;
        }
        PORT_Free(id->data);
        id->data = NULL;
        id->len = 0;
    }
    id->data = (unsigned char *)PORT_Alloc(LG_KEY_ID_SIZE);
    if (id->data == NULL) {
        return CKR_HOST_MEMORY;
    }
    id->len = LG_KEY_ID_SIZE;

    retries = 0;
    do {
        rv = RNG_GenerateGlobalRandomBytes(id->data, id->len);
    } while (rv == SECSuccess && nsslowkey_KeyForIDExists(handle, id) &&
             (++retries <= LG_KEY_MAX_RETRIES));

    if ((rv != SECSuccess) || (retries > LG_KEY_MAX_RETRIES)) {
        crv = CKR_DEVICE_ERROR; /* random number generator is bad */
        PORT_Free(id->data);
        id->data = NULL;
        id->len = 0;
    }
    return crv;
}

static NSSLOWKEYPrivateKey *
lg_mkSecretKeyRep(const CK_ATTRIBUTE *templ,
                  CK_ULONG count, CK_KEY_TYPE key_type,
                  SECItem *pubkey, SDB *sdbpw)
{
    NSSLOWKEYPrivateKey *privKey = 0;
    PLArenaPool *arena = 0;
    CK_KEY_TYPE keyType;
    PRUint32 keyTypeStorage;
    SECItem keyTypeItem;
    CK_RV crv;
    SECStatus rv;
    static unsigned char derZero[1] = { 0 };

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    privKey = (NSSLOWKEYPrivateKey *)
        PORT_ArenaZAlloc(arena, sizeof(NSSLOWKEYPrivateKey));
    if (privKey == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    privKey->arena = arena;

    /* Secret keys are represented in the database as "fake" RSA keys.
     * The RSA key is marked as a secret key representation by setting the
     * public exponent field to 0, which is an invalid RSA exponent.
     * The other fields are set as follows:
     *   modulus - CKA_ID value for the secret key
     *   private exponent - CKA_VALUE (the key itself)
     *   coefficient - CKA_KEY_TYPE, which indicates what encryption algorithm
     *      is used for the key.
     *   all others - set to integer 0
     */
    privKey->keyType = NSSLOWKEYRSAKey;

    /* The modulus is set to the key id of the symmetric key */
    privKey->u.rsa.modulus.data =
        (unsigned char *)PORT_ArenaAlloc(arena, pubkey->len);
    if (privKey->u.rsa.modulus.data == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }
    privKey->u.rsa.modulus.len = pubkey->len;
    PORT_Memcpy(privKey->u.rsa.modulus.data, pubkey->data, pubkey->len);

    /* The public exponent is set to 0 to indicate a special key */
    privKey->u.rsa.publicExponent.len = sizeof derZero;
    privKey->u.rsa.publicExponent.data = derZero;

    /* The private exponent is the actual key value */
    crv = lg_PrivAttr2SecItem(arena, CKA_VALUE, templ, count,
                              &privKey->u.rsa.privateExponent, sdbpw);
    if (crv != CKR_OK)
        goto loser;

    /* All other fields empty - needs testing */
    privKey->u.rsa.prime1.len = sizeof derZero;
    privKey->u.rsa.prime1.data = derZero;

    privKey->u.rsa.prime2.len = sizeof derZero;
    privKey->u.rsa.prime2.data = derZero;

    privKey->u.rsa.exponent1.len = sizeof derZero;
    privKey->u.rsa.exponent1.data = derZero;

    privKey->u.rsa.exponent2.len = sizeof derZero;
    privKey->u.rsa.exponent2.data = derZero;

    /* Coeficient set to KEY_TYPE */
    crv = lg_GetULongAttribute(CKA_KEY_TYPE, templ, count, &keyType);
    if (crv != CKR_OK)
        goto loser;
    /* on 64 bit platforms, we still want to store 32 bits of keyType (This is
     * safe since the PKCS #11 defines for all types are 32 bits or less). */
    keyTypeStorage = (PRUint32)keyType;
    keyTypeStorage = PR_htonl(keyTypeStorage);
    keyTypeItem.data = (unsigned char *)&keyTypeStorage;
    keyTypeItem.len = sizeof(keyTypeStorage);
    rv = SECITEM_CopyItem(arena, &privKey->u.rsa.coefficient, &keyTypeItem);
    if (rv != SECSuccess) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    /* Private key version field set normally for compatibility */
    rv = DER_SetUInteger(privKey->arena,
                         &privKey->u.rsa.version, NSSLOWKEY_VERSION);
    if (rv != SECSuccess) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

loser:
    if (crv != CKR_OK) {
        PORT_FreeArena(arena, PR_FALSE);
        privKey = 0;
    }

    return privKey;
}

/*
 * check the consistancy and initialize a Secret Key Object
 */
static CK_RV
lg_createSecretKeyObject(SDB *sdb, CK_KEY_TYPE key_type,
                         CK_OBJECT_HANDLE *handle, const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    CK_RV crv;
    NSSLOWKEYPrivateKey *privKey = NULL;
    NSSLOWKEYDBHandle *keyHandle = NULL;
    SECItem pubKey;
    char *label = NULL;
    SECStatus rv = SECSuccess;

    pubKey.data = 0;

    /* If the object is a TOKEN object, store in the database */
    keyHandle = lg_getKeyDB(sdb);

    if (keyHandle == NULL) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    label = lg_getString(CKA_LABEL, templ, count);

    crv = lg_Attribute2SecItem(NULL, CKA_ID, templ, count, &pubKey);
    /* Should this be ID? */
    if (crv != CKR_OK)
        goto loser;

    /* if we don't have an ID, generate one */
    if (pubKey.len == 0) {
        if (pubKey.data) {
            PORT_Free(pubKey.data);
            pubKey.data = NULL;
        }
        crv = lg_GenerateSecretCKA_ID(keyHandle, &pubKey, label);
        if (crv != CKR_OK)
            goto loser;
    }

    privKey = lg_mkSecretKeyRep(templ, count, key_type, &pubKey, sdb);
    if (privKey == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    rv = nsslowkey_StoreKeyByPublicKey(keyHandle,
                                       privKey, &pubKey, label, sdb /*->password*/);
    if (rv != SECSuccess) {
        crv = CKR_DEVICE_ERROR;
        goto loser;
    }

    *handle = lg_mkHandle(sdb, &pubKey, LG_TOKEN_TYPE_KEY);

loser:
    if (label)
        PORT_Free(label);
    if (privKey)
        lg_nsslowkey_DestroyPrivateKey(privKey);
    if (pubKey.data)
        PORT_Free(pubKey.data);

    return crv;
}

/*
 * check the consistancy and initialize a Key Object
 */
static CK_RV
lg_createKeyObject(SDB *sdb, CK_OBJECT_CLASS objclass,
                   CK_OBJECT_HANDLE *handle, const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    CK_RV crv;
    CK_KEY_TYPE key_type;

    /* get the key type */
    crv = lg_GetULongAttribute(CKA_KEY_TYPE, templ, count, &key_type);
    if (crv != CKR_OK) {
        return crv;
    }

    switch (objclass) {
        case CKO_PUBLIC_KEY:
            return lg_createPublicKeyObject(sdb, key_type, handle, templ, count);
        case CKO_PRIVATE_KEY:
            return lg_createPrivateKeyObject(sdb, key_type, handle, templ, count);
        case CKO_SECRET_KEY:
            return lg_createSecretKeyObject(sdb, key_type, handle, templ, count);
        default:
            break;
    }
    return CKR_ATTRIBUTE_VALUE_INVALID;
}

/*
 * return the 'next' key handle
 */
CK_RV
lg_GetNewObjectID(SDB *sdb, CK_OBJECT_HANDLE *handle)
{
    /* the upper level needs the Object ID early to populate any
     * signature attributes. The legacy can't really return a new
     * handle without the full object template (chicken and egg issue).
     * Fortunately we can just return a bogus handle because the legacy
     * database doesn't support meta data and can't store any of the signed
     * attributes anyway */
    *handle = CK_INVALID_HANDLE;
    return CKR_OK;
}

/*
 * Parse the template and create an object stored in the DB that reflects.
 * the object specified in the database.
 */
CK_RV
lg_CreateObject(SDB *sdb, CK_OBJECT_HANDLE *handle,
                const CK_ATTRIBUTE *templ, CK_ULONG count)
{
    CK_RV crv;
    CK_OBJECT_CLASS objclass;

    /* get the object class */
    crv = lg_GetULongAttribute(CKA_CLASS, templ, count, &objclass);
    if (crv != CKR_OK) {
        return crv;
    }

    /* Now handle the specific object class.
     */
    switch (objclass) {
        case CKO_CERTIFICATE:
            crv = lg_createCertObject(sdb, handle, templ, count);
            break;
        case CKO_NSS_TRUST:
            crv = lg_createTrustObject(sdb, handle, templ, count);
            break;
        case CKO_NSS_CRL:
            crv = lg_createCrlObject(sdb, handle, templ, count);
            break;
        case CKO_NSS_SMIME:
            crv = lg_createSMimeObject(sdb, handle, templ, count);
            break;
        case CKO_PRIVATE_KEY:
        case CKO_PUBLIC_KEY:
        case CKO_SECRET_KEY:
            crv = lg_createKeyObject(sdb, objclass, handle, templ, count);
            break;
        default:
            crv = CKR_ATTRIBUTE_VALUE_INVALID;
            break;
    }

    return crv;
}
