/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

#include "plarena.h"

#include "seccomon.h"
#include "secitem.h"
#include "secport.h"
#include "hasht.h"
#include "pkcs11t.h"
#include "blapi.h"
#include "sechash.h"
#include "secasn1.h"
#include "secder.h"
#include "secpkcs5.h"
#include "secoid.h"
#include "alghmac.h"
#include "softoken.h"
#include "secerr.h"

#define DES_IV_LENGTH		8
#define RC2_IV_LENGTH		8
#define MD2_LENGTH		16
#define MD5_LENGTH		16
#define SHA1_LENGTH		20
#define SEED_LENGTH 		16
#define SALT_LENGTH		8
#define PBE_SALT_LENGTH		16

/* 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 }
};

pbeBitGenParameters pbeHashAlgorithmParams[] = {
    {   0,   0, SEC_OID_UNKNOWN },
    { 128, 512, SEC_OID_MD2 },
    { 128, 512, SEC_OID_MD5 },
    { 160, 512, SEC_OID_SHA1 },
};

/* generate some random bytes.  this is used to generate the 
 * salt if it is not specified.
 */
static SECStatus 
sec_pkcs5_generate_random_bytes(PRArenaPool *poolp, 
				SECItem *dest, int len)
{
    SECStatus rv = SECFailure;

    if(dest != NULL)
    {
	void *mark = PORT_ArenaMark(poolp);
	dest->data = (unsigned char *)PORT_ArenaZAlloc(poolp, len);
	if(dest->data != NULL)
	{
	    dest->len = len;
	    RNG_GenerateGlobalRandomBytes(dest->data, dest->len);
	    PORT_ArenaUnmark(poolp, mark);
	    rv = SECSuccess;
	} else
	    PORT_ArenaRelease(poolp, mark);
    }

    return rv;
}

/* maps hash algorithm from PBE algorithm.
 */
static SECOidTag 
sec_pkcs5_hash_algorithm(SECOidTag algorithm)
{
    switch(algorithm)
    {
	case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC:
	case SEC_OID_PKCS5_PBE_WITH_SHA1_AND_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_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:
	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 SEC_OID_SHA1;
	case SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC:
	    return SEC_OID_MD5;
	case SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC:
	    return SEC_OID_MD2;
	default:
	    break;
    }
    return SEC_OID_UNKNOWN;
}

/* get the iv length needed for the PBE algorithm 
 */
static int 
sec_pkcs5_iv_length(SECOidTag algorithm)
{
    switch(algorithm)
    {
	case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC:
	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:
	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:
	    return DES_IV_LENGTH;
	case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_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:
	case SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
	    return RC2_IV_LENGTH;
	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 0;
	default:
	    break;
    }
    return -1;
}

/* get the key length needed for the PBE algorithm
 */
static int 
sec_pkcs5_key_length(SECOidTag algorithm)
{
    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;
	default:
	    break;
    }
    return -1;
}

/* the 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;
}

/* 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 keyLen 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.
 *   keyLen - only used in variable key length algorithms
 *   iv - if null, the IV will be generated based on PKCS 5 when needed.
 *   params - optional, currently unsupported additional parameters.
 * once a parameter is allocated, it should be destroyed calling 
 * sec_pkcs5_destroy_pbe_parameter or SEC_PKCS5DestroyPBEParameter.
 */
static SEC_PKCS5PBEParameter *
sec_pkcs5_create_pbe_parameter(SECOidTag algorithm, 
			SECItem *salt, 
			int iteration)
{
    PRArenaPool *poolp = NULL;
    SEC_PKCS5PBEParameter *pbe_param = NULL;
    SECStatus rv; 
    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;
    pbe_param->algorithm = algorithm;

    /* should we generate the salt? */
    if(!salt || !salt->data) {
	rv = sec_pkcs5_generate_random_bytes(poolp, &pbe_param->salt, 
						SALT_LENGTH);
    } else {
	rv = SECITEM_CopyItem(poolp, &pbe_param->salt, salt);
    }

    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;
    }

    return pbe_param;
}

/* generate bits for key and iv using MD5 hashing 
 */
static SECItem *
sec_pkcs5_compute_md5_hash(SECItem *salt, SECItem *pwd, int iter,
			   PRBool dummy)
{
    SECItem *hash = NULL, *pre_hash = NULL;
    SECStatus rv = SECFailure;

    if((salt == NULL) || (pwd == NULL) || (iter < 0)) {
	return NULL;
    }
	
    hash = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    pre_hash = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if((hash != NULL) && (pre_hash != NULL)) {
	unsigned int i, ph_len;

	ph_len = MD5_LENGTH;
	if(ph_len < (salt->len + pwd->len)) {
	    ph_len = salt->len + pwd->len;
	}

	rv = SECFailure;
	hash->data = (unsigned char *)PORT_ZAlloc(MD5_LENGTH);
	hash->len = MD5_LENGTH;
	pre_hash->data = (unsigned char *)PORT_ZAlloc(ph_len);
	pre_hash->len = salt->len + pwd->len;

	if((hash->data != NULL) && (pre_hash->data != NULL)) {
	    rv = SECSuccess;
	    /* handle 0 length password */
	    if(pwd->len > 0) {
		PORT_Memcpy(pre_hash->data, pwd->data, pwd->len);
	    }
	    if(salt->len > 0) {
	 	PORT_Memcpy((pre_hash->data+pwd->len), salt->data, salt->len);
	    }
	    for(i = 0; ((i < (unsigned int)iter) && (rv == SECSuccess)); i++) {
		rv = MD5_HashBuf(hash->data, pre_hash->data, pre_hash->len);
		if(rv != SECFailure) {
		    PORT_Memcpy(pre_hash->data, hash->data, MD5_LENGTH);
		    pre_hash->len = MD5_LENGTH;
		}
	    }
	}
    }

    if(pre_hash != NULL)
	SECITEM_FreeItem(pre_hash, PR_TRUE);

    if((rv == SECFailure) && (hash)) {
	SECITEM_FreeItem(hash, PR_TRUE);
	hash = NULL;
    }

    return hash;
}

/* generate bits for key and iv using MD2 hashing 
 */
