
/* 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/. */
/*
 * This file PKCS #12 fuctions that should really be moved to the
 * PKCS #12 directory, however we can't do that in a point release
 * because that will break binary compatibility, so we keep them here for now.
 */

#include "seccomon.h"
#include "secmod.h"
#include "secmodi.h"
#include "secmodti.h"
#include "secmodt.h"
#include "pkcs11.h"
#include "pk11func.h"
#include "secitem.h"
#include "keyhi.h"
#include "secoid.h"
#include "secasn1.h"
#include "secerr.h"
#include "prerror.h"

/* These data structures should move to a common .h file shared between the
 * wrappers and the pkcs 12 code. */

/*
** RSA Raw Private Key structures
*/

/* member names from PKCS#1, section 7.2 */
struct SECKEYRSAPrivateKeyStr {
    PLArenaPool *arena;
    SECItem version;
    SECItem modulus;
    SECItem publicExponent;
    SECItem privateExponent;
    SECItem prime1;
    SECItem prime2;
    SECItem exponent1;
    SECItem exponent2;
    SECItem coefficient;
};
typedef struct SECKEYRSAPrivateKeyStr SECKEYRSAPrivateKey;

/*
** DSA Raw Private Key structures
*/

struct SECKEYDSAPrivateKeyStr {
    SECKEYPQGParams params;
    SECItem privateValue;
};
typedef struct SECKEYDSAPrivateKeyStr SECKEYDSAPrivateKey;

/*
** Diffie-Hellman Raw Private Key structures
** Structure member names suggested by PKCS#3.
*/
struct SECKEYDHPrivateKeyStr {
    PLArenaPool *arena;
    SECItem prime;
    SECItem base;
    SECItem privateValue;
};
typedef struct SECKEYDHPrivateKeyStr SECKEYDHPrivateKey;

/*
** Elliptic Curve Private Key structures
** <https://tools.ietf.org/html/rfc5915#section-3>
*/
struct SECKEYECPrivateKeyStr {
    PLArenaPool *arena;
    SECItem version;
    SECItem curveOID;    /* optional/ignored */
    SECItem publicValue; /* required (for now) */
    SECItem privateValue;
};
typedef struct SECKEYECPrivateKeyStr SECKEYECPrivateKey;

/*
** raw private key object
*/
struct SECKEYRawPrivateKeyStr {
    PLArenaPool *arena;
    KeyType keyType;
    union {
        SECKEYRSAPrivateKey rsa;
        SECKEYDSAPrivateKey dsa;
        SECKEYDHPrivateKey dh;
        SECKEYECPrivateKey ec;
    } u;
};
typedef struct SECKEYRawPrivateKeyStr SECKEYRawPrivateKey;

SEC_ASN1_MKSUB(SEC_AnyTemplate)
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)

/* ASN1 Templates for new decoder/encoder */
/*
 * Attribute value for PKCS8 entries (static?)
 */
const SEC_ASN1Template SECKEY_AttributeTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(SECKEYAttribute) },
    { SEC_ASN1_OBJECT_ID, offsetof(SECKEYAttribute, attrType) },
    { SEC_ASN1_SET_OF | SEC_ASN1_XTRN, offsetof(SECKEYAttribute, attrValue),
      SEC_ASN1_SUB(SEC_AnyTemplate) },
    { 0 }
};

const SEC_ASN1Template SECKEY_SetOfAttributeTemplate[] = {
    { SEC_ASN1_SET_OF, 0, SECKEY_AttributeTemplate },
};

const SEC_ASN1Template SECKEY_PrivateKeyInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYPrivateKeyInfo) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYPrivateKeyInfo, version) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(SECKEYPrivateKeyInfo, algorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_OCTET_STRING, offsetof(SECKEYPrivateKeyInfo, privateKey) },
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED | SEC_ASN1_CONTEXT_SPECIFIC | 0,
      offsetof(SECKEYPrivateKeyInfo, attributes),
      SECKEY_SetOfAttributeTemplate },
    { 0 }
};

const SEC_ASN1Template SECKEY_PointerToPrivateKeyInfoTemplate[] = {
    { SEC_ASN1_POINTER, 0, SECKEY_PrivateKeyInfoTemplate }
};

