/* 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 "ckmk.h"
#include "nssbase.h"

#include "secdert.h" /* for DER_INTEGER */
#include "string.h"

/* asn1 encoder (to build pkcs#8 blobs) */
#include <seccomon.h>
#include <secitem.h>
#include <blapit.h>
#include <secoid.h>
#include <secasn1.h>

/* for importing the keys */
#include <CoreFoundation/CoreFoundation.h>
#include <security/SecImportExport.h>

/*
 * nssmkey/mobject.c
 *
 * This file implements the NSSCKMDObject object for the
 * "nssmkey" cryptoki module.
 */

const CK_ATTRIBUTE_TYPE certAttrs[] = {
    CKA_CLASS,
    CKA_TOKEN,
    CKA_PRIVATE,
    CKA_MODIFIABLE,
    CKA_LABEL,
    CKA_CERTIFICATE_TYPE,
    CKA_SUBJECT,
    CKA_ISSUER,
    CKA_SERIAL_NUMBER,
    CKA_VALUE
};
const PRUint32 certAttrsCount = NSS_CKMK_ARRAY_SIZE(certAttrs);

/* private keys, for now only support RSA */
const CK_ATTRIBUTE_TYPE privKeyAttrs[] = {
    CKA_CLASS,
    CKA_TOKEN,
    CKA_PRIVATE,
    CKA_MODIFIABLE,
    CKA_LABEL,
    CKA_KEY_TYPE,
    CKA_DERIVE,
    CKA_LOCAL,
    CKA_SUBJECT,
    CKA_SENSITIVE,
    CKA_DECRYPT,
    CKA_SIGN,
    CKA_SIGN_RECOVER,
    CKA_UNWRAP,
    CKA_EXTRACTABLE,
    CKA_ALWAYS_SENSITIVE,
    CKA_NEVER_EXTRACTABLE,
    CKA_MODULUS,
    CKA_PUBLIC_EXPONENT,
};
const PRUint32 privKeyAttrsCount = NSS_CKMK_ARRAY_SIZE(privKeyAttrs);

/* public keys, for now only support RSA */
const CK_ATTRIBUTE_TYPE pubKeyAttrs[] = {
    CKA_CLASS,
    CKA_TOKEN,
    CKA_PRIVATE,
    CKA_MODIFIABLE,
    CKA_LABEL,
    CKA_KEY_TYPE,
    CKA_DERIVE,
    CKA_LOCAL,
    CKA_SUBJECT,
    CKA_ENCRYPT,
    CKA_VERIFY,
    CKA_VERIFY_RECOVER,
    CKA_WRAP,
    CKA_MODULUS,
    CKA_PUBLIC_EXPONENT,
};
const PRUint32 pubKeyAttrsCount = NSS_CKMK_ARRAY_SIZE(pubKeyAttrs);
static const CK_BBOOL ck_true = CK_TRUE;
static const CK_BBOOL ck_false = CK_FALSE;
static const CK_CERTIFICATE_TYPE ckc_x509 = CKC_X_509;
static const CK_KEY_TYPE ckk_rsa = CKK_RSA;
static const CK_OBJECT_CLASS cko_certificate = CKO_CERTIFICATE;
static const CK_OBJECT_CLASS cko_private_key = CKO_PRIVATE_KEY;
static const CK_OBJECT_CLASS cko_public_key = CKO_PUBLIC_KEY;
static const NSSItem ckmk_trueItem = {
    (void *)&ck_true, (PRUint32)sizeof(CK_BBOOL)
};
static const NSSItem ckmk_falseItem = {
    (void *)&ck_false, (PRUint32)sizeof(CK_BBOOL)
};
static const NSSItem ckmk_x509Item = {
    (void *)&ckc_x509, (PRUint32)sizeof(CK_CERTIFICATE_TYPE)
};
static const NSSItem ckmk_rsaItem = {
    (void *)&ckk_rsa, (PRUint32)sizeof(CK_KEY_TYPE)
};
static const NSSItem ckmk_certClassItem = {
    (void *)&cko_certificate, (PRUint32)sizeof(CK_OBJECT_CLASS)
};
static const NSSItem ckmk_privKeyClassItem = {
    (void *)&cko_private_key, (PRUint32)sizeof(CK_OBJECT_CLASS)
};
static const NSSItem ckmk_pubKeyClassItem = {
    (void *)&cko_public_key, (PRUint32)sizeof(CK_OBJECT_CLASS)
};
static const NSSItem ckmk_emptyItem = {
    (void *)&ck_true, 0
};

/*
 * these are utilities. The chould be moved to a new utilities file.
 */
#ifdef DEBUG
static void
itemdump(char *str, void *data, int size, CK_RV error)
{
    unsigned char *ptr = (unsigned char *)data;
    int i;
    fprintf(stderr, str);
    for (i = 0; i < size; i++) {
        fprintf(stderr, "%02x ", (unsigned int)ptr[i]);
    }
    fprintf(stderr, " (error = %d)\n", (int)error);
}
#endif

/*
 * unwrap a single DER value
 * now that we have util linked in, we should probably use
 * the ANS1_Decoder for this work...
 */
unsigned char *
nss_ckmk_DERUnwrap(
    unsigned char *src,
    int size,
    int *outSize,
    unsigned char **next)
{
    unsigned char *start = src;
    unsigned int len = 0;

    /* initialize error condition return values */
    *outSize = 0;
    if (next) {
        *next = src;
    }

    if (size < 2) {
        return start;
    }
    src++; /* skip the tag -- should check it against an expected value! */
    len = (unsigned)*src++;
    if (len & 0x80) {
        int count = len & 0x7f;
        len = 0;

        if (count + 2 > size) {
            return start;
        }
        while (count-- > 0) {
            len = (len << 8) | (unsigned)*src++;
        }
    }
    if (len + (src - start) > (unsigned int)size) {
        return start;
    }
    if (next) {
        *next = src + len;
    }
    *outSize = len;

    return src;
}

/*
 * get an attribute from a template. Value is returned in NSS item.
 * data for the item is owned by the template.
 */
CK_RV
nss_ckmk_GetAttribute(
    CK_ATTRIBUTE_TYPE type,
    CK_ATTRIBUTE *template,
    CK_ULONG templateSize,
    NSSItem *item)
{
    CK_ULONG i;

    for (i = 0; i < templateSize; i++) {
        if (template[i].type == type) {
            item->data = template[i].pValue;
            item->size = template[i].ulValueLen;
            return CKR_OK;
        }
    }
    return CKR_TEMPLATE_INCOMPLETE;
}

/*
 * get an attribute which is type CK_ULONG.
 */
CK_ULONG
nss_ckmk_GetULongAttribute(
    CK_ATTRIBUTE_TYPE type,
    CK_ATTRIBUTE *template,
    CK_ULONG templateSize,
    CK_RV *pError)
{
    NSSItem item;

    *pError = nss_ckmk_GetAttribute(type, template, templateSize, &item);
    if (CKR_OK != *pError) {
        return (CK_ULONG)0;
    }
    if (item.size != sizeof(CK_ULONG)) {
        *pError = CKR_ATTRIBUTE_VALUE_INVALID;
        return (CK_ULONG)0;
    }
    return *(CK_ULONG *)item.data;
}

/*
 * get an attribute which is type CK_BBOOL.
 */