static SECItem *
sec_pkcs5_compute_md2_hash(SECItem *salt, SECItem *pwd, int iter,
			   PRBool dummy)
{
    SECItem *hash = NULL, *pre_hash = NULL;
    SECStatus rv = SECFailure;

    if((salt == NULL) || (pwd == NULL) || (iter < 0))
	return NULL;

    hash = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    pre_hash = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if((hash != NULL) && (pre_hash != NULL))
    {
	int i, ph_len;

	ph_len = MD2_LENGTH;
	if((salt->len + pwd->len) > MD2_LENGTH)
	    ph_len = salt->len+pwd->len;

	rv = SECFailure;
	hash->data = (unsigned char *)PORT_ZAlloc(MD2_LENGTH);
	hash->len = MD2_LENGTH;
	pre_hash->data = (unsigned char *)PORT_ZAlloc(ph_len);
	pre_hash->len = salt->len + pwd->len;

	if((hash->data != NULL) && (pre_hash->data != NULL))
	{
	    MD2Context *ctxt;

	    rv = SECSuccess;
	    if(pwd->len > 0) {
		PORT_Memcpy(pre_hash->data, pwd->data, pwd->len);
	    }
	    if(salt->len > 0) {
		PORT_Memcpy((pre_hash->data+pwd->len), salt->data, salt->len);
	    }

	    for(i = 0; ((i < iter) && (rv == SECSuccess)); i++)
	    {
		ctxt = MD2_NewContext();
		if(ctxt == NULL)
		    rv = SECFailure;		
		else
		{
		    MD2_Update(ctxt, pre_hash->data, pre_hash->len);
		    MD2_End(ctxt, hash->data, &hash->len, hash->len);
		    PORT_Memcpy(pre_hash->data, hash->data, MD2_LENGTH);
		    pre_hash->len = MD2_LENGTH;
		    MD2_DestroyContext(ctxt, PR_TRUE);
		}
	    }		
	}
    }

    if(pre_hash != NULL)
	SECITEM_FreeItem(pre_hash, PR_TRUE);

    if(rv != SECSuccess)
	if(hash != NULL)
	{
	    SECITEM_FreeItem(hash, PR_TRUE);
	    hash = NULL;
	}

    return hash;
}

/* generate bits using SHA1 hash 
 */
static SECItem *
sec_pkcs5_compute_sha1_hash(SECItem *salt, SECItem *pwd, int iter, 
			    PRBool faulty3DES) 
{
    SECItem *hash = NULL, *pre_hash = NULL;
    SECStatus rv = SECFailure;

    if((salt == NULL) || (pwd == NULL) || (iter < 0)) {
	return NULL;
    }
	
    hash = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    pre_hash = (SECItem *)PORT_ZAlloc(sizeof(SECItem));

    if((hash != NULL) && (pre_hash != NULL)) {
	int i, ph_len;

	ph_len = SHA1_LENGTH;
	if((salt->len + pwd->len) > SHA1_LENGTH) {
	    ph_len = salt->len + pwd->len;
	}

	rv = SECFailure;

	/* allocate buffers */
	hash->data = (unsigned char *)PORT_ZAlloc(SHA1_LENGTH);
	hash->len = SHA1_LENGTH;
	pre_hash->data = (unsigned char *)PORT_ZAlloc(ph_len);

	/* in pbeSHA1TripleDESCBC there was an allocation error that made
	 * it into the caller.  We do not want to propagate those errors
	 * further, so we are doing it correctly, but reading the old method.
	 */
	if(faulty3DES) {
	    pre_hash->len = ph_len;
	} else {
	    pre_hash->len = salt->len + pwd->len;
	}

	/* preform hash */
	if((hash->data != NULL) && (pre_hash->data != NULL)) {
	    rv = SECSuccess;
	    /* check for 0 length password */
	    if(pwd->len > 0) {
		PORT_Memcpy(pre_hash->data, pwd->data, pwd->len);
	    }
	    if(salt->len > 0) {
		PORT_Memcpy((pre_hash->data+pwd->len), salt->data, salt->len);
	    }
	    for(i = 0; ((i < iter) && (rv == SECSuccess)); i++) {
		rv = SHA1_HashBuf(hash->data, pre_hash->data, pre_hash->len);
		if(rv != SECFailure) {
		    pre_hash->len = SHA1_LENGTH;
		    PORT_Memcpy(pre_hash->data, hash->data, SHA1_LENGTH);
		}
	    }
	}
    }

    if(pre_hash != NULL) {
	SECITEM_FreeItem(pre_hash, PR_TRUE);
    }

    if((rv != SECSuccess) && (hash != NULL)) {
	SECITEM_FreeItem(hash, PR_TRUE);
	hash = NULL;
    }

    return hash;
}

/* bit generation/key and iv generation routines. */
typedef SECItem *(* sec_pkcs5_hash_func)(SECItem *s, SECItem *p, 
					 int iter, PRBool faulty3DES);

/* generates bits needed for the key and iv based on PKCS 5,
 * be concatenating the password and salt and using the appropriate
 * hash algorithm.  This function serves as a front end to the 
 * specific hash functions above.  a return of NULL indicates an
 * error.
 */
static SECItem *
sec_pkcs5_compute_hash(SEC_PKCS5PBEParameter *pbe_param, SECItem *pwitem,
		       PRBool faulty3DES)
{
    sec_pkcs5_hash_func hash_func;
    SECOidTag hash_alg;
    SECItem *hash = NULL;
    SECItem *salt = NULL;
	
    hash_alg = sec_pkcs5_hash_algorithm(pbe_param->algorithm);
    salt = &(pbe_param->salt);
    switch(hash_alg)
    {
	case SEC_OID_SHA1:
	    hash_func = sec_pkcs5_compute_sha1_hash;
	    break;
	case SEC_OID_MD2:
	    hash_func = sec_pkcs5_compute_md2_hash;
	    break;
	case SEC_OID_MD5:
	    hash_func = sec_pkcs5_compute_md5_hash;
	    break;
	default:
	    hash_func = NULL;
    }

    if(hash_func) {
	hash = (* hash_func)(salt, pwitem, pbe_param->iter, faulty3DES);
    }

    return hash;
}