const SEC_ASN1Template SECKEY_RSAPrivateKeyExportTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYRawPrivateKey) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.version) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.modulus) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.publicExponent) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.privateExponent) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.prime1) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.prime2) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.exponent1) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.exponent2) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.rsa.coefficient) },
    { 0 }
};

const SEC_ASN1Template SECKEY_DSAPrivateKeyExportTemplate[] = {
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.dsa.privateValue) },
};

const SEC_ASN1Template SECKEY_DHPrivateKeyExportTemplate[] = {
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.dh.privateValue) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.dh.base) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.dh.prime) },
};

SEC_ASN1_MKSUB(SEC_BitStringTemplate)
SEC_ASN1_MKSUB(SEC_ObjectIDTemplate)

const SEC_ASN1Template SECKEY_ECPrivateKeyExportTemplate[] = {
    { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECKEYRawPrivateKey) },
    { SEC_ASN1_INTEGER, offsetof(SECKEYRawPrivateKey, u.ec.version) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(SECKEYRawPrivateKey, u.ec.privateValue) },
    /* This value will always be ignored. u.ec.curveOID will always be
     * overriden with the outer AlgorithmID.parameters. */
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
          SEC_ASN1_EXPLICIT | SEC_ASN1_CONTEXT_SPECIFIC |
          SEC_ASN1_XTRN | 0,
      offsetof(SECKEYRawPrivateKey, u.ec.curveOID),
      SEC_ASN1_SUB(SEC_ObjectIDTemplate) },
    /* The public value is optional per RFC, but required in NSS. We
     * can't do scalar mult on ECs to get a raw point with PK11 APIs. */
    { SEC_ASN1_OPTIONAL | SEC_ASN1_CONSTRUCTED |
          SEC_ASN1_EXPLICIT | SEC_ASN1_CONTEXT_SPECIFIC |
          SEC_ASN1_XTRN | 1,
      offsetof(SECKEYRawPrivateKey, u.ec.publicValue),
      SEC_ASN1_SUB(SEC_BitStringTemplate) },
    { 0 }
};

/* The template operates a private key consisting only of private key. */
const SEC_ASN1Template SECKEY_EDPrivateKeyExportTemplate[] = {
    { SEC_ASN1_OCTET_STRING,
      offsetof(SECKEYRawPrivateKey, u.ec.privateValue) },
    { 0 }
};

const SEC_ASN1Template SECKEY_EncryptedPrivateKeyInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(SECKEYEncryptedPrivateKeyInfo) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(SECKEYEncryptedPrivateKeyInfo, algorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(SECKEYEncryptedPrivateKeyInfo, encryptedData) },
    { 0 }
};

const SEC_ASN1Template SECKEY_PointerToEncryptedPrivateKeyInfoTemplate[] = {
    { SEC_ASN1_POINTER, 0, SECKEY_EncryptedPrivateKeyInfoTemplate }
};

SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_EncryptedPrivateKeyInfoTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_PointerToEncryptedPrivateKeyInfoTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_PrivateKeyInfoTemplate)
SEC_ASN1_CHOOSER_IMPLEMENT(SECKEY_PointerToPrivateKeyInfoTemplate)

/*
 * See bugzilla bug 125359
 * Since NSS (via PKCS#11) wants to handle big integers as unsigned ints,
 * all of the templates above that en/decode into integers must be converted
 * from ASN.1's signed integer type.  This is done by marking either the
 * source or destination (encoding or decoding, respectively) type as
 * siUnsignedInteger.
 */

static void
prepare_rsa_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
{
    key->u.rsa.modulus.type = siUnsignedInteger;
    key->u.rsa.publicExponent.type = siUnsignedInteger;
    key->u.rsa.privateExponent.type = siUnsignedInteger;
    key->u.rsa.prime1.type = siUnsignedInteger;
    key->u.rsa.prime2.type = siUnsignedInteger;
    key->u.rsa.exponent1.type = siUnsignedInteger;
    key->u.rsa.exponent2.type = siUnsignedInteger;
    key->u.rsa.coefficient.type = siUnsignedInteger;
}

static void
prepare_dsa_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
{
    key->u.dsa.privateValue.type = siUnsignedInteger;
    key->u.dsa.params.prime.type = siUnsignedInteger;
    key->u.dsa.params.subPrime.type = siUnsignedInteger;
    key->u.dsa.params.base.type = siUnsignedInteger;
}