CK_BBOOL
nss_ckmk_GetBoolAttribute(
    CK_ATTRIBUTE_TYPE type,
    CK_ATTRIBUTE *template,
    CK_ULONG templateSize,
    CK_BBOOL defaultBool)
{
    NSSItem item;
    CK_RV error;

    error = nss_ckmk_GetAttribute(type, template, templateSize, &item);
    if (CKR_OK != error) {
        return defaultBool;
    }
    if (item.size != sizeof(CK_BBOOL)) {
        return defaultBool;
    }
    return *(CK_BBOOL *)item.data;
}

/*
 * get an attribute as a NULL terminated string. Caller is responsible to
 * free the string.
 */
char *
nss_ckmk_GetStringAttribute(
    CK_ATTRIBUTE_TYPE type,
    CK_ATTRIBUTE *template,
    CK_ULONG templateSize,
    CK_RV *pError)
{
    NSSItem item;
    char *str;

    /* get the attribute */
    *pError = nss_ckmk_GetAttribute(type, template, templateSize, &item);
    if (CKR_OK != *pError) {
        return (char *)NULL;
    }
    /* make sure it is null terminated */
    str = nss_ZNEWARRAY(NULL, char, item.size + 1);
    if ((char *)NULL == str) {
        *pError = CKR_HOST_MEMORY;
        return (char *)NULL;
    }

    nsslibc_memcpy(str, item.data, item.size);
    str[item.size] = 0;

    return str;
}

/*
 * Apple doesn't seem to have a public interface to the DER encoder,
 * wip out a quick one for integers only (anything more complicated,
 * we should use one of the 3 in lib/util). -- especially since we
 * now link with it.
 */
static CK_RV
ckmk_encodeInt(NSSItem *dest, void *src, int srcLen)
{
    int dataLen = srcLen;
    int lenLen = 1;
    int encLen;
    int isSigned = 0;
    int offset = 0;
    unsigned char *data = NULL;
    int i;

    if (*(unsigned char *)src & 0x80) {
        dataLen++;
        isSigned = 1;
    }

    /* calculate the length of the length specifier */
    /* (NOTE: destroys dataLen value) */
    if (dataLen > 0x7f) {
        do {
            lenLen++;
            dataLen >>= 8;
        } while (dataLen);
    }

    /* calculate our total length */
    dataLen = isSigned + srcLen;
    encLen = 1 + lenLen + dataLen;
    data = nss_ZNEWARRAY(NULL, unsigned char, encLen);
    if ((unsigned char *)NULL == data) {
        return CKR_HOST_MEMORY;
    }
    data[0] = DER_INTEGER;
    if (1 == lenLen) {
        data[1] = dataLen;
    } else {
        data[1] = 0x80 + lenLen;
        for (i = 0; i < lenLen; i++) {
            data[i + 1] = ((dataLen >> ((lenLen -
                                         i - 1) *
                                        8)) &
                           0xff);
        }
    }
    offset = lenLen + 1;

    if (isSigned) {
        data[offset++] = 0;
    }
    nsslibc_memcpy(&data[offset], src, srcLen);
    dest->data = data;
    dest->size = encLen;
    return CKR_OK;
}

/*
 * Get a Keyring attribute. If content is set to true, then we get the
 * content, not the attribute.
 */
static CK_RV
ckmk_GetCommonAttribute(
    ckmkInternalObject *io,
    SecItemAttr itemAttr,
    PRBool content,
    NSSItem *item,
    char *dbString)
{
    SecKeychainAttributeList *attrList = NULL;
    SecKeychainAttributeInfo attrInfo;
    PRUint32 len = 0;
    PRUint32 dataLen = 0;
    PRUint32 attrFormat = 0;
    void *dataVal = 0;
    void *out = NULL;
    CK_RV error = CKR_OK;
    OSStatus macErr;

    attrInfo.count = 1;
    attrInfo.tag = &itemAttr;
    attrInfo.format = &attrFormat;

    macErr = SecKeychainItemCopyAttributesAndData(io->u.item.itemRef,
                                                  &attrInfo, NULL, &attrList, &len, &out);
    if (noErr != macErr) {
        CKMK_MACERR(dbString, macErr);
        return CKR_ATTRIBUTE_TYPE_INVALID;
    }
    dataLen = content ? len : attrList->attr->length;
    dataVal = content ? out : attrList->attr->data;

    /* Apple's documentation says this value is DER Encoded, but it clearly isn't
     * der encode it before we ship it back off to NSS
     */
    if (kSecSerialNumberItemAttr == itemAttr) {
        error = ckmk_encodeInt(item, dataVal, dataLen);
        goto loser; /* logically 'done' if error == CKR_OK */
    }
    item->data = nss_ZNEWARRAY(NULL, char, dataLen);
    if (NULL == item->data) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }
    nsslibc_memcpy(item->data, dataVal, dataLen);
    item->size = dataLen;

loser:
    SecKeychainItemFreeAttributesAndData(attrList, out);
    return error;
}

/*
 * change an attribute (does not operate on the content).
 */
static CK_RV
ckmk_updateAttribute(
    SecKeychainItemRef itemRef,
    SecItemAttr itemAttr,
    void *data,
    PRUint32 len,
    char *dbString)
{
    SecKeychainAttributeList attrList;
    SecKeychainAttribute attrAttr;
    OSStatus macErr;
    CK_RV error = CKR_OK;

    attrList.count = 1;
    attrList.attr = &attrAttr;
    attrAttr.tag = itemAttr;
    attrAttr.data = data;
    attrAttr.length = len;
    macErr = SecKeychainItemModifyAttributesAndData(itemRef, &attrList, 0, NULL);
    if (noErr != macErr) {
        CKMK_MACERR(dbString, macErr);
        error = CKR_ATTRIBUTE_TYPE_INVALID;
    }
    return error;
}

/*
 * get an attribute (does not operate on the content)
 */
static CK_RV
ckmk_GetDataAttribute(
    ckmkInternalObject *io,
    SecItemAttr itemAttr,
    NSSItem *item,
    char *dbString)
{
    return ckmk_GetCommonAttribute(io, itemAttr, PR_FALSE, item, dbString);
}

/*
 * get an attribute we know is a BOOL.
 */
static CK_RV
ckmk_GetBoolAttribute(
    ckmkInternalObject *io,
    SecItemAttr itemAttr,
    NSSItem *item,
    char *dbString)
{
    SecKeychainAttribute attr;
    SecKeychainAttributeList attrList;
    CK_BBOOL *boolp = NULL;
    PRUint32 len = 0;
    ;
    void *out = NULL;
    CK_RV error = CKR_OK;
    OSStatus macErr;

    attr.tag = itemAttr;
    attr.length = 0;
    attr.data = NULL;
    attrList.count = 1;
    attrList.attr = &attr;

    boolp = nss_ZNEW(NULL, CK_BBOOL);
    if ((CK_BBOOL *)NULL == boolp) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }

    macErr = SecKeychainItemCopyContent(io->u.item.itemRef, NULL,
                                        &attrList, &len, &out);
    if (noErr != macErr) {
        CKMK_MACERR(dbString, macErr);
        error = CKR_ATTRIBUTE_TYPE_INVALID;
        goto loser;
    }
    if (sizeof(PRUint32) != attr.length) {
        error = CKR_ATTRIBUTE_TYPE_INVALID;
        goto loser;
    }
    *boolp = *(PRUint32 *)attr.data ? 1 : 0;
    item->data = boolp;
    boolp = NULL;
    item->size = sizeof(CK_BBOOL);

loser:
    nss_ZFreeIf(boolp);
    SecKeychainItemFreeContent(&attrList, out);
    return error;
}