/* determines the number of bits needed for key and iv generation
 * based upon the algorithm identifier.  if a number of
 * bits greater than the hash algorithm can produce are needed,
 * the bits will be generated based upon the extended PKCS 5 
 * described in PKCS 12.
 *
 * a return of -1 indicates an error.
 */
static int 
sec_pkcs5_bits_needed(SEC_PKCS5PBEParameter *pbe_param)
{
    int iv_bits;
    int key_bits;
    
    if(pbe_param == NULL) {
	return -1;
    }

    iv_bits = sec_pkcs5_iv_length(pbe_param->algorithm) * 8;
    key_bits = sec_pkcs5_key_length(pbe_param->algorithm) * 8;

    if(key_bits != 0) {
	return iv_bits + key_bits;
    }

    return -1;
}

/* determines the number of bits generated by each hash algorithm.
 * in case of an error, -1 is returned. 
 */
static int
sec_pkcs5_hash_bits_generated(SEC_PKCS5PBEParameter *pbe_param)
{
    if(pbe_param == NULL) {
	return -1;
    }

    switch(sec_pkcs5_hash_algorithm(pbe_param->algorithm)) {
	case SEC_OID_SHA1:
	    return SHA1_LENGTH * 8;
	case SEC_OID_MD2:
	    return MD2_LENGTH * 8;
	case SEC_OID_MD5:
	    return MD5_LENGTH * 8;
	default:
	    break;
    }

    return -1;
}

/* this bit generation routine is described in PKCS 12 and the proposed
 * extensions to PKCS 5.  an initial hash is generated following the
 * instructions laid out in PKCS 5.  If the number of bits generated is
 * insufficient, then the method discussed in the proposed extensions to
 * PKCS 5 in PKCS 12 are used.  This extension makes use of the HMAC
 * function.  And the P_Hash function from the TLS standard.
 */
static SECItem *
sec_pkcs5_bit_generator(SEC_PKCS5PBEParameter *pbe_param,
			SECItem *init_hash, 
			unsigned int bits_needed)
{
    SECItem *ret_bits = NULL;
    int hash_size = 0;
    unsigned int i;
    unsigned int hash_iter;
    unsigned int dig_len;
    SECStatus rv = SECFailure;
    unsigned char *state = NULL;
    unsigned int state_len;
    HMACContext *cx = NULL;

    hash_size = sec_pkcs5_hash_bits_generated(pbe_param);
    if(hash_size == -1)
	return NULL;

    hash_iter = (bits_needed + (unsigned int)hash_size - 1) / hash_size;
    hash_size /= 8;

    /* allocate return buffer */
    ret_bits = (SECItem  *)PORT_ZAlloc(sizeof(SECItem));
    if(ret_bits == NULL)
	return NULL;
    ret_bits->data = (unsigned char *)PORT_ZAlloc((hash_iter * hash_size) + 1);
    ret_bits->len = (hash_iter * hash_size);
    if(ret_bits->data == NULL) {
	PORT_Free(ret_bits);
	return NULL;
    }

    /* allocate intermediate hash buffer.  8 is for the 8 bytes of
     * data which are added based on iteration number 
     */

    if ((unsigned int)hash_size > pbe_param->salt.len) {
	state_len = hash_size;
    } else {
	state_len = pbe_param->salt.len;
    }
    state = (unsigned char *)PORT_ZAlloc(state_len);
    if(state == NULL) {
	rv = SECFailure;
	goto loser;
    }
    if(pbe_param->salt.len > 0) {
	PORT_Memcpy(state, pbe_param->salt.data, pbe_param->salt.len);
    }

    cx = HMAC_Create(sec_pkcs5_hash_algorithm(pbe_param->algorithm),
		     init_hash->data, init_hash->len);
    if (cx == NULL) {
	rv = SECFailure;
	goto loser;
    }

    for(i = 0; i < hash_iter; i++) { 

	/* generate output bits */
	HMAC_Begin(cx);
	HMAC_Update(cx, state, state_len);
	HMAC_Update(cx, pbe_param->salt.data, pbe_param->salt.len);
	rv = HMAC_Finish(cx, ret_bits->data + (i * hash_size),
			 &dig_len, hash_size);
	if (rv != SECSuccess)
	    goto loser;
	PORT_Assert((unsigned int)hash_size == dig_len);

	/* generate new state */
	HMAC_Begin(cx);
	HMAC_Update(cx, state, state_len);
	rv = HMAC_Finish(cx, state, &state_len, state_len);
	if (rv != SECSuccess)
	    goto loser;
	PORT_Assert(state_len == dig_len);
    }

loser:
    if (state != NULL)
	PORT_ZFree(state, state_len);
    HMAC_Destroy(cx);

    if(rv != SECSuccess) {
	SECITEM_ZfreeItem(ret_bits, PR_TRUE);
	ret_bits = NULL;
    }

    return ret_bits;
}

/* generate bits for the key and iv determination.  if enough bits
 * are not generated using PKCS 5, then we need to generate more bits
 * based on the extension proposed in PKCS 12
 */
static SECItem *
sec_pkcs5_generate_bits(SEC_PKCS5PBEParameter *pbe_param, SECItem *pwitem, 
			PRBool faulty3DES)
{
    SECItem * hash 		= NULL;
    SECItem * newHash 		= NULL;
    int       bits_needed;
    int       bits_available;
    
    bits_needed = sec_pkcs5_bits_needed(pbe_param);
    bits_available = sec_pkcs5_hash_bits_generated(pbe_param);

    if((bits_needed == -1) || (bits_available == -1)) {
	return NULL;
    }

    hash = sec_pkcs5_compute_hash(pbe_param, pwitem, faulty3DES);
    if(hash == NULL) {
	return NULL;
    }

    if(bits_needed <= bits_available) {
	return hash;
    } 

    newHash = sec_pkcs5_bit_generator(pbe_param, hash, bits_needed);
    if (hash != newHash)
	SECITEM_FreeItem(hash, PR_TRUE);
    return newHash;
}

/* compute the IV as per PKCS 5
 */