static void
prepare_dh_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
{
    key->u.dh.privateValue.type = siUnsignedInteger;
    key->u.dh.prime.type = siUnsignedInteger;
    key->u.dh.base.type = siUnsignedInteger;
}

static void
prepare_ec_priv_key_export_for_asn1(SECKEYRawPrivateKey *key)
{
    key->u.ec.version.type = siUnsignedInteger;
    key->u.ec.curveOID.type = siUnsignedInteger;
    key->u.ec.privateValue.type = siUnsignedInteger;
    key->u.ec.publicValue.type = siUnsignedInteger;
}

SECStatus
PK11_ImportDERPrivateKeyInfo(PK11SlotInfo *slot, SECItem *derPKI,
                             SECItem *nickname, SECItem *publicValue, PRBool isPerm,
                             PRBool isPrivate, unsigned int keyUsage, void *wincx)
{
    return PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, derPKI,
                                                    nickname, publicValue,
                                                    isPerm, isPrivate, keyUsage,
                                                    NULL, wincx);
}

SECStatus
PK11_ImportDERPrivateKeyInfoAndReturnKey(PK11SlotInfo *slot, SECItem *derPKI,
                                         SECItem *nickname, SECItem *publicValue,
                                         PRBool isPerm, PRBool isPrivate, unsigned int keyUsage,
                                         SECKEYPrivateKey **privk, void *wincx)
{
    SECKEYPrivateKeyInfo *pki = NULL;
    PLArenaPool *temparena = NULL;
    SECStatus rv = SECFailure;

    temparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!temparena) {
        return rv;
    }

    pki = PORT_ArenaZNew(temparena, SECKEYPrivateKeyInfo);
    if (!pki) {
        PORT_FreeArena(temparena, PR_FALSE);
        return rv;
    }
    pki->arena = temparena;

    rv = SEC_ASN1DecodeItem(pki->arena, pki, SECKEY_PrivateKeyInfoTemplate,
                            derPKI);
    if (rv != SECSuccess) {
        /* If SEC_ASN1DecodeItem fails, we cannot assume anything about the
         * validity of the data in pki. The best we can do is free the arena
         * and return. */
        PORT_FreeArena(temparena, PR_TRUE);
        return rv;
    }
    if (pki->privateKey.data == NULL) {
        /* If SEC_ASN1DecodeItems succeeds but SECKEYPrivateKeyInfo.privateKey
         * is a zero-length octet string, free the arena and return a failure
         * to avoid trying to zero the corresponding SECItem in
         * SECKEY_DestroyPrivateKeyInfo(). */
        PORT_FreeArena(temparena, PR_TRUE);
        PORT_SetError(SEC_ERROR_BAD_KEY);
        return SECFailure;
    }

    rv = PK11_ImportPrivateKeyInfoAndReturnKey(slot, pki, nickname,
                                               publicValue, isPerm, isPrivate,
                                               keyUsage, privk, wincx);

    /* this zeroes the key and frees the arena */
    SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE /*freeit*/);
    return rv;
}

