/* 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 "plarena.h"

#include "blapit.h"
#include "seccomon.h"
#include "secitem.h"
#include "secport.h"
#include "hasht.h"
#include "pkcs11t.h"
#include "sechash.h"
#include "secasn1.h"
#include "secder.h"
#include "secoid.h"
#include "secerr.h"
#include "secmod.h"
#include "pk11func.h"
#include "secpkcs5.h"
#include "secmodi.h"
#include "secmodti.h"
#include "pkcs11.h"
#include "pk11func.h"
#include "secitem.h"
#include "keyhi.h"

typedef struct SEC_PKCS5PBEParameterStr SEC_PKCS5PBEParameter;
struct SEC_PKCS5PBEParameterStr {
    PLArenaPool *poolp;
    SECItem salt;              /* octet string */
    SECItem iteration;         /* integer */
    SECItem keyLength;         /* PKCS5v2 only */
    SECAlgorithmID *pPrfAlgId; /* PKCS5v2 only */
    SECAlgorithmID prfAlgId;   /* PKCS5v2 only */
};

/* PKCS5 V2 has an algorithm ID for the encryption and for
 * the key generation. This is valid for SEC_OID_PKCS5_PBES2
 * and SEC_OID_PKCS5_PBMAC1
 */
struct sec_pkcs5V2ParameterStr {
    PLArenaPool *poolp;
    SECAlgorithmID pbeAlgId;    /* real pbe algorithms */
    SECAlgorithmID cipherAlgId; /* encryption/mac */
};

typedef struct sec_pkcs5V2ParameterStr sec_pkcs5V2Parameter;

/* template for PKCS 5 PBE Parameter.  This template has been expanded
 * based upon the additions in PKCS 12.  This should eventually be moved
 * if RSA updates PKCS 5.
 */
const SEC_ASN1Template SEC_PKCS5PBEParameterTemplate[] =
    {
      { SEC_ASN1_SEQUENCE,
        0, NULL, sizeof(SEC_PKCS5PBEParameter) },
      { SEC_ASN1_OCTET_STRING,
        offsetof(SEC_PKCS5PBEParameter, salt) },
      { SEC_ASN1_INTEGER,
        offsetof(SEC_PKCS5PBEParameter, iteration) },
      { 0 }
    };

const SEC_ASN1Template SEC_V2PKCS12PBEParameterTemplate[] =
    {
      { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS5PBEParameter) },
      { SEC_ASN1_OCTET_STRING, offsetof(SEC_PKCS5PBEParameter, salt) },
      { SEC_ASN1_INTEGER, offsetof(SEC_PKCS5PBEParameter, iteration) },
      { 0 }
    };

SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)

/* SECOID_PKCS5_PBKDF2 */
const SEC_ASN1Template SEC_PKCS5V2PBEParameterTemplate[] =
    {
      { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS5PBEParameter) },
      /* This is really a choice, but since we only understand this
     * choice, just inline it */
      { SEC_ASN1_OCTET_STRING, offsetof(SEC_PKCS5PBEParameter, salt) },
      { SEC_ASN1_INTEGER, offsetof(SEC_PKCS5PBEParameter, iteration) },
      { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL,
        offsetof(SEC_PKCS5PBEParameter, keyLength) },
      { SEC_ASN1_POINTER | SEC_ASN1_XTRN | SEC_ASN1_OPTIONAL,
        offsetof(SEC_PKCS5PBEParameter, pPrfAlgId),
        SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
      { 0 }
    };