static SECItem *
sec_pkcs5_compute_iv(SEC_PKCS5PBEParameter *pbe_param, SECItem *pwitem,
		     PRBool faulty3DES)
{
    SECItem *hash = NULL, *iv = NULL;

    if((pbe_param == NULL) || (pwitem == NULL)) {
	return NULL;
    }

    /* generate iv */
    iv = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if(!iv) {
	return NULL;
    }

    iv->len = sec_pkcs5_iv_length(pbe_param->algorithm);
    if(iv->len == -1) {
	PORT_Free(iv);
	return NULL;
    }

    iv->data = (unsigned char *)PORT_ZAlloc(iv->len);
    if(iv->data == NULL) {
	PORT_Free(iv);
	return NULL;
    }

    if(sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(pbe_param->algorithm)) {
	SECOidTag hashAlg;
	PBEBitGenContext *ctxt;
	hashAlg = sec_pkcs5_hash_algorithm(pbe_param->algorithm);
	ctxt = PBE_CreateContext(hashAlg, pbeBitGenCipherIV,
				 pwitem, &pbe_param->salt,
				 iv->len * 8, pbe_param->iter);
	if(!ctxt) {
	    SECITEM_FreeItem(iv, PR_TRUE);
	    return NULL;
	}

	hash = PBE_GenerateBits(ctxt);
	PBE_DestroyContext(ctxt);
    } else {
	hash = sec_pkcs5_generate_bits(pbe_param, pwitem, faulty3DES);
    }

    if(!hash) {
	SECITEM_FreeItem(iv, PR_TRUE);
	return NULL;
    }

    PORT_Memcpy(iv->data, (hash->data+(hash->len - iv->len)), iv->len);
    SECITEM_FreeItem(hash, PR_TRUE);

    return iv;
}

/* generate key as per PKCS 5
 */
static SECItem *
sec_pkcs5_compute_key(SEC_PKCS5PBEParameter *pbe_param, SECItem *pwitem,
		      PRBool faulty3DES)
{
    SECItem *hash = NULL, *key = NULL;

    if((pbe_param == NULL) || (pwitem == NULL)) {
	return NULL;
    }

    key = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if(!key) {
	return NULL;
    }

    key->len = sec_pkcs5_key_length(pbe_param->algorithm);
    if(key->len == -1) {
	PORT_Free(key);
	return NULL;
    }
	
    key->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) *
					     key->len);
    if(!key->data) {
	PORT_Free(key);
	return NULL;
    }


    if(sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(pbe_param->algorithm)) {
	SECOidTag hashAlg;
	PBEBitGenContext *ctxt;
	hashAlg = sec_pkcs5_hash_algorithm(pbe_param->algorithm);
	ctxt = PBE_CreateContext(hashAlg, pbeBitGenCipherKey,
				 pwitem, &pbe_param->salt,
				 key->len * 8, pbe_param->iter);
	if(!ctxt) {
	    SECITEM_FreeItem(key, PR_TRUE);
	    return NULL;
	}

	hash = PBE_GenerateBits(ctxt);
	PBE_DestroyContext(ctxt);
    } else {
	hash = sec_pkcs5_generate_bits(pbe_param, pwitem, faulty3DES);
    }
   
    if(!hash) {
	SECITEM_FreeItem(key, PR_TRUE);
	return NULL;
    }

    if(pbe_param->algorithm ==
		   SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC) {
	PORT_Memcpy(key->data, hash->data, (key->len * 2) / 3);
	PORT_Memcpy(&(key->data[(key->len  * 2) / 3]), key->data,
		    key->len / 3);
    } else {
	PORT_Memcpy(key->data, hash->data, key->len);
    }

    SECITEM_FreeItem(hash, PR_TRUE);
    return key;
}

/* decode the algid and generate a PKCS 5 parameter from it
 */
static SEC_PKCS5PBEParameter *
sec_pkcs5_convert_algid(SECAlgorithmID *algid)
{
    PRArenaPool *poolp;
    SEC_PKCS5PBEParameter *pbe_param = NULL;
    SECOidTag algorithm;
    SECStatus rv = SECFailure;

    if(algid == NULL)
	return NULL;

    algorithm = SECOID_GetAlgorithmTag(algid);
   
    if(sec_pkcs5_hash_algorithm(algorithm) == SEC_OID_UNKNOWN)
	return NULL;

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

    /* allocate memory for the parameter */
    pbe_param = (SEC_PKCS5PBEParameter *)PORT_ArenaZAlloc(poolp, 
	sizeof(SEC_PKCS5PBEParameter));

    /* decode parameter */
    if(pbe_param && !sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(algorithm)) {
	pbe_param->poolp = poolp;
	rv = SEC_ASN1DecodeItem(poolp, pbe_param, 
	    SEC_PKCS5PBEParameterTemplate, &algid->parameters);
	if(rv != SECSuccess) {
	    goto loser;
	}
	pbe_param->algorithm = algorithm;
	pbe_param->iter = DER_GetInteger(&pbe_param->iteration);
    } else if(sec_pkcs5_is_algorithm_v2_pkcs12_algorithm(algorithm)) {
	pbe_param->algorithm = algorithm;
	pbe_param->poolp = poolp;
	rv = SEC_ASN1DecodeItem(poolp, pbe_param, SEC_V2PKCS12PBEParameterTemplate,
				&algid->parameters);
	if(rv != SECSuccess) {
	    goto loser;
	}
	pbe_param->iter = DER_GetInteger(&pbe_param->iteration);
    }

loser:
    if((pbe_param == NULL) || (rv != SECSuccess)) {
	PORT_FreeArena(poolp, PR_TRUE);
	pbe_param = NULL;
    }

    return pbe_param;
}

/* 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);
}


/* crypto routines */

/* function pointer template for crypto functions */
typedef SECItem *(* pkcs5_crypto_func)(SECItem *key, SECItem *iv, 
					 SECItem *src, PRBool op1, PRBool op2);

/* map PBE algorithm to crypto algorithm */
static SECOidTag 
sec_pkcs5_encryption_algorithm(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_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 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;
	default:
	    break;
    }
    return SEC_OID_UNKNOWN;
}

/* perform DES encryption and decryption.  these routines are called
 * by SEC_PKCS5CipherData.  In the case of an error, NULL is returned.
 */