/*
 * macros for fetching attributes into a cache and returning the
 * appropriate value. These operate inside switch statements
 */
#define CKMK_HANDLE_ITEM(func, io, type, loc, item, error, str) \
    if (0 == (item)->loc.size) {                                \
        error = func(io, type, &(item)->loc, str);              \
    }                                                           \
    return (CKR_OK == (error)) ? &(item)->loc : NULL;

#define CKMK_HANDLE_OPT_ITEM(func, io, type, loc, item, error, str) \
    if (0 == (item)->loc.size) {                                    \
        (void)func(io, type, &(item)->loc, str);                    \
    }                                                               \
    return &(item)->loc;

#define CKMK_HANDLE_BOOL_ITEM(io, type, loc, item, error, str) \
    CKMK_HANDLE_ITEM(ckmk_GetBoolAttribute, io, type, loc, item, error, str)
#define CKMK_HANDLE_DATA_ITEM(io, type, loc, item, error, str) \
    CKMK_HANDLE_ITEM(ckmk_GetDataAttribute, io, type, loc, item, error, str)
#define CKMK_HANDLE_OPT_DATA_ITEM(io, type, loc, item, error, str) \
    CKMK_HANDLE_OPT_ITEM(ckmk_GetDataAttribute, io, type, loc, item, error, str)

/*
 * fetch the unique identifier for each object type.
 */
static void
ckmk_FetchHashKey(
    ckmkInternalObject *io)
{
    NSSItem *key = &io->hashKey;

    if (io->objClass == CKO_CERTIFICATE) {
        ckmk_GetCommonAttribute(io, kSecCertEncodingItemAttr,
                                PR_TRUE, key, "Fetching HashKey (cert)");
    } else {
        ckmk_GetCommonAttribute(io, kSecKeyLabel,
                                PR_FALSE, key, "Fetching HashKey (key)");
    }
}

/*
 * Apple mucks with the actual subject and issuer, so go fetch
 * the real ones ourselves.
 */
static void
ckmk_fetchCert(
    ckmkInternalObject *io)
{
    CK_RV error;
    unsigned char *cert, *next;
    int certSize, thisEntrySize;

    error = ckmk_GetCommonAttribute(io, kSecCertEncodingItemAttr, PR_TRUE,
                                    &io->u.item.derCert, "Fetching Value (cert)");
    if (CKR_OK != error) {
        return;
    }
    /* unwrap the cert bundle */
    cert = nss_ckmk_DERUnwrap((unsigned char *)io->u.item.derCert.data,
                              io->u.item.derCert.size,
                              &certSize, NULL);
    /* unwrap the cert itself */
    /* cert == certdata */
    cert = nss_ckmk_DERUnwrap(cert, certSize, &certSize, NULL);

    /* skip the optional version */
    if ((cert[0] & 0xa0) == 0xa0) {
        nss_ckmk_DERUnwrap(cert, certSize, &thisEntrySize, &next);
        certSize -= next - cert;
        cert = next;
    }
    /* skip the serial number */
    nss_ckmk_DERUnwrap(cert, certSize, &thisEntrySize, &next);
    certSize -= next - cert;
    cert = next;

    /* skip the OID */
    nss_ckmk_DERUnwrap(cert, certSize, &thisEntrySize, &next);
    certSize -= next - cert;
    cert = next;

    /* save the (wrapped) issuer */
    io->u.item.issuer.data = cert;
    nss_ckmk_DERUnwrap(cert, certSize, &thisEntrySize, &next);
    io->u.item.issuer.size = next - cert;
    certSize -= io->u.item.issuer.size;
    cert = next;

    /* skip the OID */
    nss_ckmk_DERUnwrap(cert, certSize, &thisEntrySize, &next);
    certSize -= next - cert;
    cert = next;

    /* save the (wrapped) subject */
    io->u.item.subject.data = cert;
    nss_ckmk_DERUnwrap(cert, certSize, &thisEntrySize, &next);
    io->u.item.subject.size = next - cert;
    certSize -= io->u.item.subject.size;
    cert = next;
}

static void
ckmk_fetchModulus(
    ckmkInternalObject *io)
{
    NSSItem item;
    PRInt32 modLen;
    CK_RV error;

    /* we can't reliably get the modulus for private keys through CSSM (sigh).
     * For NSS this is OK because we really only use this to get the modulus
     * length (unless we are trying to get a public key from a private keys,
     * something CSSM ALSO does not do!).
     */
    error = ckmk_GetDataAttribute(io, kSecKeyKeySizeInBits, &item,
                                  "Key Fetch Modulus");
    if (CKR_OK != error) {
        return;
    }

    modLen = *(PRInt32 *)item.data;
    modLen = modLen / 8; /* convert from bits to bytes */

    nss_ZFreeIf(item.data);
    io->u.item.modulus.data = nss_ZNEWARRAY(NULL, char, modLen);
    if (NULL == io->u.item.modulus.data) {
        return;
    }
    *(char *)io->u.item.modulus.data = 0x80; /* fake NSS out or it will
                                              * drop the first byte */
    io->u.item.modulus.size = modLen;
    return;
}

const NSSItem *
ckmk_FetchCertAttribute(
    ckmkInternalObject *io,
    CK_ATTRIBUTE_TYPE type,
    CK_RV *pError)
{
    ckmkItemObject *item = &io->u.item;
    *pError = CKR_OK;
    switch (type) {
        case CKA_CLASS:
            return &ckmk_certClassItem;
        case CKA_TOKEN:
        case CKA_MODIFIABLE:
            return &ckmk_trueItem;
        case CKA_PRIVATE:
            return &ckmk_falseItem;
        case CKA_CERTIFICATE_TYPE:
            return &ckmk_x509Item;
        case CKA_LABEL:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecLabelItemAttr, label, item, *pError,
                                      "Cert:Label attr")
        case CKA_SUBJECT:
            /* OK, well apple does provide an subject and issuer attribute, but they
             * decided to cannonicalize that value. Probably a good move for them,
             * but makes it useless for most users of PKCS #11.. Get the real subject
             * from the certificate */
            if (0 == item->derCert.size) {
                ckmk_fetchCert(io);
            }
            return &item->subject;
        case CKA_ISSUER:
            if (0 == item->derCert.size) {
                ckmk_fetchCert(io);
            }
            return &item->issuer;
        case CKA_SERIAL_NUMBER:
            CKMK_HANDLE_DATA_ITEM(io, kSecSerialNumberItemAttr, serial, item, *pError,
                                  "Cert:Serial Number attr")
        case CKA_VALUE:
            if (0 == item->derCert.size) {
                ckmk_fetchCert(io);
            }
            return &item->derCert;
        case CKA_ID:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecPublicKeyHashItemAttr, id, item, *pError,
                                      "Cert:ID attr")
        default:
            *pError = CKR_ATTRIBUTE_TYPE_INVALID;
            break;
    }
    return NULL;
}