SECStatus
PK11_ImportAndReturnPrivateKey(PK11SlotInfo *slot, SECKEYRawPrivateKey *lpk,
                               SECItem *nickname, SECItem *publicValue, PRBool isPerm,
                               PRBool isPrivate, unsigned int keyUsage, SECKEYPrivateKey **privk,
                               void *wincx)
{
    CK_BBOOL cktrue = CK_TRUE;
    CK_BBOOL ckfalse = CK_FALSE;
    CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
    CK_KEY_TYPE keyType = CKK_RSA;
    CK_OBJECT_HANDLE objectID;
    CK_ATTRIBUTE theTemplate[20];
    int templateCount = 0;
    SECStatus rv = SECFailure;
    CK_ATTRIBUTE *attrs;
    CK_ATTRIBUTE *signedattr = NULL;
    int signedcount = 0;
    CK_ATTRIBUTE *ap;
    SECItem *ck_id = NULL;

    attrs = theTemplate;

    PK11_SETATTRS(attrs, CKA_CLASS, &keyClass, sizeof(keyClass));
    attrs++;
    PK11_SETATTRS(attrs, CKA_KEY_TYPE, &keyType, sizeof(keyType));
    attrs++;
    PK11_SETATTRS(attrs, CKA_TOKEN, isPerm ? &cktrue : &ckfalse,
                  sizeof(CK_BBOOL));
    attrs++;
    PK11_SETATTRS(attrs, CKA_SENSITIVE, isPrivate ? &cktrue : &ckfalse,
                  sizeof(CK_BBOOL));
    attrs++;
    PK11_SETATTRS(attrs, CKA_PRIVATE, isPrivate ? &cktrue : &ckfalse,
                  sizeof(CK_BBOOL));
    attrs++;

    switch (lpk->keyType) {
        case rsaKey:
            keyType = CKK_RSA;
            PK11_SETATTRS(attrs, CKA_UNWRAP, (keyUsage & KU_KEY_ENCIPHERMENT) ? &cktrue : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            PK11_SETATTRS(attrs, CKA_DECRYPT, (keyUsage & KU_DATA_ENCIPHERMENT) ? &cktrue : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            PK11_SETATTRS(attrs, CKA_SIGN, (keyUsage & KU_DIGITAL_SIGNATURE) ? &cktrue : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            PK11_SETATTRS(attrs, CKA_SIGN_RECOVER,
                          (keyUsage & KU_DIGITAL_SIGNATURE) ? &cktrue
                                                            : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            ck_id = PK11_MakeIDFromPubKey(&lpk->u.rsa.modulus);
            if (ck_id == NULL) {
                goto loser;
            }
            PK11_SETATTRS(attrs, CKA_ID, ck_id->data, ck_id->len);
            attrs++;
            if (nickname) {
                PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
                attrs++;
            }
            signedattr = attrs;
            PK11_SETATTRS(attrs, CKA_MODULUS, lpk->u.rsa.modulus.data,
                          lpk->u.rsa.modulus.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_PUBLIC_EXPONENT,
                          lpk->u.rsa.publicExponent.data,
                          lpk->u.rsa.publicExponent.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_PRIVATE_EXPONENT,
                          lpk->u.rsa.privateExponent.data,
                          lpk->u.rsa.privateExponent.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_PRIME_1,
                          lpk->u.rsa.prime1.data,
                          lpk->u.rsa.prime1.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_PRIME_2,
                          lpk->u.rsa.prime2.data,
                          lpk->u.rsa.prime2.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_EXPONENT_1,
                          lpk->u.rsa.exponent1.data,
                          lpk->u.rsa.exponent1.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_EXPONENT_2,
                          lpk->u.rsa.exponent2.data,
                          lpk->u.rsa.exponent2.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_COEFFICIENT,
                          lpk->u.rsa.coefficient.data,
                          lpk->u.rsa.coefficient.len);
            attrs++;
            break;
        case dsaKey:
            keyType = CKK_DSA;
            /* To make our intenal PKCS #11 module work correctly with
             * our database, we need to pass in the public key value for
             * this dsa key. We have a netscape only CKA_ value to do this.
             * Only send it to internal slots */
            if (publicValue == NULL) {
                goto loser;
            }
            if (PK11_IsInternal(slot)) {
                PK11_SETATTRS(attrs, CKA_NSS_DB,
                              publicValue->data, publicValue->len);
                attrs++;
            }
            PK11_SETATTRS(attrs, CKA_SIGN, &cktrue, sizeof(CK_BBOOL));
            attrs++;
            PK11_SETATTRS(attrs, CKA_SIGN_RECOVER, &cktrue, sizeof(CK_BBOOL));
            attrs++;
            if (nickname) {
                PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
                attrs++;
            }
            ck_id = PK11_MakeIDFromPubKey(publicValue);
            if (ck_id == NULL) {
                goto loser;
            }
            PK11_SETATTRS(attrs, CKA_ID, ck_id->data, ck_id->len);
            attrs++;
            signedattr = attrs;
            PK11_SETATTRS(attrs, CKA_PRIME, lpk->u.dsa.params.prime.data,
                          lpk->u.dsa.params.prime.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_SUBPRIME, lpk->u.dsa.params.subPrime.data,
                          lpk->u.dsa.params.subPrime.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_BASE, lpk->u.dsa.params.base.data,
                          lpk->u.dsa.params.base.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_VALUE, lpk->u.dsa.privateValue.data,
                          lpk->u.dsa.privateValue.len);
            attrs++;
            break;
        case dhKey:
            keyType = CKK_DH;
            /* To make our intenal PKCS #11 module work correctly with
             * our database, we need to pass in the public key value for
             * this dh key. We have a netscape only CKA_ value to do this.
             * Only send it to internal slots */
            if (PK11_IsInternal(slot)) {
                PK11_SETATTRS(attrs, CKA_NSS_DB,
                              publicValue->data, publicValue->len);
                attrs++;
            }
            PK11_SETATTRS(attrs, CKA_DERIVE, &cktrue, sizeof(CK_BBOOL));
            attrs++;
            if (nickname) {
                PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
                attrs++;
            }
            ck_id = PK11_MakeIDFromPubKey(publicValue);
            if (ck_id == NULL) {
                goto loser;
            }
            PK11_SETATTRS(attrs, CKA_ID, ck_id->data, ck_id->len);
            attrs++;
            signedattr = attrs;
            PK11_SETATTRS(attrs, CKA_PRIME, lpk->u.dh.prime.data,
                          lpk->u.dh.prime.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_BASE, lpk->u.dh.base.data,
                          lpk->u.dh.base.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_VALUE, lpk->u.dh.privateValue.data,
                          lpk->u.dh.privateValue.len);
            attrs++;
            break;
        case ecKey:
            keyType = CKK_EC;
            if (lpk->u.ec.publicValue.len == 0) {
                goto loser;
            }
            if (PK11_IsInternal(slot)) {
                PK11_SETATTRS(attrs, CKA_NSS_DB,
                              lpk->u.ec.publicValue.data,
                              lpk->u.ec.publicValue.len);
                attrs++;
            }
            PK11_SETATTRS(attrs, CKA_SIGN, (keyUsage & KU_DIGITAL_SIGNATURE) ? &cktrue : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            PK11_SETATTRS(attrs, CKA_SIGN_RECOVER,
                          (keyUsage & KU_DIGITAL_SIGNATURE) ? &cktrue
                                                            : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            PK11_SETATTRS(attrs, CKA_DERIVE, (keyUsage & KU_KEY_AGREEMENT) ? &cktrue : &ckfalse,
                          sizeof(CK_BBOOL));
            attrs++;
            if (nickname) {
                PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
                attrs++;
            }
            ck_id = PK11_MakeIDFromPubKey(&lpk->u.ec.publicValue);
            if (ck_id == NULL) {
                goto loser;
            }
            PK11_SETATTRS(attrs, CKA_ID, ck_id->data, ck_id->len);
            attrs++;
            /* No signed attrs for EC */
            /* curveOID always is a copy of AlgorithmID.parameters. */
            PK11_SETATTRS(attrs, CKA_EC_PARAMS, lpk->u.ec.curveOID.data,
                          lpk->u.ec.curveOID.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_VALUE, lpk->u.ec.privateValue.data,
                          lpk->u.ec.privateValue.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_EC_POINT, lpk->u.ec.publicValue.data,
                          lpk->u.ec.publicValue.len);
            attrs++;
            break;
        case edKey:
            keyType = CKK_EC_EDWARDS;
            PK11_SETATTRS(attrs, CKA_SIGN, &cktrue, sizeof(CK_BBOOL));
            attrs++;
            if (nickname) {
                PK11_SETATTRS(attrs, CKA_LABEL, nickname->data, nickname->len);
                attrs++;
            }

            /* No signed attrs for EC */
            /* curveOID always is a copy of AlgorithmID.parameters. */
            PK11_SETATTRS(attrs, CKA_EC_PARAMS, lpk->u.ec.curveOID.data,
                          lpk->u.ec.curveOID.len);
            attrs++;
            PK11_SETATTRS(attrs, CKA_VALUE, lpk->u.ec.privateValue.data,
                          lpk->u.ec.privateValue.len);
            attrs++;
            break;
        default:
            PORT_SetError(SEC_ERROR_BAD_KEY);
            goto loser;
    }
    templateCount = attrs - theTemplate;
    PORT_Assert(templateCount <= sizeof(theTemplate) / sizeof(CK_ATTRIBUTE));
    if (lpk->keyType != ecKey && lpk->keyType != edKey) {
        PORT_Assert(signedattr);
        signedcount = attrs - signedattr;
        for (ap = signedattr; signedcount; ap++, signedcount--) {
            pk11_SignedToUnsigned(ap);
        }
    }

    rv = PK11_CreateNewObject(slot, CK_INVALID_HANDLE,
                              theTemplate, templateCount, isPerm, &objectID);

    /* create and return a SECKEYPrivateKey */
    if (rv == SECSuccess && privk != NULL) {
        *privk = PK11_MakePrivKey(slot, lpk->keyType, !isPerm, objectID, wincx);
        if (*privk == NULL) {
            rv = SECFailure;
        }
    }
loser:
    if (ck_id) {
        SECITEM_ZfreeItem(ck_id, PR_TRUE);
    }
    return rv;
}

SECStatus
PK11_ImportPrivateKeyInfoAndReturnKey(PK11SlotInfo *slot,
                                      SECKEYPrivateKeyInfo *pki, SECItem *nickname, SECItem *publicValue,
                                      PRBool isPerm, PRBool isPrivate, unsigned int keyUsage,
                                      SECKEYPrivateKey **privk, void *wincx)
{
    SECStatus rv = SECFailure;
    SECKEYRawPrivateKey *lpk = NULL;
    const SEC_ASN1Template *keyTemplate, *paramTemplate;
    void *paramDest = NULL;
    PLArenaPool *arena = NULL;

    arena = PORT_NewArena(2048);
    if (!arena) {
        return SECFailure;
    }

    /* need to change this to use RSA/DSA keys */
    lpk = (SECKEYRawPrivateKey *)PORT_ArenaZAlloc(arena,
                                                  sizeof(SECKEYRawPrivateKey));
    if (lpk == NULL) {
        goto loser;
    }
    lpk->arena = arena;

    switch (SECOID_GetAlgorithmTag(&pki->algorithm)) {
        case SEC_OID_PKCS1_RSA_ENCRYPTION:
            prepare_rsa_priv_key_export_for_asn1(lpk);
            keyTemplate = SECKEY_RSAPrivateKeyExportTemplate;
            paramTemplate = NULL;
            paramDest = NULL;
            lpk->keyType = rsaKey;
            break;
        case SEC_OID_ANSIX9_DSA_SIGNATURE:
            prepare_dsa_priv_key_export_for_asn1(lpk);
            keyTemplate = SECKEY_DSAPrivateKeyExportTemplate;
            paramTemplate = SECKEY_PQGParamsTemplate;
            paramDest = &(lpk->u.dsa.params);
            lpk->keyType = dsaKey;
            break;
        case SEC_OID_X942_DIFFIE_HELMAN_KEY:
            if (!publicValue) {
                goto loser;
            }
            prepare_dh_priv_key_export_for_asn1(lpk);
            keyTemplate = SECKEY_DHPrivateKeyExportTemplate;
            paramTemplate = NULL;
            paramDest = NULL;
            lpk->keyType = dhKey;
            break;
        case SEC_OID_ED25519_PUBLIC_KEY:
            keyTemplate = SECKEY_EDPrivateKeyExportTemplate;
            paramTemplate = NULL;
            paramDest = NULL;
            lpk->keyType = edKey;
            break;
        case SEC_OID_ANSIX962_EC_PUBLIC_KEY:
            prepare_ec_priv_key_export_for_asn1(lpk);
            keyTemplate = SECKEY_ECPrivateKeyExportTemplate;
            paramTemplate = NULL;
            paramDest = NULL;
            lpk->keyType = ecKey;
            break;

        default:
            keyTemplate = NULL;
            paramTemplate = NULL;
            paramDest = NULL;
            break;
    }

    if (!keyTemplate) {
        goto loser;
    }

    /* decode the private key and any algorithm parameters */
    rv = SEC_QuickDERDecodeItem(arena, lpk, keyTemplate, &pki->privateKey);
    if (rv != SECSuccess) {
        goto loser;
    }

    if (lpk->keyType == ecKey) {
        /* Convert length in bits to length in bytes. */
        lpk->u.ec.publicValue.len >>= 3;

        /* Always override curveOID, we're ignoring any given value. */
        rv = SECITEM_CopyItem(arena, &lpk->u.ec.curveOID,
                              &pki->algorithm.parameters);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    if (lpk->keyType == edKey) {
        /* Convert length in bits to length in bytes. */
        lpk->u.ec.publicValue.len >>= 3;

        if (pki->algorithm.parameters.len != 0) {
            /* Currently supporting only (Pure)Ed25519 .*/
            PORT_SetError(SEC_ERROR_UNSUPPORTED_KEYALG);
            goto loser;
        }

        SECOidData *oidEd25519 = SECOID_FindOIDByTag(SEC_OID_ED25519_PUBLIC_KEY);

        if (!SECITEM_AllocItem(arena, &lpk->u.ec.curveOID, oidEd25519->oid.len + 2)) {
            goto loser;
        }
        lpk->u.ec.curveOID.data[0] = SEC_ASN1_OBJECT_ID;
        lpk->u.ec.curveOID.data[1] = oidEd25519->oid.len;
        PORT_Memcpy(lpk->u.ec.curveOID.data + 2, oidEd25519->oid.data, oidEd25519->oid.len);
    }

    if (paramDest && paramTemplate) {
        rv = SEC_ASN1DecodeItem(arena, paramDest, paramTemplate,
                                &(pki->algorithm.parameters));
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    rv = PK11_ImportAndReturnPrivateKey(slot, lpk, nickname, publicValue, isPerm,
                                        isPrivate, keyUsage, privk, wincx);
loser:
    if (arena != NULL) {
        PORT_FreeArena(arena, PR_TRUE);
    }

    return rv;
}

SECStatus
PK11_ImportPrivateKeyInfo(PK11SlotInfo *slot, SECKEYPrivateKeyInfo *pki,
                          SECItem *nickname, SECItem *publicValue, PRBool isPerm,
                          PRBool isPrivate, unsigned int keyUsage, void *wincx)
{
    return PK11_ImportPrivateKeyInfoAndReturnKey(slot, pki, nickname,
                                                 publicValue, isPerm, isPrivate, keyUsage, NULL, wincx);
}

SECItem *
PK11_ExportDERPrivateKeyInfo(SECKEYPrivateKey *pk, void *wincx)
{
    SECKEYPrivateKeyInfo *pki = PK11_ExportPrivKeyInfo(pk, wincx);
    SECItem *derPKI;

    if (!pki) {
        return NULL;
    }
    derPKI = SEC_ASN1EncodeItem(NULL, NULL, pki,
                                SECKEY_PrivateKeyInfoTemplate);
    SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE);
    return derPKI;
}

static PRBool
ReadAttribute(SECKEYPrivateKey *key, CK_ATTRIBUTE_TYPE type,
              PLArenaPool *arena, SECItem *output)
{
    SECStatus rv = PK11_ReadAttribute(key->pkcs11Slot, key->pkcs11ID, type,
                                      arena, output);
    return rv == SECSuccess;
}

/*
 * The caller is responsible for freeing the return value by passing it to
 * SECKEY_DestroyPrivateKeyInfo(..., PR_TRUE).
 */
SECKEYPrivateKeyInfo *
PK11_ExportPrivKeyInfo(SECKEYPrivateKey *pk, void *wincx)
{
    /* PrivateKeyInfo version (always zero) */
    const unsigned char pkiVersion = 0;
    /* RSAPrivateKey version (always zero) */
    const unsigned char rsaVersion = 0;
    /* ECPrivateKey version (always one) */
    const unsigned char ecVersion = 1;
    PLArenaPool *arena = NULL;
    SECKEYRawPrivateKey rawKey;
    SECKEYPrivateKeyInfo *pki;
    SECItem *encoded;
    const SEC_ASN1Template *keyTemplate;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) {
        goto loser;
    }
    memset(&rawKey, 0, sizeof(rawKey));
    rawKey.keyType = pk->keyType;
    pki = PORT_ArenaZNew(arena, SECKEYPrivateKeyInfo);
    if (!pki) {
        goto loser;
    }

    switch (pk->keyType) {
        case rsaKey: {
            rawKey.u.rsa.version.type = siUnsignedInteger;
            rawKey.u.rsa.version.data = (unsigned char *)PORT_ArenaAlloc(arena, 1);
            if (!rawKey.u.rsa.version.data) {
                goto loser;
            }

            rawKey.u.rsa.version.data[0] = rsaVersion;
            rawKey.u.rsa.version.len = 1;

            /* Read the component attributes of the private key */
            prepare_rsa_priv_key_export_for_asn1(&rawKey);
            if (!ReadAttribute(pk, CKA_MODULUS, arena, &rawKey.u.rsa.modulus) ||
                !ReadAttribute(pk, CKA_PUBLIC_EXPONENT, arena,
                               &rawKey.u.rsa.publicExponent) ||
                !ReadAttribute(pk, CKA_PRIVATE_EXPONENT, arena,
                               &rawKey.u.rsa.privateExponent) ||
                !ReadAttribute(pk, CKA_PRIME_1, arena, &rawKey.u.rsa.prime1) ||
                !ReadAttribute(pk, CKA_PRIME_2, arena, &rawKey.u.rsa.prime2) ||
                !ReadAttribute(pk, CKA_EXPONENT_1, arena,
                               &rawKey.u.rsa.exponent1) ||
                !ReadAttribute(pk, CKA_EXPONENT_2, arena,
                               &rawKey.u.rsa.exponent2) ||
                !ReadAttribute(pk, CKA_COEFFICIENT, arena,
                               &rawKey.u.rsa.coefficient)) {
                goto loser;
            }

            keyTemplate = SECKEY_RSAPrivateKeyExportTemplate;

            rv = SECOID_SetAlgorithmID(arena, &pki->algorithm, SEC_OID_PKCS1_RSA_ENCRYPTION, NULL);
            if (rv != SECSuccess) {
                goto loser;
            }

        } break;
        case ecKey: {
            rawKey.u.ec.version.type = siUnsignedInteger;
            rawKey.u.ec.version.data = (unsigned char *)PORT_ArenaAlloc(arena, 1);
            if (!rawKey.u.ec.version.data) {
                goto loser;
            }
            rawKey.u.ec.version.data[0] = ecVersion;
            rawKey.u.ec.version.len = 1;

            SECItem curveOID;
            /* Read the component attributes of the private key */
            prepare_ec_priv_key_export_for_asn1(&rawKey);
            if (!ReadAttribute(pk, CKA_VALUE, arena,
                               &rawKey.u.ec.privateValue) ||
                !ReadAttribute(pk, CKA_EC_PARAMS, arena, &curveOID)) {
                goto loser;
            }
            if (!ReadAttribute(pk, CKA_EC_POINT, arena,
                               &rawKey.u.ec.publicValue)) {
                SECKEYPublicKey *pubk = SECKEY_ConvertToPublicKey(pk);
                if (pubk == NULL)
                    goto loser;
                rv = SECITEM_CopyItem(arena, &rawKey.u.ec.publicValue, &pubk->u.ec.publicValue);
                SECKEY_DestroyPublicKey(pubk);
                if (rv != SECSuccess) {
                    goto loser;
                }
            }

            keyTemplate = SECKEY_ECPrivateKeyExportTemplate;
            /* Convert length in bytes to length in bits. */
            rawKey.u.ec.publicValue.len <<= 3;

            rv = SECOID_SetAlgorithmID(arena, &pki->algorithm, SEC_OID_ANSIX962_EC_PUBLIC_KEY, &curveOID);
            if (rv != SECSuccess) {
                goto loser;
            }

        } break;
        case edKey: {
            rawKey.u.ec.version.type = siUnsignedInteger;
            rawKey.u.ec.version.data = (unsigned char *)PORT_ArenaAlloc(arena, 1);
            if (!rawKey.u.ec.version.data) {
                goto loser;
            }
            rawKey.u.ec.version.data[0] = ecVersion;
            rawKey.u.ec.version.len = 1;

            if (!ReadAttribute(pk, CKA_VALUE, arena,
                               &rawKey.u.ec.privateValue)) {
                goto loser;
            }

            keyTemplate = SECKEY_EDPrivateKeyExportTemplate;
            /* Currently, ED25519 does not support any parameter.  */
            rv = SECOID_SetAlgorithmID(arena, &pki->algorithm, SEC_OID_ED25519_PUBLIC_KEY, NULL);
            if (rv != SECSuccess) {
                goto loser;
            }

        } break;
        default: {
            PORT_SetError(PR_NOT_IMPLEMENTED_ERROR);
            goto loser;
        }
    }

    encoded = SEC_ASN1EncodeItem(arena, &pki->privateKey, &rawKey, keyTemplate);
    if (!encoded) {
        goto loser;
    }
    pki->version.type = siUnsignedInteger;
    pki->version.data = (unsigned char *)PORT_ArenaAlloc(arena, 1);
    if (!pki->version.data) {
        goto loser;
    }
    pki->version.data[0] = pkiVersion;
    pki->version.len = 1;
    pki->arena = arena;

    return pki;

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }
    return NULL;
}