static SECItem *
sec_pkcs5_des(SECItem *key, 
	      SECItem *iv, 
	      SECItem *src,
	      PRBool triple_des, 
	      PRBool encrypt)
{
    SECItem *dest;
    SECItem *dup_src;
    SECStatus rv = SECFailure;
    int pad;

    if((src == NULL) || (key == NULL) || (iv == NULL))
	return NULL;

    dup_src = SECITEM_DupItem(src);
    if(dup_src == NULL) {
	return NULL;
    }

    if(encrypt != PR_FALSE) {
	void *dummy;

	dummy = DES_PadBuffer(NULL, dup_src->data, 
	    dup_src->len, &dup_src->len);
	if(dummy == NULL) {
	    SECITEM_FreeItem(dup_src, PR_TRUE);
	    return NULL;
	}
	dup_src->data = (unsigned char*)dummy;
    }

    dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if(dest != NULL) {
	/* allocate with over flow */
	dest->data = (unsigned char *)PORT_ZAlloc(dup_src->len + 64);
	if(dest->data != NULL) {
	    DESContext *ctxt;
	    ctxt = DES_CreateContext(key->data, iv->data, 
			(triple_des ? NSS_DES_EDE3_CBC : NSS_DES_CBC), 
			encrypt);
	
	    if(ctxt != NULL) {
		rv = ((encrypt != PR_TRUE) ? DES_Decrypt : DES_Encrypt)(
			ctxt, dest->data, &dest->len,
			dup_src->len + 64, dup_src->data, dup_src->len);

		/* remove padding -- assumes 64 bit blocks */
		if((encrypt == PR_FALSE) && (rv == SECSuccess)) {
		    pad = dest->data[dest->len-1];
		    if((pad > 0) && (pad <= 8)) {
			if(dest->data[dest->len-pad] != pad) {
			    rv = SECFailure;
			    PORT_SetError(SEC_ERROR_BAD_PASSWORD);
			} else {
			    dest->len -= pad;
			}
		    } else {
			rv = SECFailure;
			PORT_SetError(SEC_ERROR_BAD_PASSWORD);
		    }
		}
		DES_DestroyContext(ctxt, PR_TRUE);
	    }
	}
    }

    if(rv == SECFailure) {
	if(dest != NULL) {
	    SECITEM_FreeItem(dest, PR_TRUE);
	}
	dest = NULL;
    }

    if(dup_src != NULL) {
	SECITEM_FreeItem(dup_src, PR_TRUE);
    }

    return dest;
}

/* perform rc2 encryption/decryption if an error occurs, NULL is returned
 */
static SECItem *
sec_pkcs5_rc2(SECItem *key, 
	      SECItem *iv, 
	      SECItem *src,
	      PRBool cbc_mode, 
	      PRBool encrypt)
{
    SECItem *dest;
    SECItem *dup_src;
    SECStatus rv = SECFailure;
    int pad;

    if((src == NULL) || (key == NULL) || (iv == NULL)) {
	return NULL;
    }

    dup_src = SECITEM_DupItem(src);
    if(dup_src == NULL) {
	return NULL;
    }

    if(encrypt != PR_FALSE) {
	void *dummy;

	dummy = DES_PadBuffer(NULL, dup_src->data, 
			      dup_src->len, &dup_src->len);
	if(dummy == NULL) {
	    SECITEM_FreeItem(dup_src, PR_TRUE);
	    return NULL;
	}
	dup_src->data = (unsigned char*)dummy;
    }

    dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if(dest != NULL) {
	dest->data = (unsigned char *)PORT_ZAlloc(dup_src->len + 64);
	if(dest->data != NULL) {
	    RC2Context *ctxt;

	    ctxt = RC2_CreateContext(key->data, key->len, iv->data, 
			((cbc_mode != PR_TRUE) ? NSS_RC2 : NSS_RC2_CBC), 
			key->len);

	    if(ctxt != NULL) {
		rv = ((encrypt != PR_TRUE) ? RC2_Decrypt : RC2_Encrypt)(
			ctxt, dest->data, &dest->len,
			dup_src->len + 64, dup_src->data, dup_src->len);

		/* assumes 8 byte blocks  -- remove padding */	
		if((rv == SECSuccess) && (encrypt != PR_TRUE) && 
			(cbc_mode == PR_TRUE)) {
		    pad = dest->data[dest->len-1];
		    if((pad > 0) && (pad <= 8)) {
			if(dest->data[dest->len-pad] != pad) {
			    PORT_SetError(SEC_ERROR_BAD_PASSWORD);
			    rv = SECFailure;
			} else {
			    dest->len -= pad;
			}
		    } else {
			PORT_SetError(SEC_ERROR_BAD_PASSWORD);
			rv = SECFailure;
		    }
		}

	    }
	}
    }

    if((rv != SECSuccess) && (dest != NULL)) {
	SECITEM_FreeItem(dest, PR_TRUE);
	dest = NULL;
    }

    if(dup_src != NULL) {
	SECITEM_FreeItem(dup_src, PR_TRUE);
    }

    return dest;
}

/* perform rc4 encryption and decryption */
static SECItem *
sec_pkcs5_rc4(SECItem *key, 
	      SECItem *iv, 
	      SECItem *src, 
	      PRBool dummy_op,
	      PRBool encrypt)
{
    SECItem *dest;
    SECStatus rv = SECFailure;

    if((src == NULL) || (key == NULL) || (iv == NULL)) {
	return NULL;
    }

    dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if(dest != NULL) {
	dest->data = (unsigned char *)PORT_ZAlloc(sizeof(unsigned char) *
	    (src->len + 64));
	if(dest->data != NULL) {
	    RC4Context *ctxt;

	    ctxt = RC4_CreateContext(key->data, key->len);
	    if(ctxt) { 
		rv = ((encrypt != PR_FALSE) ? RC4_Decrypt : RC4_Encrypt)(
				ctxt, dest->data, &dest->len,
				src->len + 64, src->data, src->len);
		RC4_DestroyContext(ctxt, PR_TRUE);
	    }
	}
    }

    if((rv != SECSuccess) && (dest)) {
	SECITEM_FreeItem(dest, PR_TRUE);
	dest = NULL;
    }

    return dest;
}

/* performs the cipher operation on the src and returns the result.
 * if an error occurs, NULL is returned. 
 *
 * a null length password is allowed.  this corresponds to encrypting
 * the data with ust the salt.
 */