const NSSItem *
ckmk_FetchPubKeyAttribute(
    ckmkInternalObject *io,
    CK_ATTRIBUTE_TYPE type,
    CK_RV *pError)
{
    ckmkItemObject *item = &io->u.item;
    *pError = CKR_OK;

    switch (type) {
        case CKA_CLASS:
            return &ckmk_pubKeyClassItem;
        case CKA_TOKEN:
        case CKA_LOCAL:
            return &ckmk_trueItem;
        case CKA_KEY_TYPE:
            return &ckmk_rsaItem;
        case CKA_LABEL:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecKeyPrintName, label, item, *pError,
                                      "PubKey:Label attr")
        case CKA_ENCRYPT:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyEncrypt, encrypt, item, *pError,
                                  "PubKey:Encrypt attr")
        case CKA_VERIFY:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyVerify, verify, item, *pError,
                                  "PubKey:Verify attr")
        case CKA_VERIFY_RECOVER:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyVerifyRecover, verifyRecover,
                                  item, *pError, "PubKey:VerifyRecover attr")
        case CKA_PRIVATE:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyPrivate, private, item, *pError,
                                  "PubKey:Private attr")
        case CKA_MODIFIABLE:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyModifiable, modify, item, *pError,
                                  "PubKey:Modify attr")
        case CKA_DERIVE:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyDerive, derive, item, *pError,
                                  "PubKey:Derive attr")
        case CKA_WRAP:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyWrap, wrap, item, *pError,
                                  "PubKey:Wrap attr")
        case CKA_SUBJECT:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecSubjectItemAttr, subject, item, *pError,
                                      "PubKey:Subect attr")
        case CKA_MODULUS:
            return &ckmk_emptyItem;
        case CKA_PUBLIC_EXPONENT:
            return &ckmk_emptyItem;
        case CKA_ID:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecKeyLabel, id, item, *pError,
                                      "PubKey:ID attr")
        default:
            *pError = CKR_ATTRIBUTE_TYPE_INVALID;
            break;
    }
    return NULL;
}

const NSSItem *
ckmk_FetchPrivKeyAttribute(
    ckmkInternalObject *io,
    CK_ATTRIBUTE_TYPE type,
    CK_RV *pError)
{
    ckmkItemObject *item = &io->u.item;
    *pError = CKR_OK;

    switch (type) {
        case CKA_CLASS:
            return &ckmk_privKeyClassItem;
        case CKA_TOKEN:
        case CKA_LOCAL:
            return &ckmk_trueItem;
        case CKA_SENSITIVE:
        case CKA_EXTRACTABLE: /* will probably move in the future */
        case CKA_ALWAYS_SENSITIVE:
        case CKA_NEVER_EXTRACTABLE:
            return &ckmk_falseItem;
        case CKA_KEY_TYPE:
            return &ckmk_rsaItem;
        case CKA_LABEL:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecKeyPrintName, label, item, *pError,
                                      "PrivateKey:Label attr")
        case CKA_DECRYPT:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyDecrypt, decrypt, item, *pError,
                                  "PrivateKey:Decrypt attr")
        case CKA_SIGN:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeySign, sign, item, *pError,
                                  "PrivateKey:Sign attr")
        case CKA_SIGN_RECOVER:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeySignRecover, signRecover, item, *pError,
                                  "PrivateKey:Sign Recover attr")
        case CKA_PRIVATE:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyPrivate, private, item, *pError,
                                  "PrivateKey:Private attr")
        case CKA_MODIFIABLE:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyModifiable, modify, item, *pError,
                                  "PrivateKey:Modify attr")
        case CKA_DERIVE:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyDerive, derive, item, *pError,
                                  "PrivateKey:Derive attr")
        case CKA_UNWRAP:
            CKMK_HANDLE_BOOL_ITEM(io, kSecKeyUnwrap, unwrap, item, *pError,
                                  "PrivateKey:Unwrap attr")
        case CKA_SUBJECT:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecSubjectItemAttr, subject, item, *pError,
                                      "PrivateKey:Subject attr")
        case CKA_MODULUS:
            if (0 == item->modulus.size) {
                ckmk_fetchModulus(io);
            }
            return &item->modulus;
        case CKA_PUBLIC_EXPONENT:
            return &ckmk_emptyItem;
#ifdef notdef
        /* the following are sensitive attributes. We could implement them for
         * sensitive keys using the key export function, but it's better to
         * just support wrap through this token. That will more reliably allow us
         * to export any private key that is truly exportable.
         */
        case CKA_PRIVATE_EXPONENT:
            CKMK_HANDLE_DATA_ITEM(io, kSecPrivateExponentItemAttr, privateExponent,
                                  item, *pError)
        case CKA_PRIME_1:
            CKMK_HANDLE_DATA_ITEM(io, kSecPrime1ItemAttr, prime1, item, *pError)
        case CKA_PRIME_2:
            CKMK_HANDLE_DATA_ITEM(io, kSecPrime2ItemAttr, prime2, item, *pError)
        case CKA_EXPONENT_1:
            CKMK_HANDLE_DATA_ITEM(io, kSecExponent1ItemAttr, exponent1, item, *pError)
        case CKA_EXPONENT_2:
            CKMK_HANDLE_DATA_ITEM(io, kSecExponent2ItemAttr, exponent2, item, *pError)
        case CKA_COEFFICIENT:
            CKMK_HANDLE_DATA_ITEM(io, kSecCoefficientItemAttr, coefficient,
                                  item, *pError)
#endif
        case CKA_ID:
            CKMK_HANDLE_OPT_DATA_ITEM(io, kSecKeyLabel, id, item, *pError,
                                      "PrivateKey:ID attr")
        default:
            *pError = CKR_ATTRIBUTE_TYPE_INVALID;
            return NULL;
    }
}

const NSSItem *
nss_ckmk_FetchAttribute(
    ckmkInternalObject *io,
    CK_ATTRIBUTE_TYPE type,
    CK_RV *pError)
{
    CK_ULONG i;
    const NSSItem *value = NULL;

    if (io->type == ckmkRaw) {
        for (i = 0; i < io->u.raw.n; i++) {
            if (type == io->u.raw.types[i]) {
                return &io->u.raw.items[i];
            }
        }
        *pError = CKR_ATTRIBUTE_TYPE_INVALID;
        return NULL;
    }
    /* deal with the common attributes */
    switch (io->objClass) {
        case CKO_CERTIFICATE:
            value = ckmk_FetchCertAttribute(io, type, pError);
            break;
        case CKO_PRIVATE_KEY:
            value = ckmk_FetchPrivKeyAttribute(io, type, pError);
            break;
        case CKO_PUBLIC_KEY:
            value = ckmk_FetchPubKeyAttribute(io, type, pError);
            break;
        default:
            *pError = CKR_OBJECT_HANDLE_INVALID;
            return NULL;
    }

#ifdef DEBUG
    if (CKA_ID == type) {
        itemdump("id: ", value->data, value->size, *pError);
    }
#endif
    return value;
}

static void
ckmk_removeObjectFromHash(
    ckmkInternalObject *io);

/*
 *
 * These are the MSObject functions we need to implement
 *
 * Finalize - unneeded (actually we should clean up the hashtables)
 * Destroy
 * IsTokenObject - CK_TRUE
 * GetAttributeCount
 * GetAttributeTypes
 * GetAttributeSize
 * GetAttribute
 * SetAttribute
 * GetObjectSize
 */

static CK_RV
ckmk_mdObject_Destroy(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance)
{
    ckmkInternalObject *io = (ckmkInternalObject *)mdObject->etc;
    OSStatus macErr;

    if (ckmkRaw == io->type) {
        /* there is not 'object write protected' error, use the next best thing */
        return CKR_TOKEN_WRITE_PROTECTED;
    }

    /* This API is done well. The following 4 lines are the complete apple
     * specific part of this implementation */
    macErr = SecKeychainItemDelete(io->u.item.itemRef);
    if (noErr != macErr) {
        CKMK_MACERR("Delete object", macErr);
    }

    /* remove it from the hash */
    ckmk_removeObjectFromHash(io);

    /* free the puppy.. */
    nss_ckmk_DestroyInternalObject(io);

    return CKR_OK;
}