/* SEC_OID_PKCS5_PBES2, SEC_OID_PKCS5_PBMAC1 */
const SEC_ASN1Template SEC_PKCS5V2ParameterTemplate[] =
    {
      { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SEC_PKCS5PBEParameter) },
      { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(sec_pkcs5V2Parameter, pbeAlgId),
        SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
      { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
        offsetof(sec_pkcs5V2Parameter, cipherAlgId),
        SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
      { 0 }
    };

/*
 * maps a PBE algorithm to a crypto algorithm. for PKCS12 and PKCS5v1
 * for PKCS5v2 it returns SEC_OID_PKCS5_PBKDF2.
 */
SECOidTag
sec_pkcs5GetCryptoFromAlgTag(SECOidTag algorithm)
{
    switch (algorithm) {
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC:
            return SEC_OID_DES_EDE3_CBC;
        case SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC:
        case SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC:
        case SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC:
            return SEC_OID_DES_CBC;
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
            return SEC_OID_RC2_CBC;
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4:
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4:
            return SEC_OID_RC4;
        case SEC_OID_PKCS5_PBKDF2:
        case SEC_OID_PKCS5_PBES2:
        case SEC_OID_PKCS5_PBMAC1:
            return SEC_OID_PKCS5_PBKDF2;
        default:
            break;
    }

    return SEC_OID_UNKNOWN;
}

/*
 * get a new PKCS5 V2 Parameter from the algorithm id.
 *  if arena is passed in, use it, otherwise create a new arena.
 */
sec_pkcs5V2Parameter *
sec_pkcs5_v2_get_v2_param(PLArenaPool *arena, SECAlgorithmID *algid)
{
    PLArenaPool *localArena = NULL;
    sec_pkcs5V2Parameter *pbeV2_param;
    SECStatus rv;

    if (arena == NULL) {
        localArena = arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
        if (arena == NULL) {
            return NULL;
        }
    }
    pbeV2_param = PORT_ArenaZNew(arena, sec_pkcs5V2Parameter);
    if (pbeV2_param == NULL) {
        goto loser;
    }

    rv = SEC_ASN1DecodeItem(arena, pbeV2_param,
                            SEC_PKCS5V2ParameterTemplate, &algid->parameters);
    if (rv == SECFailure) {
        goto loser;
    }

    pbeV2_param->poolp = arena;
    return pbeV2_param;
loser:
    if (localArena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return NULL;
}

void
sec_pkcs5_v2_destroy_v2_param(sec_pkcs5V2Parameter *param)
{
    if (param && param->poolp) {
        PORT_FreeArena(param->poolp, PR_TRUE);
    }
}

/* maps crypto algorithm from PBE algorithm.
 */
SECOidTag
SEC_PKCS5GetCryptoAlgorithm(SECAlgorithmID *algid)
{

    SECOidTag pbeAlg;
    SECOidTag cipherAlg;

    if (algid == NULL)
        return SEC_OID_UNKNOWN;

    pbeAlg = SECOID_GetAlgorithmTag(algid);
    cipherAlg = sec_pkcs5GetCryptoFromAlgTag(pbeAlg);
    if ((cipherAlg == SEC_OID_PKCS5_PBKDF2) &&
        (pbeAlg != SEC_OID_PKCS5_PBKDF2)) {
        sec_pkcs5V2Parameter *pbeV2_param;
        cipherAlg = SEC_OID_UNKNOWN;

        pbeV2_param = sec_pkcs5_v2_get_v2_param(NULL, algid);
        if (pbeV2_param != NULL) {
            cipherAlg = SECOID_GetAlgorithmTag(&pbeV2_param->cipherAlgId);
            sec_pkcs5_v2_destroy_v2_param(pbeV2_param);
        }
    }

    return cipherAlg;
}

/* check to see if an oid is a pbe algorithm
 */
PRBool
SEC_PKCS5IsAlgorithmPBEAlg(SECAlgorithmID *algid)
{
    return (PRBool)(SEC_PKCS5GetCryptoAlgorithm(algid) != SEC_OID_UNKNOWN);
}

PRBool
SEC_PKCS5IsAlgorithmPBEAlgTag(SECOidTag algtag)
{
    return (PRBool)(sec_pkcs5GetCryptoFromAlgTag(algtag) != SEC_OID_UNKNOWN);
}

/*
 * find the most appropriate PKCS5v2 overall oid tag from a regular
 * cipher/hash algorithm tag.
 */
static SECOidTag
sec_pkcs5v2_get_pbe(SECOidTag algTag)
{
    /* if it's a valid hash oid... */
    if (HASH_GetHashOidTagByHMACOidTag(algTag) != SEC_OID_UNKNOWN) {
        /* use the MAC tag */
        return SEC_OID_PKCS5_PBMAC1;
    }
    if (HASH_GetHashTypeByOidTag(algTag) != HASH_AlgNULL) {
        /* eliminate Hash algorithms */
        return SEC_OID_UNKNOWN;
    }
    if (PK11_AlgtagToMechanism(algTag) != CKM_INVALID_MECHANISM) {
        /* it's not a hash, if it has a PKCS #11 mechanism associated
         * with it, assume it's a cipher. (NOTE this will generate
         * some false positives). */
        return SEC_OID_PKCS5_PBES2;
    }
    return SEC_OID_UNKNOWN;
}

/*
 * maps PBE algorithm from crypto algorithm, assumes SHA1 hashing.
 *  input keyLen in bits.
 */
SECOidTag
SEC_PKCS5GetPBEAlgorithm(SECOidTag algTag, int keyLen)
{
    switch (algTag) {
        case SEC_OID_DES_EDE3_CBC:
            switch (keyLen) {
                case 168:
                case 192:
                case 0:
                    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC;
                case 128:
                case 92:
                    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC;
                default:
                    break;
            }
            break;
        case SEC_OID_DES_CBC:
            return SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC;
        case SEC_OID_RC2_CBC:
            switch (keyLen) {
                case 40:
                    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC;
                case 128:
                case 0:
                    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC;
                default:
                    break;
            }
            break;
        case SEC_OID_RC4:
            switch (keyLen) {
                case 40:
                    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4;
                case 128:
                case 0:
                    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4;
                default:
                    break;
            }
            break;
        default:
            return sec_pkcs5v2_get_pbe(algTag);
    }

    return SEC_OID_UNKNOWN;
}

static PRBool
sec_pkcs5_is_algorithm_v2_aes_algorithm(SECOidTag algorithm)
{
    switch (algorithm) {
        case SEC_OID_AES_128_CBC:
        case SEC_OID_AES_192_CBC:
        case SEC_OID_AES_256_CBC:
            return PR_TRUE;
        default:
            return PR_FALSE;
    }
}

static int
sec_pkcs5v2_aes_key_length(SECOidTag algorithm)
{
    switch (algorithm) {
        /* The key length for the AES-CBC-Pad algorithms are
         * determined from the undelying cipher algorithm.  */
        case SEC_OID_AES_128_CBC:
            return AES_128_KEY_LENGTH;
        case SEC_OID_AES_192_CBC:
            return AES_192_KEY_LENGTH;
        case SEC_OID_AES_256_CBC:
            return AES_256_KEY_LENGTH;
        default:
            break;
    }
    return 0;
}

/*
 * get the key length in bytes from a PKCS5 PBE
 */
static int
sec_pkcs5v2_key_length(SECAlgorithmID *algid, SECAlgorithmID *cipherAlgId)
{
    SECOidTag algorithm;
    PLArenaPool *arena = NULL;
    SEC_PKCS5PBEParameter p5_param;
    SECStatus rv;
    int length = -1;
    SECOidTag cipherAlg = SEC_OID_UNKNOWN;

    algorithm = SECOID_GetAlgorithmTag(algid);
    /* sanity check, they should all be PBKDF2 here */
    if (algorithm != SEC_OID_PKCS5_PBKDF2) {
        return -1;
    }

    arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (arena == NULL) {
        goto loser;
    }
    PORT_Memset(&p5_param, 0, sizeof(p5_param));
    rv = SEC_ASN1DecodeItem(arena, &p5_param,
                            SEC_PKCS5V2PBEParameterTemplate, &algid->parameters);
    if (rv != SECSuccess) {
        goto loser;
    }

    if (cipherAlgId)
        cipherAlg = SECOID_GetAlgorithmTag(cipherAlgId);

    if (sec_pkcs5_is_algorithm_v2_aes_algorithm(cipherAlg)) {
        /* Previously, the PKCS#12 files created with the old NSS
         * releases encoded the maximum key size of AES (that is 32)
         * in the keyLength field of PBKDF2-params. That resulted in
         * always performing AES-256 even if AES-128-CBC or
         * AES-192-CBC is specified in the encryptionScheme field of
         * PBES2-params. This is wrong, but for compatibility reasons,
         * check the keyLength field and use the value if it is 32.
         */
        if (p5_param.keyLength.data != NULL) {
            length = DER_GetInteger(&p5_param.keyLength);
        }
        /* If the keyLength field is present and contains a value
         * other than 32, that means the file is created outside of
         * NSS, which we don't care about. Note that the following
         * also handles the case when the field is absent. */
        if (length != 32) {
            length = sec_pkcs5v2_aes_key_length(cipherAlg);
        }
    } else if (p5_param.keyLength.data != NULL) {
        length = DER_GetInteger(&p5_param.keyLength);
    } else {
        CK_MECHANISM_TYPE cipherMech;
        cipherMech = PK11_AlgtagToMechanism(cipherAlg);
        if (cipherMech == CKM_INVALID_MECHANISM) {
            goto loser;
        }
        length = PK11_GetMaxKeyLength(cipherMech);
    }

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return length;
}

/*
 *  get the key length in bytes needed for the PBE algorithm
 */
int
SEC_PKCS5GetKeyLength(SECAlgorithmID *algid)
{

    SECOidTag algorithm;

    if (algid == NULL)
        return SEC_OID_UNKNOWN;

    algorithm = SECOID_GetAlgorithmTag(algid);

    switch (algorithm) {
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC:
            return 24;
        case SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC:
        case SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC:
        case SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC:
            return 8;
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
            return 5;
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
        case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4:
            return 16;
        case SEC_OID_PKCS5_PBKDF2:
            return sec_pkcs5v2_key_length(algid, NULL);
        case SEC_OID_PKCS5_PBES2:
        case SEC_OID_PKCS5_PBMAC1: {
            sec_pkcs5V2Parameter *pbeV2_param;
            int length = -1;
            pbeV2_param = sec_pkcs5_v2_get_v2_param(NULL, algid);
            if (pbeV2_param != NULL) {
                length = sec_pkcs5v2_key_length(&pbeV2_param->pbeAlgId,
                                                &pbeV2_param->cipherAlgId);
                sec_pkcs5_v2_destroy_v2_param(pbeV2_param);
            }
            return length;
        }

        default:
            break;
    }
    return -1;
}

/* the PKCS12 V2 algorithms only encode the salt, there is no iteration
 * count so we need a check for V2 algorithm parameters.
 */
static PRBool
sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(SECOidTag algorithm)
{
    switch (algorithm) {
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
        case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
            return PR_TRUE;
        default:
            break;
    }

    return PR_FALSE;
}

static PRBool
sec_pkcs5_is_algorithm_v2_pkcs5_algorithm(SECOidTag algorithm)
{
    switch (algorithm) {
        case SEC_OID_PKCS5_PBES2:
        case SEC_OID_PKCS5_PBMAC1:
        case SEC_OID_PKCS5_PBKDF2:
            return PR_TRUE;
        default:
            break;
    }

    return PR_FALSE;
}

/* destroy a pbe parameter.  it assumes that the parameter was
 * generated using the appropriate create function and therefor
 * contains an arena pool.
 */
static void
sec_pkcs5_destroy_pbe_param(SEC_PKCS5PBEParameter *pbe_param)
{
    if (pbe_param != NULL)
        PORT_FreeArena(pbe_param->poolp, PR_TRUE);
}

/* creates a PBE parameter based on the PBE algorithm.  the only required
 * parameters are algorithm and interation.  the return is a PBE parameter
 * which conforms to PKCS 5 parameter unless an extended parameter is needed.
 * this is primarily if keyLength and a variable key length algorithm are
 * specified.
 *   salt -  if null, a salt will be generated from random bytes.
 *   iteration - number of iterations to perform hashing.
 *   keyLength - only used in variable key length algorithms. if specified,
 *            should be in bytes.
 * once a parameter is allocated, it should be destroyed calling
 * sec_pkcs5_destroy_pbe_parameter or SEC_PKCS5DestroyPBEParameter.
 */
#define DEFAULT_SALT_LENGTH 16
static SEC_PKCS5PBEParameter *
sec_pkcs5_create_pbe_parameter(SECOidTag algorithm,
                               SECItem *salt,
                               int iteration,
                               int keyLength,
                               SECOidTag prfAlg)
{
    PLArenaPool *poolp = NULL;
    SEC_PKCS5PBEParameter *pbe_param = NULL;
    SECStatus rv = SECSuccess;
    void *dummy = NULL;

    if (iteration < 0) {
        return NULL;
    }

    poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (poolp == NULL)
        return NULL;

    pbe_param = (SEC_PKCS5PBEParameter *)PORT_ArenaZAlloc(poolp,
                                                          sizeof(SEC_PKCS5PBEParameter));
    if (!pbe_param) {
        PORT_FreeArena(poolp, PR_TRUE);
        return NULL;
    }

    pbe_param->poolp = poolp;

    rv = SECFailure;
    if (salt && salt->data) {
        rv = SECITEM_CopyItem(poolp, &pbe_param->salt, salt);
    } else {
        /* sigh, the old interface generated salt on the fly, so we have to
         * preserve the semantics */
        pbe_param->salt.len = DEFAULT_SALT_LENGTH;
        pbe_param->salt.data = PORT_ArenaZAlloc(poolp, DEFAULT_SALT_LENGTH);
        if (pbe_param->salt.data) {
            rv = PK11_GenerateRandom(pbe_param->salt.data, DEFAULT_SALT_LENGTH);
        }
    }

    if (rv != SECSuccess) {
        PORT_FreeArena(poolp, PR_TRUE);
        return NULL;
    }

    /* encode the integer */
    dummy = SEC_ASN1EncodeInteger(poolp, &pbe_param->iteration,
                                  iteration);
    rv = (dummy) ? SECSuccess : SECFailure;

    if (rv != SECSuccess) {
        PORT_FreeArena(poolp, PR_FALSE);
        return NULL;
    }

    /*
     * for PKCS5 v2 Add the keylength and the prf
     */
    if (algorithm == SEC_OID_PKCS5_PBKDF2) {
        dummy = SEC_ASN1EncodeInteger(poolp, &pbe_param->keyLength,
                                      keyLength);
        rv = (dummy) ? SECSuccess : SECFailure;
        if (rv != SECSuccess) {
            PORT_FreeArena(poolp, PR_FALSE);
            return NULL;
        }
        rv = SECOID_SetAlgorithmID(poolp, &pbe_param->prfAlgId, prfAlg, NULL);
        if (rv != SECSuccess) {
            PORT_FreeArena(poolp, PR_FALSE);
            return NULL;
        }
        pbe_param->pPrfAlgId = &pbe_param->prfAlgId;
    }

    return pbe_param;
}

/* creates a algorithm ID containing the PBE algorithm and appropriate
 * parameters.  the required parameter is the algorithm.  if salt is
 * not specified, it is generated randomly.
 *
 * the returned SECAlgorithmID should be destroyed using
 * SECOID_DestroyAlgorithmID
 */
SECAlgorithmID *
sec_pkcs5CreateAlgorithmID(SECOidTag algorithm,
                           SECOidTag cipherAlgorithm,
                           SECOidTag prfAlg,
                           SECOidTag *pPbeAlgorithm,
                           int keyLength,
                           SECItem *salt,
                           int iteration)
{
    PLArenaPool *poolp = NULL;
    SECAlgorithmID *algid, *ret_algid = NULL;
    SECOidTag pbeAlgorithm = algorithm;
    SECItem der_param;
    void *dummy;
    SECStatus rv = SECFailure;
    SEC_PKCS5PBEParameter *pbe_param = NULL;
    sec_pkcs5V2Parameter pbeV2_param;

    if (iteration <= 0) {
        return NULL;
    }

    poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (!poolp) {
        goto loser;
    }

    if (!SEC_PKCS5IsAlgorithmPBEAlgTag(algorithm) ||
        sec_pkcs5_is_algorithm_v2_pkcs5_algorithm(algorithm)) {
        /* use PKCS 5 v2 */
        SECItem *cipherParams;

        /*
         * if we ask for pkcs5 Algorithms directly, then the
         * application needs to supply the cipher algorithm,
         * otherwise we are implicitly using pkcs5 v2 and the
         * passed in algorithm is the encryption algorithm.
         */
        if (sec_pkcs5_is_algorithm_v2_pkcs5_algorithm(algorithm)) {
            if (cipherAlgorithm == SEC_OID_UNKNOWN) {
                goto loser;
            }
        } else {
            cipherAlgorithm = algorithm;
            /* force algorithm to be chosen below */
            algorithm = SEC_OID_PKCS5_PBKDF2;
        }

        pbeAlgorithm = SEC_OID_PKCS5_PBKDF2;
        /*
         * 'algorithm' is the overall algorithm oid tag used to wrap the
         * entire algorithm ID block. For PKCS5v1 and PKCS12, this
         * algorithm OID has encoded in it both the PBE KDF function
         * and the encryption algorithm. For PKCS 5v2, PBE KDF and
         * encryption/macing oids are encoded as parameters in
         * the algorithm ID block.
         *
         * Thus in PKCS5 v1 and PKCS12, this algorithm maps to a pkcs #11
         * mechanism, where as in PKCS 5v2, this algorithm tag does not map
         * directly to a PKCS #11 mechanim, instead the 2 oids in the
         * algorithm ID block map the the actual PKCS #11 mechanism.
         * algorithm is). We use choose this algorithm oid based on the
         * cipherAlgorithm to determine what this should be (MAC1 or PBES2).
         */
        if (algorithm == SEC_OID_PKCS5_PBKDF2) {
            /* choose mac or pbes */
            algorithm = sec_pkcs5v2_get_pbe(cipherAlgorithm);
        }

        /* set the PKCS5v2 specific parameters */
        if (keyLength == 0) {
            SECOidTag hashAlg = HASH_GetHashOidTagByHMACOidTag(cipherAlgorithm);
            if (hashAlg != SEC_OID_UNKNOWN) {
                keyLength = HASH_ResultLenByOidTag(hashAlg);
            } else if (sec_pkcs5_is_algorithm_v2_aes_algorithm(cipherAlgorithm)) {
                keyLength = sec_pkcs5v2_aes_key_length(cipherAlgorithm);
            } else {
                CK_MECHANISM_TYPE cryptoMech;
                cryptoMech = PK11_AlgtagToMechanism(cipherAlgorithm);
                if (cryptoMech == CKM_INVALID_MECHANISM) {
                    goto loser;
                }
                keyLength = PK11_GetMaxKeyLength(cryptoMech);
            }
            if (keyLength == 0) {
                goto loser;
            }
        }
        /* currently SEC_OID_HMAC_SHA1 is the default */
        if (prfAlg == SEC_OID_UNKNOWN) {
            prfAlg = SEC_OID_HMAC_SHA1;
        }

        /* build the PKCS5v2 cipher algorithm id */
        cipherParams = pk11_GenerateNewParamWithKeyLen(
            PK11_AlgtagToMechanism(cipherAlgorithm), keyLength);
        if (!cipherParams) {
            goto loser;
        }

        PORT_Memset(&pbeV2_param, 0, sizeof(pbeV2_param));

        rv = PK11_ParamToAlgid(cipherAlgorithm, cipherParams,
                               poolp, &pbeV2_param.cipherAlgId);
        SECITEM_FreeItem(cipherParams, PR_TRUE);
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    /* generate the parameter */
    pbe_param = sec_pkcs5_create_pbe_parameter(pbeAlgorithm, salt, iteration,
                                               keyLength, prfAlg);
    if (!pbe_param) {
        goto loser;
    }

    /* generate the algorithm id */
    algid = (SECAlgorithmID *)PORT_ArenaZAlloc(poolp, sizeof(SECAlgorithmID));
    if (algid == NULL) {
        goto loser;
    }

    der_param.data = NULL;
    der_param.len = 0;
    if (sec_pkcs5_is_algorithm_v2_pkcs5_algorithm(algorithm)) {
        /* first encode the PBE algorithm ID */
        dummy = SEC_ASN1EncodeItem(poolp, &der_param, pbe_param,
                                   SEC_PKCS5V2PBEParameterTemplate);
        if (dummy == NULL) {
            goto loser;
        }
        rv = SECOID_SetAlgorithmID(poolp, &pbeV2_param.pbeAlgId,
                                   pbeAlgorithm, &der_param);
        if (rv != SECSuccess) {
            goto loser;
        }

        /* now encode the Full PKCS 5 parameter */
        der_param.data = NULL;
        der_param.len = 0;
        dummy = SEC_ASN1EncodeItem(poolp, &der_param, &pbeV2_param,
                                   SEC_PKCS5V2ParameterTemplate);
    } else if (!sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(algorithm)) {
        dummy = SEC_ASN1EncodeItem(poolp, &der_param, pbe_param,
                                   SEC_PKCS5PBEParameterTemplate);
    } else {
        dummy = SEC_ASN1EncodeItem(poolp, &der_param, pbe_param,
                                   SEC_V2PKCS12PBEParameterTemplate);
    }
    if (dummy == NULL) {
        goto loser;
    }

    rv = SECOID_SetAlgorithmID(poolp, algid, algorithm, &der_param);
    if (rv != SECSuccess) {
        goto loser;
    }

    ret_algid = (SECAlgorithmID *)PORT_ZAlloc(sizeof(SECAlgorithmID));
    if (ret_algid == NULL) {
        goto loser;
    }

    rv = SECOID_CopyAlgorithmID(NULL, ret_algid, algid);
    if (rv != SECSuccess) {
        SECOID_DestroyAlgorithmID(ret_algid, PR_TRUE);
        ret_algid = NULL;
    } else if (pPbeAlgorithm) {
        *pPbeAlgorithm = pbeAlgorithm;
    }

loser:
    if (poolp != NULL) {
        PORT_FreeArena(poolp, PR_TRUE);
        algid = NULL;
    }

    if (pbe_param) {
        sec_pkcs5_destroy_pbe_param(pbe_param);
    }

    return ret_algid;
}

SECStatus
pbe_PK11AlgidToParam(SECAlgorithmID *algid, SECItem *mech)
{
    SEC_PKCS5PBEParameter p5_param;
    SECItem *salt = NULL;
    SECOidTag algorithm = SECOID_GetAlgorithmTag(algid);
    PLArenaPool *arena = NULL;
    SECStatus rv = SECFailure;
    unsigned char *paramData = NULL;
    unsigned char *pSalt = NULL;
    CK_ULONG iterations;
    int paramLen = 0;
    int iv_len;

    arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (arena == NULL) {
        goto loser;
    }

    /*
     * decode the algid based on the pbe type
     */
    PORT_Memset(&p5_param, 0, sizeof(p5_param));
    if (sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(algorithm)) {
        iv_len = PK11_GetIVLength(PK11_AlgtagToMechanism(algorithm));
        rv = SEC_ASN1DecodeItem(arena, &p5_param,
                                SEC_V2PKCS12PBEParameterTemplate, &algid->parameters);
    } else if (algorithm == SEC_OID_PKCS5_PBKDF2) {
        iv_len = 0;
        rv = SEC_ASN1DecodeItem(arena, &p5_param,
                                SEC_PKCS5V2PBEParameterTemplate, &algid->parameters);
    } else {
        iv_len = PK11_GetIVLength(PK11_AlgtagToMechanism(algorithm));
        rv = SEC_ASN1DecodeItem(arena, &p5_param, SEC_PKCS5PBEParameterTemplate,
                                &algid->parameters);
    }

    if (iv_len < 0) {
        goto loser;
    }

    if (rv != SECSuccess) {
        goto loser;
    }

    /* get salt */
    salt = &p5_param.salt;
    iterations = (CK_ULONG)DER_GetInteger(&p5_param.iteration);

    /* allocate and fill in the PKCS #11 parameters
     * based on the algorithm. */
    if (algorithm == SEC_OID_PKCS5_PBKDF2) {
        SECOidTag prfAlgTag;
        CK_PKCS5_PBKD2_PARAMS *pbeV2_params =
            (CK_PKCS5_PBKD2_PARAMS *)PORT_ZAlloc(
                sizeof(CK_PKCS5_PBKD2_PARAMS) + salt->len);

        if (pbeV2_params == NULL) {
            goto loser;
        }
        paramData = (unsigned char *)pbeV2_params;
        paramLen = sizeof(CK_PKCS5_PBKD2_PARAMS);

        /* set the prf */
        prfAlgTag = SEC_OID_HMAC_SHA1;
        if (p5_param.pPrfAlgId &&
            p5_param.pPrfAlgId->algorithm.data != 0) {
            prfAlgTag = SECOID_GetAlgorithmTag(p5_param.pPrfAlgId);
        }
        switch (prfAlgTag) {
            case SEC_OID_HMAC_SHA1:
                pbeV2_params->prf = CKP_PKCS5_PBKD2_HMAC_SHA1;
                break;
            case SEC_OID_HMAC_SHA224:
                pbeV2_params->prf = CKP_PKCS5_PBKD2_HMAC_SHA224;
                break;
            case SEC_OID_HMAC_SHA256:
                pbeV2_params->prf = CKP_PKCS5_PBKD2_HMAC_SHA256;
                break;
            case SEC_OID_HMAC_SHA384:
                pbeV2_params->prf = CKP_PKCS5_PBKD2_HMAC_SHA384;
                break;
            case SEC_OID_HMAC_SHA512:
                pbeV2_params->prf = CKP_PKCS5_PBKD2_HMAC_SHA512;
                break;
            default:
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                goto loser;
        }

        /* probably should fetch these from the prfAlgid */
        pbeV2_params->pPrfData = NULL;
        pbeV2_params->ulPrfDataLen = 0;
        pbeV2_params->saltSource = CKZ_SALT_SPECIFIED;
        pSalt = ((CK_CHAR_PTR)pbeV2_params) + sizeof(CK_PKCS5_PBKD2_PARAMS);
        if (salt->data) {
            PORT_Memcpy(pSalt, salt->data, salt->len);
        }
        pbeV2_params->pSaltSourceData = pSalt;
        pbeV2_params->ulSaltSourceDataLen = salt->len;
        pbeV2_params->iterations = iterations;
    } else {
        CK_PBE_PARAMS *pbe_params = NULL;
        pbe_params = (CK_PBE_PARAMS *)PORT_ZAlloc(sizeof(CK_PBE_PARAMS) +
                                                  salt->len + iv_len);
        if (pbe_params == NULL) {
            goto loser;
        }
        paramData = (unsigned char *)pbe_params;
        paramLen = sizeof(CK_PBE_PARAMS);

        pSalt = ((CK_CHAR_PTR)pbe_params) + sizeof(CK_PBE_PARAMS);
        pbe_params->pSalt = pSalt;
        if (salt->data) {
            PORT_Memcpy(pSalt, salt->data, salt->len);
        }
        pbe_params->ulSaltLen = salt->len;
        if (iv_len) {
            pbe_params->pInitVector =
                ((CK_CHAR_PTR)pbe_params) + sizeof(CK_PBE_PARAMS) + salt->len;
        }
        pbe_params->ulIteration = iterations;
    }

    /* copy into the mechanism sec item */
    mech->data = paramData;
    mech->len = paramLen;
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }
    return SECSuccess;

loser:
    if (paramData) {
        PORT_Free(paramData);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }
    return SECFailure;
}

/*
 * public, deprecated, not valid for pkcs5 v2
 *
 * use PK11_CreatePBEV2AlgorithmID or PK11_CreatePBEAlgorithmID to create
 * PBE algorithmID's directly.
 */
SECStatus
PBE_PK11ParamToAlgid(SECOidTag algTag, SECItem *param, PLArenaPool *arena,
                     SECAlgorithmID *algId)
{
    CK_PBE_PARAMS *pbe_param;
    SECItem pbeSalt;
    SECAlgorithmID *pbeAlgID = NULL;
    SECStatus rv;

    if (!param || !algId) {
        return SECFailure;
    }

    pbe_param = (CK_PBE_PARAMS *)param->data;
    pbeSalt.data = (unsigned char *)pbe_param->pSalt;
    pbeSalt.len = pbe_param->ulSaltLen;
    pbeAlgID = sec_pkcs5CreateAlgorithmID(algTag, SEC_OID_UNKNOWN,
                                          SEC_OID_UNKNOWN, NULL, 0,
                                          &pbeSalt, (int)pbe_param->ulIteration);
    if (!pbeAlgID) {
        return SECFailure;
    }

    rv = SECOID_CopyAlgorithmID(arena, algId, pbeAlgID);
    SECOID_DestroyAlgorithmID(pbeAlgID, PR_TRUE);
    return rv;
}

/*
 * public, Deprecated, This function is only for binary compatibility with
 * older applications. Does not support PKCS5v2.
 *
 * Applications should use PK11_PBEKeyGen() for keys and PK11_GetPBEIV() for
 * iv values rather than generating PBE bits directly.
 */
PBEBitGenContext *
PBE_CreateContext(SECOidTag hashAlgorithm, PBEBitGenID bitGenPurpose,
                  SECItem *pwitem, SECItem *salt, unsigned int bitsNeeded,
                  unsigned int iterations)
{
    SECItem *context = NULL;
    SECItem mechItem;
    CK_PBE_PARAMS pbe_params;
    CK_MECHANISM_TYPE mechanism = CKM_INVALID_MECHANISM;
    PK11SlotInfo *slot;
    PK11SymKey *symKey = NULL;
    unsigned char ivData[8];

    /* use the purpose to select the low level keygen algorithm */
    switch (bitGenPurpose) {
        case pbeBitGenIntegrityKey:
            switch (hashAlgorithm) {
                case SEC_OID_SHA1:
                    mechanism = CKM_PBA_SHA1_WITH_SHA1_HMAC;
                    break;
                case SEC_OID_MD2:
                    mechanism = CKM_NSS_PBE_MD2_HMAC_KEY_GEN;
                    break;
                case SEC_OID_MD5:
                    mechanism = CKM_NSS_PBE_MD5_HMAC_KEY_GEN;
                    break;
                default:
                    break;
            }
            break;
        case pbeBitGenCipherIV:
            if (bitsNeeded > 64) {
                break;
            }
            if (hashAlgorithm != SEC_OID_SHA1) {
                break;
            }
            mechanism = CKM_PBE_SHA1_DES3_EDE_CBC;
            break;
        case pbeBitGenCipherKey:
            if (hashAlgorithm != SEC_OID_SHA1) {
                break;
            }
            switch (bitsNeeded) {
                case 40:
                    mechanism = CKM_PBE_SHA1_RC4_40;
                    break;
                case 128:
                    mechanism = CKM_PBE_SHA1_RC4_128;
                    break;
                default:
                    break;
            }
        case pbeBitGenIDNull:
            break;
    }

    if (mechanism == CKM_INVALID_MECHANISM) {
        /* we should set an error, but this is a deprecated function, and
         * we are keeping bug for bug compatibility;)... */
        return NULL;
    }

    pbe_params.pInitVector = ivData;
    pbe_params.pPassword = pwitem->data;
    pbe_params.ulPasswordLen = pwitem->len;
    pbe_params.pSalt = salt->data;
    pbe_params.ulSaltLen = salt->len;
    pbe_params.ulIteration = iterations;
    mechItem.data = (unsigned char *)&pbe_params;
    mechItem.len = sizeof(pbe_params);

    slot = PK11_GetInternalSlot();
    symKey = PK11_RawPBEKeyGen(slot, mechanism,
                               &mechItem, pwitem, PR_FALSE, NULL);
    PK11_FreeSlot(slot);
    if (symKey != NULL) {
        if (bitGenPurpose == pbeBitGenCipherIV) {
            /* NOTE: this assumes that bitsNeeded is a multiple of 8! */
            SECItem ivItem;

            ivItem.data = ivData;
            ivItem.len = bitsNeeded / 8;
            context = SECITEM_DupItem(&ivItem);
        } else {
            SECItem *keyData;
            PK11_ExtractKeyValue(symKey);
            keyData = PK11_GetKeyData(symKey);

            /* assert bitsNeeded with length? */
            if (keyData) {
                context = SECITEM_DupItem(keyData);
            }
        }
        PK11_FreeSymKey(symKey);
    }

    return (PBEBitGenContext *)context;
}

/*
 * public, Deprecated, This function is only for binary compatibility with
 * older applications. Does not support PKCS5v2.
 *
 * Applications should use PK11_PBEKeyGen() for keys and PK11_GetIV() for
 * iv values rather than generating PBE bits directly.
 */
SECItem *
PBE_GenerateBits(PBEBitGenContext *context)
{
    return (SECItem *)context;
}

/*
 * public, Deprecated, This function is only for binary compatibility with
 * older applications. Does not support PKCS5v2.
 *
 * Applications should use PK11_PBEKeyGen() for keys and PK11_GetPBEIV() for
 * iv values rather than generating PBE bits directly.
 */
void
PBE_DestroyContext(PBEBitGenContext *context)
{
    SECITEM_FreeItem((SECItem *)context, PR_TRUE);
}

/*
 * public, deprecated. Replaced with PK11_GetPBEIV().
 */
SECItem *
SEC_PKCS5GetIV(SECAlgorithmID *algid, SECItem *pwitem, PRBool faulty3DES)
{
    /* pbe stuff */
    CK_MECHANISM_TYPE type;
    SECItem *param = NULL;
    SECItem *iv = NULL;
    SECItem src;
    int iv_len = 0;
    PK11SymKey *symKey;
    PK11SlotInfo *slot;
    CK_PBE_PARAMS_PTR pPBEparams;
    SECOidTag pbeAlg;

    pbeAlg = SECOID_GetAlgorithmTag(algid);
    if (sec_pkcs5_is_algorithm_v2_pkcs5_algorithm(pbeAlg)) {
        unsigned char *ivData;
        sec_pkcs5V2Parameter *pbeV2_param = NULL;

        /* can only return the IV if the crypto Algorithm exists */
        if (pbeAlg == SEC_OID_PKCS5_PBKDF2) {
            PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
            goto loser;
        }
        pbeV2_param = sec_pkcs5_v2_get_v2_param(NULL, algid);
        if (pbeV2_param == NULL) {
            goto loser;
        }
        /* extract the IV from the cipher algid portion of our pkcs 5 v2
         * algorithm id */
        type = PK11_AlgtagToMechanism(
            SECOID_GetAlgorithmTag(&pbeV2_param->cipherAlgId));
        param = PK11_ParamFromAlgid(&pbeV2_param->cipherAlgId);
        sec_pkcs5_v2_destroy_v2_param(pbeV2_param);
        if (!param) {
            goto loser;
        }
        /* NOTE: NULL is a permissible return here */
        ivData = PK11_IVFromParam(type, param, &iv_len);
        src.data = ivData;
        src.len = iv_len;
        goto done;
    }

    type = PK11_AlgtagToMechanism(pbeAlg);
    param = PK11_ParamFromAlgid(algid);
    if (param == NULL) {
        goto done;
    }
    slot = PK11_GetInternalSlot();
    symKey = PK11_RawPBEKeyGen(slot, type, param, pwitem, faulty3DES, NULL);
    PK11_FreeSlot(slot);
    if (symKey == NULL) {
        goto loser;
    }
    PK11_FreeSymKey(symKey);
    pPBEparams = (CK_PBE_PARAMS_PTR)param->data;
    iv_len = PK11_GetIVLength(type);

    src.data = (unsigned char *)pPBEparams->pInitVector;
    src.len = iv_len;

done:
    iv = SECITEM_DupItem(&src);

loser:
    if (param) {
        SECITEM_ZfreeItem(param, PR_TRUE);
    }
    return iv;
}

/*
 * Subs from nss 3.x that are deprecated
 */
PBEBitGenContext *
__PBE_CreateContext(SECOidTag hashAlgorithm, PBEBitGenID bitGenPurpose,
                    SECItem *pwitem, SECItem *salt, unsigned int bitsNeeded,
                    unsigned int iterations)
{
    PORT_Assert("__PBE_CreateContext is Deprecated" == NULL);
    return NULL;
}

SECItem *
__PBE_GenerateBits(PBEBitGenContext *context)
{
    PORT_Assert("__PBE_GenerateBits is Deprecated" == NULL);
    return NULL;
}

void
__PBE_DestroyContext(PBEBitGenContext *context)
{
    PORT_Assert("__PBE_DestroyContext is Deprecated" == NULL);
}

SECStatus
RSA_FormatBlock(SECItem *result, unsigned modulusLen,
                int blockType, SECItem *data)
{
    PORT_Assert("RSA_FormatBlock is Deprecated" == NULL);
    return SECFailure;
}

/****************************************************************************
 *
 * Now Do The PBE Functions Here...
 *
 ****************************************************************************/

static void
pk11_destroy_ck_pbe_params(CK_PBE_PARAMS *pbe_params)
{
    if (pbe_params) {
        if (pbe_params->pPassword)
            PORT_ZFree(pbe_params->pPassword, pbe_params->ulPasswordLen);
        if (pbe_params->pSalt)
            PORT_ZFree(pbe_params->pSalt, pbe_params->ulSaltLen);
        PORT_ZFree(pbe_params, sizeof(CK_PBE_PARAMS));
    }
}

/*
 * public, deprecated.  use PK11_CreatePBEAlgorithmID or
 * PK11_CreatePBEV2AlgorithmID instead. If you needthe pkcs #11 parameters,
 * use PK11_ParamFromAlgid from the algorithm id you created using
 * PK11_CreatePBEAlgorithmID or PK11_CreatePBEV2AlgorithmID.
 */
SECItem *
PK11_CreatePBEParams(SECItem *salt, SECItem *pwd, unsigned int iterations)
{
    CK_PBE_PARAMS *pbe_params = NULL;
    SECItem *paramRV = NULL;

    paramRV = SECITEM_AllocItem(NULL, NULL, sizeof(CK_PBE_PARAMS));
    if (!paramRV) {
        goto loser;
    }
    /* init paramRV->data with zeros. SECITEM_AllocItem does not do it */
    PORT_Memset(paramRV->data, 0, sizeof(CK_PBE_PARAMS));

    pbe_params = (CK_PBE_PARAMS *)paramRV->data;
    pbe_params->pPassword = (CK_CHAR_PTR)PORT_ZAlloc(pwd->len);
    if (!pbe_params->pPassword) {
        goto loser;
    }
    if (pwd->data) {
        PORT_Memcpy(pbe_params->pPassword, pwd->data, pwd->len);
    }
    pbe_params->ulPasswordLen = pwd->len;

    pbe_params->pSalt = (CK_CHAR_PTR)PORT_ZAlloc(salt->len);
    if (!pbe_params->pSalt) {
        goto loser;
    }
    PORT_Memcpy(pbe_params->pSalt, salt->data, salt->len);
    pbe_params->ulSaltLen = salt->len;

    pbe_params->ulIteration = (CK_ULONG)iterations;
    return paramRV;

loser:
    if (pbe_params)
        pk11_destroy_ck_pbe_params(pbe_params);
    if (paramRV)
        PORT_ZFree(paramRV, sizeof(SECItem));
    return NULL;
}

/*
 * public, deprecated.
 */
void
PK11_DestroyPBEParams(SECItem *pItem)
{
    if (pItem) {
        CK_PBE_PARAMS *params = (CK_PBE_PARAMS *)(pItem->data);
        if (params)
            pk11_destroy_ck_pbe_params(params);
        PORT_ZFree(pItem, sizeof(SECItem));
    }
}

/*
 * public, Partially supports PKCS5 V2 (some parameters are not controllable
 * through this interface). Use PK11_CreatePBEV2AlgorithmID() if you need
 * finer control these.
 */
SECAlgorithmID *
PK11_CreatePBEAlgorithmID(SECOidTag algorithm, int iteration, SECItem *salt)
{
    SECAlgorithmID *algid = NULL;
    algid = sec_pkcs5CreateAlgorithmID(algorithm,
                                       SEC_OID_UNKNOWN, SEC_OID_UNKNOWN, NULL,
                                       0, salt, iteration);
    return algid;
}

/*
 * public, fully support pkcs5v2.
 */
SECAlgorithmID *
PK11_CreatePBEV2AlgorithmID(SECOidTag pbeAlgTag, SECOidTag cipherAlgTag,
                            SECOidTag prfAlgTag, int keyLength, int iteration,
                            SECItem *salt)
{
    SECAlgorithmID *algid = NULL;
    algid = sec_pkcs5CreateAlgorithmID(pbeAlgTag, cipherAlgTag, prfAlgTag,
                                       NULL, keyLength, salt, iteration);
    return algid;
}

/*
 * private.
 */
PK11SymKey *
pk11_RawPBEKeyGenWithKeyType(PK11SlotInfo *slot, CK_MECHANISM_TYPE type,
                             SECItem *params, CK_KEY_TYPE keyType, int keyLen,
                             SECItem *pwitem, void *wincx)
{
    CK_ULONG pwLen;
    /* do some sanity checks */
    if ((params == NULL) || (params->data == NULL)) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    if (type == CKM_INVALID_MECHANISM) {
        PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
        return NULL;
    }

    /* set the password pointer in the parameters... */
    if (type == CKM_PKCS5_PBKD2) {
        CK_PKCS5_PBKD2_PARAMS *pbev2_params;
        if (params->len < sizeof(CK_PKCS5_PBKD2_PARAMS)) {
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return NULL;
        }
        pbev2_params = (CK_PKCS5_PBKD2_PARAMS *)params->data;
        pbev2_params->pPassword = pwitem->data;
        pwLen = pwitem->len;
        pbev2_params->ulPasswordLen = &pwLen;
    } else {
        CK_PBE_PARAMS *pbe_params;
        if (params->len < sizeof(CK_PBE_PARAMS)) {
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return NULL;
        }
        pbe_params = (CK_PBE_PARAMS *)params->data;
        pbe_params->pPassword = pwitem->data;
        pbe_params->ulPasswordLen = pwitem->len;
    }

    /* generate the key (and sometimes the IV as a side effect...) */
    return pk11_TokenKeyGenWithFlagsAndKeyType(slot, type, params, keyType,
                                               keyLen, NULL,
                                               CKF_SIGN | CKF_ENCRYPT | CKF_DECRYPT | CKF_UNWRAP | CKF_WRAP,
                                               0, wincx);
}

/*
 * public, deprecated. use PK11_PBEKeyGen instead.
 */
PK11SymKey *
PK11_RawPBEKeyGen(PK11SlotInfo *slot, CK_MECHANISM_TYPE type, SECItem *mech,
                  SECItem *pwitem, PRBool faulty3DES, void *wincx)
{
    if (faulty3DES && (type == CKM_NSS_PBE_SHA1_TRIPLE_DES_CBC)) {
        type = CKM_NSS_PBE_SHA1_FAULTY_3DES_CBC;
    }
    return pk11_RawPBEKeyGenWithKeyType(slot, type, mech, -1, 0, pwitem, wincx);
}

/*
 * pubic, supports pkcs5 v2.
 *
 * Create symkey from a PBE key. The algid can be created with
 *  PK11_CreatePBEV2AlgorithmID and PK11_CreatePBEAlgorithmID, or by
 *  extraction of der data.
 */
PK11SymKey *
PK11_PBEKeyGen(PK11SlotInfo *slot, SECAlgorithmID *algid, SECItem *pwitem,
               PRBool faulty3DES, void *wincx)
{
    CK_MECHANISM_TYPE type;
    SECItem *param = NULL;
    PK11SymKey *symKey = NULL;
    SECOidTag pbeAlg;
    CK_KEY_TYPE keyType = -1;
    int keyLen = 0;

    pbeAlg = SECOID_GetAlgorithmTag(algid);
    /* if we're using PKCS5v2, extract the additional information we need
     * (key length, key type, and pbeAlg). */
    if (sec_pkcs5_is_algorithm_v2_pkcs5_algorithm(pbeAlg)) {
        CK_MECHANISM_TYPE cipherMech;
        sec_pkcs5V2Parameter *pbeV2_param;

        pbeV2_param = sec_pkcs5_v2_get_v2_param(NULL, algid);
        if (pbeV2_param == NULL) {
            return NULL;
        }
        cipherMech = PK11_AlgtagToMechanism(
            SECOID_GetAlgorithmTag(&pbeV2_param->cipherAlgId));
        pbeAlg = SECOID_GetAlgorithmTag(&pbeV2_param->pbeAlgId);
        param = PK11_ParamFromAlgid(&pbeV2_param->pbeAlgId);
        sec_pkcs5_v2_destroy_v2_param(pbeV2_param);
        keyLen = SEC_PKCS5GetKeyLength(algid);
        if (keyLen == -1) {
            keyLen = 0;
        }
        keyType = PK11_GetKeyType(cipherMech, keyLen);
    } else {
        param = PK11_ParamFromAlgid(algid);
    }

    if (param == NULL) {
        goto loser;
    }

    type = PK11_AlgtagToMechanism(pbeAlg);
    if (type == CKM_INVALID_MECHANISM) {
        PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
        goto loser;
    }
    if (faulty3DES && (type == CKM_NSS_PBE_SHA1_TRIPLE_DES_CBC)) {
        type = CKM_NSS_PBE_SHA1_FAULTY_3DES_CBC;
    }
    symKey = pk11_RawPBEKeyGenWithKeyType(slot, type, param, keyType, keyLen,
                                          pwitem, wincx);

loser:
    if (param) {
        SECITEM_ZfreeItem(param, PR_TRUE);
    }
    return symKey;
}

/*
 * public, supports pkcs5v2
 */
SECItem *
PK11_GetPBEIV(SECAlgorithmID *algid, SECItem *pwitem)
{
    return SEC_PKCS5GetIV(algid, pwitem, PR_FALSE);
}

CK_MECHANISM_TYPE
pk11_GetPBECryptoMechanism(SECAlgorithmID *algid, SECItem **param,
                           SECItem *pbe_pwd, PRBool faulty3DES)
{
    int keyLen = 0;
    SECOidTag algTag = SEC_PKCS5GetCryptoAlgorithm(algid);
    CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(algTag);
    CK_MECHANISM_TYPE returnedMechanism = CKM_INVALID_MECHANISM;
    SECItem *iv = NULL;

    if (mech == CKM_INVALID_MECHANISM) {
        PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
        goto loser;
    }
    if (PK11_GetIVLength(mech)) {
        iv = SEC_PKCS5GetIV(algid, pbe_pwd, faulty3DES);
        if (iv == NULL) {
            goto loser;
        }
    }

    keyLen = SEC_PKCS5GetKeyLength(algid);

    *param = pk11_ParamFromIVWithLen(mech, iv, keyLen);
    if (*param == NULL) {
        goto loser;
    }
    returnedMechanism = mech;

loser:
    if (iv) {
        SECITEM_FreeItem(iv, PR_TRUE);
    }
    return returnedMechanism;
}

/*
 * Public, supports pkcs5 v2
 *
 * Get the crypto mechanism directly from the pbe algorithmid.
 *
 * It's important to go directly from the algorithm id so that we can
 * handle both the PKCS #5 v1, PKCS #12, and PKCS #5 v2 cases.
 *
 * This function returns both the mechanism and the parameter for the mechanism.
 * The caller is responsible for freeing the parameter.
 */
CK_MECHANISM_TYPE
PK11_GetPBECryptoMechanism(SECAlgorithmID *algid, SECItem **param,
                           SECItem *pbe_pwd)
{
    return pk11_GetPBECryptoMechanism(algid, param, pbe_pwd, PR_FALSE);
}