/* change this to use PKCS 11? */
SECItem *
SEC_PKCS5CipherData(SECAlgorithmID *algid, 
		    SECItem *pwitem,
		    SECItem *src, 
		    PRBool encrypt, PRBool *update)
{
    SEC_PKCS5PBEParameter *pbe_param;
    SECOidTag enc_alg;
    SECItem *key = NULL, *iv = NULL;
    SECItem *dest = NULL;
    int iv_len;

    if (update) { 
        *update = PR_FALSE;
    }

    if((algid == NULL) || (pwitem == NULL) || (src == NULL)) {
	return NULL;
    }

    /* convert algid to pbe parameter */
    pbe_param = sec_pkcs5_convert_algid(algid);
    if(pbe_param == NULL) {
	return NULL;
    }

    /* get algorithm, key, and iv */
    enc_alg = sec_pkcs5_encryption_algorithm(pbe_param->algorithm);
    key = sec_pkcs5_compute_key(pbe_param, pwitem, PR_FALSE);
    if(key != NULL) {
	iv_len = sec_pkcs5_iv_length(pbe_param->algorithm);
    	iv = sec_pkcs5_compute_iv(pbe_param, pwitem, PR_FALSE);

	if((iv != NULL) || (iv_len == 0)) {
	    /*perform encryption / decryption */
	    PRBool op1 = PR_TRUE;
	    pkcs5_crypto_func cryptof;

	    switch(enc_alg) {
		case SEC_OID_DES_EDE3_CBC:
		    cryptof = sec_pkcs5_des;
		    break;
		case SEC_OID_DES_CBC:
		    cryptof = sec_pkcs5_des;
		    op1 = PR_FALSE;
		    break;
		case SEC_OID_RC2_CBC:
		    cryptof = sec_pkcs5_rc2;
		    break;
		case SEC_OID_RC4:
		    cryptof = sec_pkcs5_rc4;
		    break;
		default:
		    cryptof = NULL;
		    break;
	    }

	    if(cryptof) {
		dest = (*cryptof)(key, iv, src, op1, encrypt);
		/* 
		 * it's possible for some keys and keydb's to claim to
		 * be triple des when they're really des. In this case
		 * we simply try des. If des works we set the update flag
		 * so the key db knows it needs to update all it's entries.
		 *  The case can only happen on decrypted of a 
		 *  SEC_OID_DES_EDE3_CBD.
		 */
		if ((dest == NULL) && (encrypt == PR_FALSE) && 
					(enc_alg == SEC_OID_DES_EDE3_CBC)) {
		    dest = (*cryptof)(key, iv, src, PR_FALSE, encrypt);
		    if (update && (dest != NULL)) *update = PR_TRUE;
		}
	    }
	}
    }

    sec_pkcs5_destroy_pbe_param(pbe_param);

    if(key != NULL) {
	SECITEM_ZfreeItem(key, PR_TRUE);
    }
    if(iv != NULL) {
	SECITEM_ZfreeItem(iv, PR_TRUE);
    }

    return dest;
}

/* 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.  if IV is specified, it overrides
 * the PKCS 5 generation of the IV.  
 *
 * the returned SECAlgorithmID should be destroyed using 
 * SECOID_DestroyAlgorithmID
 */
SECAlgorithmID *
SEC_PKCS5CreateAlgorithmID(SECOidTag algorithm, 
			   SECItem *salt, 
			   int iteration)
{
    PRArenaPool *poolp = NULL;
    SECAlgorithmID *algid, *ret_algid;
    SECItem der_param;
    SECStatus rv = SECFailure;
    SEC_PKCS5PBEParameter *pbe_param;

    if(sec_pkcs5_hash_algorithm(algorithm) == SEC_OID_UNKNOWN)
	return NULL;

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

    der_param.data = NULL;
    der_param.len = 0;

    /* generate the parameter */
    pbe_param = sec_pkcs5_create_pbe_parameter(algorithm, salt, iteration);
    if(!pbe_param) {
	return NULL;
    }

    poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if(!poolp) {
	sec_pkcs5_destroy_pbe_param(pbe_param);
	return NULL;
    }

    /* generate the algorithm id */
    algid = (SECAlgorithmID *)PORT_ArenaZAlloc(poolp, sizeof(SECAlgorithmID));
    if(algid != NULL) {
	void *dummy;
	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) {
	    rv = SECOID_SetAlgorithmID(poolp, algid, algorithm, &der_param);
	}
    }

    ret_algid = NULL;
    if(algid != NULL) {
	ret_algid = (SECAlgorithmID *)PORT_ZAlloc(sizeof(SECAlgorithmID));
	if(ret_algid != NULL) {
	    rv = SECOID_CopyAlgorithmID(NULL, ret_algid, algid);
	    if(rv != SECSuccess) {
		SECOID_DestroyAlgorithmID(ret_algid, PR_TRUE);
		ret_algid = NULL;
	    }
	}
    }
	
    if(poolp != NULL) {
	PORT_FreeArena(poolp, PR_TRUE);
	algid = NULL;
    }

    sec_pkcs5_destroy_pbe_param(pbe_param);

    return ret_algid;
}

/* wrapper for converting the algid to a pbe parameter. 
 */
SEC_PKCS5PBEParameter *
SEC_PKCS5GetPBEParameter(SECAlgorithmID *algid)
{
    if(algid) {
	return sec_pkcs5_convert_algid(algid);
    }

    return NULL;
}

/* destroy a pbe parameter */
void
SEC_PKCS5DestroyPBEParameter(SEC_PKCS5PBEParameter *pbe_param)
{
    sec_pkcs5_destroy_pbe_param(pbe_param);
}

/* return the initialization vector either the preset one if it 
 * exists or generated based on pkcs 5.
 *
 * a null length password is allowed...but not a null password
 * secitem.
 */
SECItem *
SEC_PKCS5GetIV(SECAlgorithmID *algid, SECItem *pwitem, PRBool faulty3DES)
{
    SECItem *iv;
    SEC_PKCS5PBEParameter *pbe_param;

    if((algid == NULL) || (pwitem == NULL)) {
	return NULL;
    }

    pbe_param = sec_pkcs5_convert_algid(algid);
    if(!pbe_param) {
	return NULL;
    }

    iv = sec_pkcs5_compute_iv(pbe_param, pwitem, faulty3DES);

    sec_pkcs5_destroy_pbe_param(pbe_param);

    return iv;
}