static CK_BBOOL
ckmk_mdObject_IsTokenObject(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance)
{
    return CK_TRUE;
}

static CK_ULONG
ckmk_mdObject_GetAttributeCount(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_RV *pError)
{
    ckmkInternalObject *io = (ckmkInternalObject *)mdObject->etc;

    if (ckmkRaw == io->type) {
        return io->u.raw.n;
    }
    switch (io->objClass) {
        case CKO_CERTIFICATE:
            return certAttrsCount;
        case CKO_PUBLIC_KEY:
            return pubKeyAttrsCount;
        case CKO_PRIVATE_KEY:
            return privKeyAttrsCount;
        default:
            break;
    }
    return 0;
}

static CK_RV
ckmk_mdObject_GetAttributeTypes(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_ATTRIBUTE_TYPE_PTR typeArray,
    CK_ULONG ulCount)
{
    ckmkInternalObject *io = (ckmkInternalObject *)mdObject->etc;
    CK_ULONG i;
    CK_RV error = CKR_OK;
    const CK_ATTRIBUTE_TYPE *attrs = NULL;
    CK_ULONG size = ckmk_mdObject_GetAttributeCount(
        mdObject, fwObject, mdSession, fwSession,
        mdToken, fwToken, mdInstance, fwInstance, &error);

    if (size != ulCount) {
        return CKR_BUFFER_TOO_SMALL;
    }
    if (io->type == ckmkRaw) {
        attrs = io->u.raw.types;
    } else
        switch (io->objClass) {
            case CKO_CERTIFICATE:
                attrs =
                    certAttrs;
                break;
            case CKO_PUBLIC_KEY:
                attrs =
                    pubKeyAttrs;
                break;
            case CKO_PRIVATE_KEY:
                attrs =
                    privKeyAttrs;
                break;
            default:
                return CKR_OK;
        }

    for (i = 0; i < size; i++) {
        typeArray[i] = attrs[i];
    }

    return CKR_OK;
}

static CK_ULONG
ckmk_mdObject_GetAttributeSize(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_ATTRIBUTE_TYPE attribute,
    CK_RV *pError)
{
    ckmkInternalObject *io = (ckmkInternalObject *)mdObject->etc;

    const NSSItem *b;

    b = nss_ckmk_FetchAttribute(io, attribute, pError);

    if ((const NSSItem *)NULL == b) {
        return 0;
    }
    return b->size;
}

static CK_RV
ckmk_mdObject_SetAttribute(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_ATTRIBUTE_TYPE attribute,
    NSSItem *value)
{
    ckmkInternalObject *io = (ckmkInternalObject *)mdObject->etc;
    SecKeychainItemRef itemRef;

    if (io->type == ckmkRaw) {
        return CKR_TOKEN_WRITE_PROTECTED;
    }
    itemRef = io->u.item.itemRef;

    switch (io->objClass) {
        case CKO_PRIVATE_KEY:
        case CKO_PUBLIC_KEY:
            switch (attribute) {
                case CKA_ID:
                    ckmk_updateAttribute(itemRef, kSecKeyLabel,
                                         value->data, value->size, "Set Attr Key ID");
#ifdef DEBUG
                    itemdump("key id: ", value->data, value->size, CKR_OK);
#endif
                    break;
                case CKA_LABEL:
                    ckmk_updateAttribute(itemRef, kSecKeyPrintName, value->data,
                                         value->size, "Set Attr Key Label");
                    break;
                default:
                    break;
            }
            break;

        case CKO_CERTIFICATE:
            switch (attribute) {
                case CKA_ID:
                    ckmk_updateAttribute(itemRef, kSecPublicKeyHashItemAttr,
                                         value->data, value->size, "Set Attr Cert ID");
                    break;
                case CKA_LABEL:
                    ckmk_updateAttribute(itemRef, kSecLabelItemAttr, value->data,
                                         value->size, "Set Attr Cert Label");
                    break;
                default:
                    break;
            }
            break;

        default:
            break;
    }
    return CKR_OK;
}

static NSSCKFWItem
ckmk_mdObject_GetAttribute(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_ATTRIBUTE_TYPE attribute,
    CK_RV *pError)
{
    NSSCKFWItem mdItem;
    ckmkInternalObject *io = (ckmkInternalObject *)mdObject->etc;

    mdItem.needsFreeing = PR_FALSE;
    mdItem.item = (NSSItem *)nss_ckmk_FetchAttribute(io, attribute, pError);

    return mdItem;
}

static CK_ULONG
ckmk_mdObject_GetObjectSize(
    NSSCKMDObject *mdObject,
    NSSCKFWObject *fwObject,
    NSSCKMDSession *mdSession,
    NSSCKFWSession *fwSession,
    NSSCKMDToken *mdToken,
    NSSCKFWToken *fwToken,
    NSSCKMDInstance *mdInstance,
    NSSCKFWInstance *fwInstance,
    CK_RV *pError)
{
    CK_ULONG rv = 1;

    /* size is irrelevant to this token */
    return rv;
}

static const NSSCKMDObject
    ckmk_prototype_mdObject = {
        (void *)NULL, /* etc */
        NULL,         /* Finalize */
        ckmk_mdObject_Destroy,
        ckmk_mdObject_IsTokenObject,
        ckmk_mdObject_GetAttributeCount,
        ckmk_mdObject_GetAttributeTypes,
        ckmk_mdObject_GetAttributeSize,
        ckmk_mdObject_GetAttribute,
        NULL, /* FreeAttribute */
        ckmk_mdObject_SetAttribute,
        ckmk_mdObject_GetObjectSize,
        (void *)NULL /* null terminator */
    };

static nssHash *ckmkInternalObjectHash = NULL;

NSS_IMPLEMENT NSSCKMDObject *
nss_ckmk_CreateMDObject(
    NSSArena *arena,
    ckmkInternalObject *io,
    CK_RV *pError)
{
    if ((nssHash *)NULL == ckmkInternalObjectHash) {
        ckmkInternalObjectHash = nssHash_CreateItem(NULL, 10);
    }
    if (ckmkItem == io->type) {
        /* the hash key, not a cryptographic key */
        NSSItem *key = &io->hashKey;
        ckmkInternalObject *old_o = NULL;

        if (key->size == 0) {
            ckmk_FetchHashKey(io);
        }
        old_o = (ckmkInternalObject *)
            nssHash_Lookup(ckmkInternalObjectHash, key);
        if (!old_o) {
            nssHash_Add(ckmkInternalObjectHash, key, io);
        } else if (old_o != io) {
            nss_ckmk_DestroyInternalObject(io);
            io = old_o;
        }
    }

    if ((void *)NULL == io->mdObject.etc) {
        (void)nsslibc_memcpy(&io->mdObject, &ckmk_prototype_mdObject,
                             sizeof(ckmk_prototype_mdObject));
        io->mdObject.etc = (void *)io;
    }
    return &io->mdObject;
}

static void
ckmk_removeObjectFromHash(
    ckmkInternalObject *io)
{
    NSSItem *key = &io->hashKey;

    if ((nssHash *)NULL == ckmkInternalObjectHash) {
        return;
    }
    if (key->size == 0) {
        ckmk_FetchHashKey(io);
    }
    nssHash_Remove(ckmkInternalObjectHash, key);
    return;
}