/* generate the key 
 * a 0 length password is allowed.  corresponds to a key generated
 * from just the salt.
 */
SECItem *
SEC_PKCS5GetKey(SECAlgorithmID *algid, SECItem *pwitem, PRBool faulty3DES)
{
    SECItem *key;
    SEC_PKCS5PBEParameter *pbe_param;

    if((algid == NULL) || (pwitem == NULL)) {
	return NULL;
    }

    pbe_param = sec_pkcs5_convert_algid(algid);
    if(pbe_param == NULL) {
	return NULL;
    }

    key = sec_pkcs5_compute_key(pbe_param, pwitem, faulty3DES);

    sec_pkcs5_destroy_pbe_param(pbe_param);

    return key;
}

/* retrieve the salt */
SECItem *
SEC_PKCS5GetSalt(SECAlgorithmID *algid)
{
    SECItem *salt;
    SEC_PKCS5PBEParameter *pbe_param;

    if(algid == NULL)
	return NULL;

    pbe_param = sec_pkcs5_convert_algid(algid);
    if(pbe_param == NULL)
	return NULL;

    if(pbe_param->salt.data) {
	salt = SECITEM_DupItem(&pbe_param->salt);
    } else {
	salt = NULL;
    }

    sec_pkcs5_destroy_pbe_param(pbe_param);

    return salt;
}

/* check to see if an oid is a pbe algorithm
 */ 
PRBool 
SEC_PKCS5IsAlgorithmPBEAlg(SECAlgorithmID *algid)
{
    SECOidTag algorithm;

    algorithm = SECOID_GetAlgorithmTag(algid);
    if(sec_pkcs5_hash_algorithm(algorithm) == SEC_OID_UNKNOWN) {
	return PR_FALSE;
    }

    return PR_TRUE;
}

int 
SEC_PKCS5GetKeyLength(SECAlgorithmID *algid)
{
    SEC_PKCS5PBEParameter *pbe_param;
    int keyLen = -1;

    if(algid == NULL)
	return -1;

    pbe_param = sec_pkcs5_convert_algid(algid);
    if(pbe_param == NULL)
	return -1;

    keyLen = sec_pkcs5_key_length(pbe_param->algorithm);

    sec_pkcs5_destroy_pbe_param(pbe_param);

    return keyLen;
}

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

    if(algid == NULL)
	return SEC_OID_UNKNOWN;

    pbe_param = sec_pkcs5_convert_algid(algid);
    if(pbe_param == NULL)
	return SEC_OID_UNKNOWN;

    switch(pbe_param->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;
	default:
	    break;
    }

    sec_pkcs5_destroy_pbe_param(pbe_param);

    return SEC_OID_UNKNOWN;
}

/* maps PBE algorithm from crypto algorithm, assumes SHA1 hashing.
 */
SECOidTag 
SEC_PKCS5GetPBEAlgorithm(SECOidTag algTag, int keyLen)
{
    switch(algTag)
    {
	case SEC_OID_DES_EDE3_CBC:
	    switch(keyLen) {
		case 168:
		case 192:
		    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:
		    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:
		    return SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4;
		default:
		    break;
	    }
	    break;
	default:
	    break;
    }

    return SEC_OID_UNKNOWN;
}

/* zero length password and salts are allowed.  however, the items
 * containing the salt and password must be non-null.
 */
PBEBitGenContext *
PBE_CreateContext(SECOidTag hashAlgorithm, PBEBitGenID bitGenPurpose,
		  SECItem *pwitem, SECItem *salt, unsigned int bitsNeeded,
		  unsigned int iterations)
{
    PRArenaPool *arena = NULL;
    PBEBitGenContext *pbeCtxt = NULL;
    HASH_HashType pbeHash;
    int vbytes, ubytes;
    
    unsigned int c;

    if(!pwitem || !salt) {
	return NULL;
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if(!arena) {
	return NULL;
    }

    pbeCtxt = (PBEBitGenContext*)PORT_ArenaZAlloc(arena, sizeof(PBEBitGenContext));
    if(!pbeCtxt) {
	goto loser;
    }

    switch(hashAlgorithm) {
	case SEC_OID_MD2:
	    pbeHash = HASH_AlgMD2;
	    break;
	case SEC_OID_MD5:
	    pbeHash = HASH_AlgMD5;
	    break;
	case SEC_OID_SHA1:
	    pbeHash = HASH_AlgSHA1;
	    break;
	default:
	    goto loser;
    }

    pbeCtxt->hashObject = &SECRawHashObjects[pbeHash]; 
    PORT_Memcpy(&pbeCtxt->pbeParams, &pbeHashAlgorithmParams[pbeHash], 
    		sizeof(pbeBitGenParameters));
    PORT_Assert(pbeCtxt->pbeParams.hashAlgorithm == hashAlgorithm);

    vbytes = pbeCtxt->pbeParams.v / 8;
    ubytes = pbeCtxt->pbeParams.u / 8;

    c = (bitsNeeded / pbeCtxt->pbeParams.u);
    c += ((bitsNeeded - (pbeCtxt->pbeParams.u * c)) > 0) ? 1 : 0;
    pbeCtxt->c = c;
    pbeCtxt->n = bitsNeeded;
    pbeCtxt->iterations = iterations;

    /* allocate buffers */
    pbeCtxt->D.len = vbytes;
    pbeCtxt->S.len = (((salt->len * 8) / pbeCtxt->pbeParams.v) + 
    	      ((((salt->len * 8) % pbeCtxt->pbeParams.v) > 0) ? 1 : 0)) *
    	      vbytes;
    pbeCtxt->P.len = (((pwitem->len * 8) / pbeCtxt->pbeParams.v) + 
    	      ((((pwitem->len * 8) % pbeCtxt->pbeParams.v) > 0) ? 1 : 0)) *
	      vbytes;
    pbeCtxt->I.len = pbeCtxt->S.len + pbeCtxt->P.len;
    pbeCtxt->A.len = c * ubytes;
    pbeCtxt->B.len = pbeCtxt->D.len;

    pbeCtxt->D.data = (unsigned char*)PORT_ArenaZAlloc(arena, pbeCtxt->D.len);
    if(pbeCtxt->S.len) {
	pbeCtxt->S.data = (unsigned char*)PORT_ArenaZAlloc(arena, pbeCtxt->S.len);
    }
    if(pbeCtxt->P.len) {
	pbeCtxt->P.data = (unsigned char*)PORT_ArenaZAlloc(arena, pbeCtxt->P.len);
    }
    if(pbeCtxt->I.len) {
	pbeCtxt->I.data = (unsigned char*)PORT_ArenaZAlloc(arena, pbeCtxt->I.len);
    }
    pbeCtxt->A.data = (unsigned char*)PORT_ArenaZAlloc(arena, pbeCtxt->A.len);
    pbeCtxt->B.data = (unsigned char*)PORT_ArenaZAlloc(arena, pbeCtxt->B.len);

    if(!pbeCtxt->D.data || !pbeCtxt->A.data || !pbeCtxt->B.data ||
       (!pbeCtxt->S.data && pbeCtxt->S.len) || 
       (!pbeCtxt->P.data && pbeCtxt->P.len) ||
       (!pbeCtxt->I.data && pbeCtxt->I.len)) {
	goto loser;
    }

    PORT_Memset(pbeCtxt->D.data, (char)bitGenPurpose, pbeCtxt->D.len);
    if(pbeCtxt->P.len) {
	unsigned int z = 0;
	while(z < pbeCtxt->P.len) {
	    PORT_Memcpy(&(pbeCtxt->P.data[z]), pwitem->data,  
	    	((z + pwitem->len > pbeCtxt->P.len) ? (pbeCtxt->P.len - z) :
	    					      (pwitem->len)));
	    z += pwitem->len;
	}
    } 
    if(pbeCtxt->S.len) {
	unsigned int z = 0;
	while(z < pbeCtxt->S.len) {
	    PORT_Memcpy(&(pbeCtxt->S.data[z]), salt->data,  
	    	((z + salt->len > pbeCtxt->S.len) ? (pbeCtxt->S.len - z) :
	    					    (salt->len)));
	    z += salt->len;
	}
    } 
    if(pbeCtxt->I.len) {
	if(pbeCtxt->S.len) {
	    PORT_Memcpy(pbeCtxt->I.data, pbeCtxt->S.data, pbeCtxt->S.len);
	}
	if(pbeCtxt->P.len) {
	    PORT_Memcpy(&(pbeCtxt->I.data[pbeCtxt->S.len]), pbeCtxt->P.data, 
	    		pbeCtxt->P.len);
	}
    }

    pbeCtxt->arena = arena;

    return pbeCtxt;

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

    return NULL;
}

SECItem *
PBE_GenerateBits(PBEBitGenContext *pbeCtxt)
{
    unsigned int i;
    SECItem *A, *D, *I, *B, *S, *P;
    unsigned int u, v, c, z, hashLen, n, iter;
    unsigned int vbyte, ubyte;
    unsigned char *iterBuf;
    void *hash = NULL;

    if(!pbeCtxt) {
	return NULL;
    }

    A = &pbeCtxt->A;
    B = &pbeCtxt->B;
    I = &pbeCtxt->I;
    D = &pbeCtxt->D;
    S = &pbeCtxt->S;
    P = &pbeCtxt->P;
    u = pbeCtxt->pbeParams.u;
    v = pbeCtxt->pbeParams.v;
    vbyte = v / 8;
    ubyte = u / 8;
    c = pbeCtxt->c;
    n = pbeCtxt->n;
    z = 0;

    iterBuf = (unsigned char*)PORT_Alloc(ubyte);
    if(!iterBuf) {
	goto loser;
    }

    for(i = 1; i <= c; i++) {
	unsigned int Bidx;
	unsigned int k, j;


	for(iter = 1; iter <= pbeCtxt->iterations; iter++) {
	    hash = pbeCtxt->hashObject->create();
	    if(!hash) {
		goto loser;
	    }

	    pbeCtxt->hashObject->begin(hash);

	    if(iter == 1) {
		pbeCtxt->hashObject->update(hash, D->data, D->len);
		pbeCtxt->hashObject->update(hash, I->data, I->len); 
	    } else {
		pbeCtxt->hashObject->update(hash, iterBuf, hashLen);
	    }

	    pbeCtxt->hashObject->end(hash, iterBuf, &hashLen, (ubyte));
	    pbeCtxt->hashObject->destroy(hash, PR_TRUE);
	    if(hashLen != (ubyte)) {
		goto loser;
	    }
	}

	PORT_Memcpy(&(A->data[z]), iterBuf, (ubyte));

	Bidx = 0;
	while(Bidx < B->len) {
	    PORT_Memcpy(&(B->data[Bidx]), &(A->data[z]), 
			(((Bidx + (ubyte)) > B->len) ? (B->len - Bidx) : 
						       (ubyte)));
	    Bidx += (ubyte);
	}

	k = (S->len / (vbyte)) + (P->len / (vbyte));
	for(j = 0; j < k; j++) {
	    unsigned int byteIdx = (vbyte);
	    unsigned int q, carryBit = 0;

	    while(byteIdx > 0) {
		q = (unsigned int)I->data[(j * (vbyte)) + byteIdx - 1];
		q += (unsigned int)B->data[byteIdx - 1];
		q += carryBit;
		if(byteIdx == (vbyte)) {
		    q += 1;
		}

		carryBit = ((q > 255) ? 1 : 0);

		I->data[(j * (vbyte)) + byteIdx - 1] = (unsigned char)(q & 255);

		byteIdx--;
	    }
	}

	z += (ubyte);
    }

    A->len = (n / 8);
    
    return SECITEM_DupItem(A);

loser:
    return NULL;
} 

void
PBE_DestroyContext(PBEBitGenContext *pbeCtxt) {
    if(pbeCtxt) {
	PORT_FreeArena(pbeCtxt->arena, PR_TRUE);
    }
}

SECStatus
PBE_PK11ParamToAlgid(SECOidTag algTag, SECItem *param, PRArenaPool *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, &pbeSalt, 
					  (int)pbe_param->ulIteration);
    if(!pbeAlgID) {
	return SECFailure;
    }

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