void
nss_ckmk_DestroyInternalObject(
    ckmkInternalObject *io)
{
    switch (io->type) {
        case ckmkRaw:
            return;
        case ckmkItem:
            nss_ZFreeIf(io->u.item.modify.data);
            nss_ZFreeIf(io->u.item.private.data);
            nss_ZFreeIf(io->u.item.encrypt.data);
            nss_ZFreeIf(io->u.item.decrypt.data);
            nss_ZFreeIf(io->u.item.derive.data);
            nss_ZFreeIf(io->u.item.sign.data);
            nss_ZFreeIf(io->u.item.signRecover.data);
            nss_ZFreeIf(io->u.item.verify.data);
            nss_ZFreeIf(io->u.item.verifyRecover.data);
            nss_ZFreeIf(io->u.item.wrap.data);
            nss_ZFreeIf(io->u.item.unwrap.data);
            nss_ZFreeIf(io->u.item.label.data);
            /*nss_ZFreeIf(io->u.item.subject.data); */
            /*nss_ZFreeIf(io->u.item.issuer.data); */
            nss_ZFreeIf(io->u.item.serial.data);
            nss_ZFreeIf(io->u.item.modulus.data);
            nss_ZFreeIf(io->u.item.exponent.data);
            nss_ZFreeIf(io->u.item.privateExponent.data);
            nss_ZFreeIf(io->u.item.prime1.data);
            nss_ZFreeIf(io->u.item.prime2.data);
            nss_ZFreeIf(io->u.item.exponent1.data);
            nss_ZFreeIf(io->u.item.exponent2.data);
            nss_ZFreeIf(io->u.item.coefficient.data);
            break;
    }
    nss_ZFreeIf(io);
    return;
}

static ckmkInternalObject *
nss_ckmk_NewInternalObject(
    CK_OBJECT_CLASS objClass,
    SecKeychainItemRef itemRef,
    SecItemClass itemClass,
    CK_RV *pError)
{
    ckmkInternalObject *io = nss_ZNEW(NULL, ckmkInternalObject);

    if ((ckmkInternalObject *)NULL == io) {
        *pError = CKR_HOST_MEMORY;
        return io;
    }
    io->type = ckmkItem;
    io->objClass = objClass;
    io->u.item.itemRef = itemRef;
    io->u.item.itemClass = itemClass;
    return io;
}

/*
 * Apple doesn't alway have a default keyChain set by the OS, use the
 * SearchList to try to find one.
 */
static CK_RV
ckmk_GetSafeDefaultKeychain(
    SecKeychainRef *keychainRef)
{
    OSStatus macErr;
    CFArrayRef searchList = 0;
    CK_RV error = CKR_OK;

    macErr = SecKeychainCopyDefault(keychainRef);
    if (noErr != macErr) {
        int searchCount = 0;
        if (errSecNoDefaultKeychain != macErr) {
            CKMK_MACERR("Getting default key chain", macErr);
            error = CKR_GENERAL_ERROR;
            goto loser;
        }
        /* ok, we don't have a default key chain, find one */
        macErr = SecKeychainCopySearchList(&searchList);
        if (noErr != macErr) {
            CKMK_MACERR("failed to find a keyring searchList", macErr);
            error = CKR_DEVICE_REMOVED;
            goto loser;
        }
        searchCount = CFArrayGetCount(searchList);
        if (searchCount < 1) {
            error = CKR_DEVICE_REMOVED;
            goto loser;
        }
        *keychainRef =
            (SecKeychainRef)CFRetain(CFArrayGetValueAtIndex(searchList, 0));
        if (0 == *keychainRef) {
            error = CKR_DEVICE_REMOVED;
            goto loser;
        }
        /* should we set it as default? */
    }
loser:
    if (0 != searchList) {
        CFRelease(searchList);
    }
    return error;
}
static ckmkInternalObject *
nss_ckmk_CreateCertificate(
    NSSCKFWSession *fwSession,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount,
    CK_RV *pError)
{
    NSSItem value;
    ckmkInternalObject *io = NULL;
    OSStatus macErr;
    SecCertificateRef certRef;
    SecKeychainItemRef itemRef;
    SecKeychainRef keychainRef;
    CSSM_DATA certData;

    *pError = nss_ckmk_GetAttribute(CKA_VALUE, pTemplate,
                                    ulAttributeCount, &value);
    if (CKR_OK != *pError) {
        goto loser;
    }

    certData.Data = value.data;
    certData.Length = value.size;
    macErr = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3,
                                          CSSM_CERT_ENCODING_BER, &certRef);
    if (noErr != macErr) {
        CKMK_MACERR("Create cert from data Failed", macErr);
        *pError = CKR_GENERAL_ERROR; /* need to map macErr */
        goto loser;
    }

    *pError = ckmk_GetSafeDefaultKeychain(&keychainRef);
    if (CKR_OK != *pError) {
        goto loser;
    }

    macErr = SecCertificateAddToKeychain(certRef, keychainRef);
    itemRef = (SecKeychainItemRef)certRef;
    if (errSecDuplicateItem != macErr) {
        NSSItem keyID = { NULL, 0 };
        char *nickname = NULL;
        CK_RV dummy;

        if (noErr != macErr) {
            CKMK_MACERR("Add cert to keychain Failed", macErr);
            *pError = CKR_GENERAL_ERROR; /* need to map macErr */
            goto loser;
        }
        /* these two are optional */
        nickname = nss_ckmk_GetStringAttribute(CKA_LABEL, pTemplate,
                                               ulAttributeCount, &dummy);
        /* we've added a new one, update the attributes in the key ring */
        if (nickname) {
            ckmk_updateAttribute(itemRef, kSecLabelItemAttr, nickname,
                                 strlen(nickname) + 1, "Modify Cert Label");
            nss_ZFreeIf(nickname);
        }
        dummy = nss_ckmk_GetAttribute(CKA_ID, pTemplate,
                                      ulAttributeCount, &keyID);
        if (CKR_OK == dummy) {
            dummy = ckmk_updateAttribute(itemRef, kSecPublicKeyHashItemAttr,
                                         keyID.data, keyID.size, "Modify Cert ID");
        }
    }

    io = nss_ckmk_NewInternalObject(CKO_CERTIFICATE, itemRef,
                                    kSecCertificateItemClass, pError);
    if ((ckmkInternalObject *)NULL != io) {
        itemRef = 0;
    }

loser:
    if (0 != itemRef) {
        CFRelease(itemRef);
    }
    if (0 != keychainRef) {
        CFRelease(keychainRef);
    }

    return io;
}

/*
 * PKCS #8 attributes
 */
struct ckmk_AttributeStr {
    SECItem attrType;
    SECItem *attrValue;
};
typedef struct ckmk_AttributeStr ckmk_Attribute;

/*
 ** A PKCS#8 private key info object
 */
struct PrivateKeyInfoStr {
    PLArenaPool *arena;
    SECItem version;
    SECAlgorithmID algorithm;
    SECItem privateKey;
    ckmk_Attribute **attributes;
};
typedef struct PrivateKeyInfoStr PrivateKeyInfo;

const SEC_ASN1Template ckmk_RSAPrivateKeyTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(RSAPrivateKey) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, version) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, modulus) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, publicExponent) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, privateExponent) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, prime1) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, prime2) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, exponent1) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, exponent2) },
    { SEC_ASN1_INTEGER, offsetof(RSAPrivateKey, coefficient) },
    { 0 }
};

const SEC_ASN1Template ckmk_AttributeTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ckmk_Attribute) },
    { SEC_ASN1_OBJECT_ID, offsetof(ckmk_Attribute, attrType) },
    { SEC_ASN1_SET_OF, offsetof(ckmk_Attribute, attrValue),
      SEC_AnyTemplate },
    { 0 }
};

const SEC_ASN1Template ckmk_SetOfAttributeTemplate[] = {
    { SEC_ASN1_SET_OF, 0, ckmk_AttributeTemplate },
};

SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)

/* ASN1 Templates for new decoder/encoder */
const SEC_ASN1Template ckmk_PrivateKeyInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PrivateKeyInfo) },
    { SEC_ASN1_INTEGER, offsetof(PrivateKeyInfo, version) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(PrivateKeyInfo, algorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_OCTET_STRING, offsetof(PrivateKeyInfo, privateKey) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
      offsetof(PrivateKeyInfo, attributes), ckmk_SetOfAttributeTemplate },
    { 0 }
};

#define CKMK_PRIVATE_KEY_INFO_VERSION 0
static CK_RV
ckmk_CreateRSAKeyBlob(
    RSAPrivateKey *lk,
    NSSItem *keyBlob)
{
    PrivateKeyInfo *pki = NULL;
    PLArenaPool *arena = NULL;
    SECOidTag algorithm = SEC_OID_UNKNOWN;
    void *dummy;
    SECStatus rv;
    SECItem *encodedKey = NULL;
    CK_RV error = CKR_OK;

    arena = PORT_NewArena(2048); /* XXX different size? */
    if (!arena) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }

    pki = (PrivateKeyInfo *)PORT_ArenaZAlloc(arena, sizeof(PrivateKeyInfo));
    if (!pki) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }
    pki->arena = arena;

    dummy = SEC_ASN1EncodeItem(arena, &pki->privateKey, lk,
                               ckmk_RSAPrivateKeyTemplate);
    algorithm = SEC_OID_PKCS1_RSA_ENCRYPTION;

    if (!dummy) {
        error = CKR_DEVICE_ERROR; /* should map NSS SECError */
        goto loser;
    }

    rv = SECOID_SetAlgorithmID(arena, &pki->algorithm, algorithm,
                               (SECItem *)NULL);
    if (rv != SECSuccess) {
        error = CKR_DEVICE_ERROR; /* should map NSS SECError */
        goto loser;
    }

    dummy = SEC_ASN1EncodeInteger(arena, &pki->version,
                                  CKMK_PRIVATE_KEY_INFO_VERSION);
    if (!dummy) {
        error = CKR_DEVICE_ERROR; /* should map NSS SECError */
        goto loser;
    }

    encodedKey = SEC_ASN1EncodeItem(NULL, NULL, pki,
                                    ckmk_PrivateKeyInfoTemplate);
    if (!encodedKey) {
        error = CKR_DEVICE_ERROR;
        goto loser;
    }

    keyBlob->data = nss_ZNEWARRAY(NULL, char, encodedKey->len);
    if (NULL == keyBlob->data) {
        error = CKR_HOST_MEMORY;
        goto loser;
    }
    nsslibc_memcpy(keyBlob->data, encodedKey->data, encodedKey->len);
    keyBlob->size = encodedKey->len;

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }
    if (encodedKey) {
        SECITEM_FreeItem(encodedKey, PR_TRUE);
    }

    return error;
}
/*
 * There MUST be a better way to do this. For now, find the key based on the
 * default name Apple gives it once we import.
 */
#define IMPORTED_NAME "Imported Private Key"
static CK_RV
ckmk_FindImportedKey(
    SecKeychainRef keychainRef,
    SecItemClass itemClass,
    SecKeychainItemRef *outItemRef)
{
    OSStatus macErr;
    SecKeychainSearchRef searchRef = 0;
    SecKeychainItemRef newItemRef;

    macErr = SecKeychainSearchCreateFromAttributes(keychainRef, itemClass,
                                                   NULL, &searchRef);
    if (noErr != macErr) {
        CKMK_MACERR("Can't search for Key", macErr);
        return CKR_GENERAL_ERROR;
    }
    while (noErr == SecKeychainSearchCopyNext(searchRef, &newItemRef)) {
        SecKeychainAttributeList *attrList = NULL;
        SecKeychainAttributeInfo attrInfo;
        SecItemAttr itemAttr = kSecKeyPrintName;
        PRUint32 attrFormat = 0;
        OSStatus macErr;

        attrInfo.count = 1;
        attrInfo.tag = &itemAttr;
        attrInfo.format = &attrFormat;

        macErr = SecKeychainItemCopyAttributesAndData(newItemRef,
                                                      &attrInfo, NULL, &attrList, NULL, NULL);
        if (noErr == macErr) {
            if (nsslibc_memcmp(attrList->attr->data, IMPORTED_NAME,
                               attrList->attr->length, NULL) == 0) {
                *outItemRef = newItemRef;
                CFRelease(searchRef);
                SecKeychainItemFreeAttributesAndData(attrList, NULL);
                return CKR_OK;
            }
            SecKeychainItemFreeAttributesAndData(attrList, NULL);
        }
        CFRelease(newItemRef);
    }
    CFRelease(searchRef);
    return CKR_GENERAL_ERROR; /* we can come up with something better! */
}

static ckmkInternalObject *
nss_ckmk_CreatePrivateKey(
    NSSCKFWSession *fwSession,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount,
    CK_RV *pError)
{
    NSSItem attribute;
    RSAPrivateKey lk;
    NSSItem keyID;
    char *nickname = NULL;
    ckmkInternalObject *io = NULL;
    CK_KEY_TYPE keyType;
    OSStatus macErr;
    SecKeychainItemRef itemRef = 0;
    NSSItem keyBlob = { NULL, 0 };
    CFDataRef dataRef = 0;
    SecExternalFormat inputFormat = kSecFormatBSAFE;
    /*SecExternalFormat inputFormat = kSecFormatOpenSSL;  */
    SecExternalItemType itemType = kSecItemTypePrivateKey;
    SecKeyImportExportParameters keyParams;
    SecKeychainRef targetKeychain = 0;
    unsigned char zero = 0;
    CK_RV error;

    keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
    keyParams.flags = 0;
    keyParams.passphrase = 0;
    keyParams.alertTitle = 0;
    keyParams.alertPrompt = 0;
    keyParams.accessRef = 0;                          /* default */
    keyParams.keyUsage = 0;                           /* will get filled in */
    keyParams.keyAttributes = CSSM_KEYATTR_PERMANENT; /* will get filled in */
    keyType = nss_ckmk_GetULongAttribute(CKA_KEY_TYPE, pTemplate, ulAttributeCount, pError);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    if (CKK_RSA != keyType) {
        *pError = CKR_ATTRIBUTE_VALUE_INVALID;
        return (ckmkInternalObject *)NULL;
    }
    if (nss_ckmk_GetBoolAttribute(CKA_DECRYPT,
                                  pTemplate, ulAttributeCount, CK_TRUE)) {
        keyParams.keyUsage |= CSSM_KEYUSE_DECRYPT;
    }
    if (nss_ckmk_GetBoolAttribute(CKA_UNWRAP,
                                  pTemplate, ulAttributeCount, CK_TRUE)) {
        keyParams.keyUsage |= CSSM_KEYUSE_UNWRAP;
    }
    if (nss_ckmk_GetBoolAttribute(CKA_SIGN,
                                  pTemplate, ulAttributeCount, CK_TRUE)) {
        keyParams.keyUsage |= CSSM_KEYUSE_SIGN;
    }
    if (nss_ckmk_GetBoolAttribute(CKA_DERIVE,
                                  pTemplate, ulAttributeCount, CK_FALSE)) {
        keyParams.keyUsage |= CSSM_KEYUSE_DERIVE;
    }
    if (nss_ckmk_GetBoolAttribute(CKA_SENSITIVE,
                                  pTemplate, ulAttributeCount, CK_TRUE)) {
        keyParams.keyAttributes |= CSSM_KEYATTR_SENSITIVE;
    }
    if (nss_ckmk_GetBoolAttribute(CKA_EXTRACTABLE,
                                  pTemplate, ulAttributeCount, CK_TRUE)) {
        keyParams.keyAttributes |= CSSM_KEYATTR_EXTRACTABLE;
    }

    lk.version.type = siUnsignedInteger;
    lk.version.data = &zero;
    lk.version.len = 1;

    *pError = nss_ckmk_GetAttribute(CKA_MODULUS, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.modulus.type = siUnsignedInteger;
    lk.modulus.data = attribute.data;
    lk.modulus.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_PUBLIC_EXPONENT, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.publicExponent.type = siUnsignedInteger;
    lk.publicExponent.data = attribute.data;
    lk.publicExponent.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_PRIVATE_EXPONENT, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.privateExponent.type = siUnsignedInteger;
    lk.privateExponent.data = attribute.data;
    lk.privateExponent.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_PRIME_1, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.prime1.type = siUnsignedInteger;
    lk.prime1.data = attribute.data;
    lk.prime1.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_PRIME_2, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.prime2.type = siUnsignedInteger;
    lk.prime2.data = attribute.data;
    lk.prime2.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_EXPONENT_1, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.exponent1.type = siUnsignedInteger;
    lk.exponent1.data = attribute.data;
    lk.exponent1.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_EXPONENT_2, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.exponent2.type = siUnsignedInteger;
    lk.exponent2.data = attribute.data;
    lk.exponent2.len = attribute.size;

    *pError = nss_ckmk_GetAttribute(CKA_COEFFICIENT, pTemplate,
                                    ulAttributeCount, &attribute);
    if (CKR_OK != *pError) {
        return (ckmkInternalObject *)NULL;
    }
    lk.coefficient.type = siUnsignedInteger;
    lk.coefficient.data = attribute.data;
    lk.coefficient.len = attribute.size;

    /* ASN1 Encode the pkcs8 structure... look at softoken to see how this
     * is done... */
    error = ckmk_CreateRSAKeyBlob(&lk, &keyBlob);
    if (CKR_OK != error) {
        goto loser;
    }

    dataRef = CFDataCreate(NULL, (UInt8 *)keyBlob.data, keyBlob.size);
    if (0 == dataRef) {
        *pError = CKR_HOST_MEMORY;
        goto loser;
    }

    *pError == ckmk_GetSafeDefaultKeychain(&targetKeychain);
    if (CKR_OK != *pError) {
        goto loser;
    }

    /* the itemArray that is returned is useless. the item does not
     * is 'not on the key chain' so none of the modify calls work on it.
     * It also has a key that isn't the same key as the one in the actual
     * key chain. In short it isn't the item we want, and it gives us zero
     * information about the item we want, so don't even bother with it...
     */
    macErr = SecKeychainItemImport(dataRef, NULL, &inputFormat, &itemType, 0,
                                   &keyParams, targetKeychain, NULL);
    if (noErr != macErr) {
        CKMK_MACERR("Import Private Key", macErr);
        *pError = CKR_GENERAL_ERROR;
        goto loser;
    }

    *pError = ckmk_FindImportedKey(targetKeychain,
                                   CSSM_DL_DB_RECORD_PRIVATE_KEY,
                                   &itemRef);
    if (CKR_OK != *pError) {
#ifdef DEBUG
        fprintf(stderr, "couldn't find key in keychain \n");
#endif
        goto loser;
    }

    /* set the CKA_ID and  the CKA_LABEL */
    error = nss_ckmk_GetAttribute(CKA_ID, pTemplate,
                                  ulAttributeCount, &keyID);
    if (CKR_OK == error) {
        error = ckmk_updateAttribute(itemRef, kSecKeyLabel,
                                     keyID.data, keyID.size, "Modify Key ID");
#ifdef DEBUG
        itemdump("key id: ", keyID.data, keyID.size, error);
#endif
    }
    nickname = nss_ckmk_GetStringAttribute(CKA_LABEL, pTemplate,
                                           ulAttributeCount, &error);
    if (nickname) {
        ckmk_updateAttribute(itemRef, kSecKeyPrintName, nickname,
                             strlen(nickname) + 1, "Modify Key Label");
    } else {
#define DEFAULT_NICKNAME "NSS Imported Key"
        ckmk_updateAttribute(itemRef, kSecKeyPrintName, DEFAULT_NICKNAME,
                             sizeof(DEFAULT_NICKNAME), "Modify Key Label");
    }

    io = nss_ckmk_NewInternalObject(CKO_PRIVATE_KEY, itemRef,
                                    CSSM_DL_DB_RECORD_PRIVATE_KEY, pError);
    if ((ckmkInternalObject *)NULL == io) {
        CFRelease(itemRef);
    }

    return io;

loser:
    /* free the key blob */
    if (keyBlob.data) {
        nss_ZFreeIf(keyBlob.data);
    }
    if (0 != targetKeychain) {
        CFRelease(targetKeychain);
    }
    if (0 != dataRef) {
        CFRelease(dataRef);
    }
    return io;
}

NSS_EXTERN NSSCKMDObject *
nss_ckmk_CreateObject(
    NSSCKFWSession *fwSession,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount,
    CK_RV *pError)
{
    CK_OBJECT_CLASS objClass;
    ckmkInternalObject *io = NULL;
    CK_BBOOL isToken;

    /*
     * only create token objects
     */
    isToken = nss_ckmk_GetBoolAttribute(CKA_TOKEN, pTemplate,
                                        ulAttributeCount, CK_FALSE);
    if (!isToken) {
        *pError = CKR_ATTRIBUTE_VALUE_INVALID;
        return (NSSCKMDObject *)NULL;
    }

    /*
     * only create keys and certs.
     */
    objClass = nss_ckmk_GetULongAttribute(CKA_CLASS, pTemplate,
                                          ulAttributeCount, pError);
    if (CKR_OK != *pError) {
        return (NSSCKMDObject *)NULL;
    }
#ifdef notdef
    if (objClass == CKO_PUBLIC_KEY) {
        return CKR_OK; /* fake public key creation, happens as a side effect of
                        * private key creation */
    }
#endif
    if (objClass == CKO_CERTIFICATE) {
        io = nss_ckmk_CreateCertificate(fwSession, pTemplate,
                                        ulAttributeCount, pError);
    } else if (objClass == CKO_PRIVATE_KEY) {
        io = nss_ckmk_CreatePrivateKey(fwSession, pTemplate,
                                       ulAttributeCount, pError);
    } else {
        *pError = CKR_ATTRIBUTE_VALUE_INVALID;
    }

    if ((ckmkInternalObject *)NULL == io) {
        return (NSSCKMDObject *)NULL;
    }
    return nss_ckmk_CreateMDObject(NULL, io, pError);
}
