/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <security/cryptoki.h>
#include <bignum.h>
#include <des_impl.h>
#include "softGlobal.h"
#include "softSession.h"
#include "softObject.h"
#include "softDH.h"
#include "softRandom.h"
#include "softCrypt.h"


/*
 * This function converts the big integer of the specified attribute
 * to an octet string and store it in the corresponding key object.
 */
CK_RV
soft_genDHkey_set_attribute(soft_object_t *key, BIGNUM *bn,
    CK_ATTRIBUTE_TYPE type, uint32_t prime_len, boolean_t public)
{

	uchar_t	*buf;
	uint32_t buflen;
	CK_RV rv = CKR_OK;
	biginteger_t *dst = NULL;
	biginteger_t src;

	/*
	 * Allocate the buffer used to store the value of key fields
	 * for bignum2bytestring. Since bignum only deals with a buffer
	 * whose size is multiple of 4, prime_len is rounded up to be
	 * multiple of 4.
	 */
	if ((buf = malloc((prime_len + 3) & ~3)) == NULL) {
		rv = CKR_HOST_MEMORY;
		goto cleanexit;
	}

	buflen = bn->len * (int)sizeof (uint32_t);
	bignum2bytestring(buf, bn, buflen);

	switch (type) {

	case CKA_VALUE:
		if (public)
			dst = OBJ_PUB_DH_VALUE(key);
		else
			dst = OBJ_PRI_DH_VALUE(key);
		break;

	case CKA_PRIME:
		dst = OBJ_PRI_DH_PRIME(key);
		break;

	case CKA_BASE:
		dst = OBJ_PRI_DH_BASE(key);
		break;
	}

	src.big_value_len = buflen;

	if ((src.big_value = malloc(buflen)) == NULL) {
		rv = CKR_HOST_MEMORY;
		goto cleanexit;
	}
	(void) memcpy(src.big_value, buf, buflen);

	/* Copy the attribute in the key object. */
	copy_bigint_attr(&src, dst);

cleanexit:
	free(buf);
	return (rv);

}

/*
 * This function covers the DH Key agreement.
 */
CK_RV
soft_dh_genkey_pair(soft_object_t *pubkey, soft_object_t *prikey)
{
	CK_RV		rv;
	BIG_ERR_CODE	brv;
	uchar_t		prime[MAX_KEY_ATTR_BUFLEN];
	uint32_t	prime_len = sizeof (prime);
	uint32_t	primebit_len;
	uint32_t	value_bits;
	uchar_t		base[MAX_KEY_ATTR_BUFLEN];
	uint32_t	base_len = sizeof (base);
	BIGNUM		bnprime;
	BIGNUM		bnbase;
	BIGNUM		bnprival;
	BIGNUM		bnpubval;
	CK_ATTRIBUTE 	template;


	if ((pubkey->class != CKO_PUBLIC_KEY) ||
	    (pubkey->key_type != CKK_DH))
		return (CKR_KEY_TYPE_INCONSISTENT);

	if ((prikey->class != CKO_PRIVATE_KEY) ||
	    (prikey->key_type != CKK_DH))
		return (CKR_KEY_TYPE_INCONSISTENT);

	/*
	 * The input to the first phase shall be the Diffie-Hellman
	 * parameters, which include prime, base, and private-value length.
	 */
	rv = soft_get_public_attr(pubkey, CKA_PRIME, prime, &prime_len);

	if (rv != CKR_OK) {
		return (rv);
	}

	if ((prime_len < (MIN_DH_KEYLENGTH / 8)) ||
	    (prime_len > (MAX_DH_KEYLENGTH / 8))) {
		rv = CKR_ATTRIBUTE_VALUE_INVALID;
		goto ret0;
	}

	if ((brv = big_init(&bnprime, (prime_len + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret0;
	}

	/* Convert the prime octet string to big integer format. */
	bytestring2bignum(&bnprime, prime, prime_len);

	rv = soft_get_public_attr(pubkey, CKA_BASE, base, &base_len);

	if (rv != CKR_OK) {
		goto ret1;
	}

	if ((brv = big_init(&bnbase, (base_len + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret1;
	}

	/* Convert the base octet string to big integer format. */
	bytestring2bignum(&bnbase, base, base_len);

	if (big_cmp_abs(&bnbase, &bnprime) >= 0) {
		rv = CKR_ATTRIBUTE_VALUE_INVALID;
		goto ret2;
	}

	primebit_len = big_bitlength(&bnprime);

	template.pValue = malloc(sizeof (CK_ULONG));

	if (template.pValue == NULL) {
		rv = CKR_HOST_MEMORY;
		goto ret2;
	}

	template.ulValueLen = sizeof (CK_ULONG);

	rv = get_ulong_attr_from_object(OBJ_PRI_DH_VAL_BITS(prikey),
	    &template);

	if (rv != CKR_OK) {
		goto ret2;
	}

	/*
	 * The intention of selecting a private-value length is to reduce
	 * the computation time for key agreement, while maintaining a
	 * given level of security.
	 */

#ifdef	__sparcv9
	/* LINTED */
	value_bits = (uint32_t)(*((CK_ULONG *)(template.pValue)));
#else	/* !__sparcv9 */
	value_bits = *((CK_ULONG *)(template.pValue));
#endif	/* __sparcv9 */

	if (value_bits > primebit_len) {
		rv = CKR_ATTRIBUTE_VALUE_INVALID;
		goto ret3;
	}

	/* Generate DH key pair private and public values. */
	if ((brv = big_init(&bnprival, (prime_len + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret3;
	}

	if ((brv = big_init(&bnpubval, (prime_len + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret4;
	}

	/*
	 * The big integer of the private value shall be generated privately
	 * and randomly.
	 */
	if ((brv = random_bignum(&bnprival, (value_bits == 0) ?
	    primebit_len : value_bits, (IS_TOKEN_OBJECT(pubkey) ||
	    IS_TOKEN_OBJECT(prikey)))) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret5;
	}

	/*
	 * The base g shall be raised to the private value x modulo p to
	 * give an integer y, the integer public value.
	 */
	if ((brv = big_modexp(&bnpubval,
	    &bnbase, &bnprival, &bnprime, NULL)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret5;
	}

	/*
	 * The integer public value y shall be converted to an octet
	 * string PV of length k, the public value.
	 */
	if ((rv = soft_genDHkey_set_attribute(pubkey, &bnpubval,
	    CKA_VALUE, prime_len, B_TRUE)) != CKR_OK) {
		goto ret5;
	}

	/* Convert the big integer private value to an octet string. */
	if ((rv = soft_genDHkey_set_attribute(prikey, &bnprival,
	    CKA_VALUE, prime_len, B_FALSE)) != CKR_OK) {
		goto ret5;
	}

	/* Convert the big integer prime to an octet string. */
	if ((rv = soft_genDHkey_set_attribute(prikey, &bnprime,
	    CKA_PRIME, prime_len, B_FALSE)) != CKR_OK) {
		goto ret5;
	}

	/* Convert the big integer base to an octet string. */
	if ((rv = soft_genDHkey_set_attribute(prikey, &bnbase,
	    CKA_BASE, prime_len, B_FALSE)) != CKR_OK) {
		goto ret5;
	}

	if (value_bits == 0) {
		OBJ_PRI_DH_VAL_BITS(prikey) = primebit_len;
	}


ret5:
	big_finish(&bnpubval);
ret4:
	big_finish(&bnprival);
ret3:
	free(template.pValue);
ret2:
	big_finish(&bnbase);
ret1:
	big_finish(&bnprime);
ret0:
	return (rv);
}

CK_RV
soft_dh_key_derive(soft_object_t *basekey, soft_object_t *secretkey,
    void *publicvalue, size_t publicvaluelen)
{
	uchar_t		privatevalue[MAX_KEY_ATTR_BUFLEN];
	uint32_t	privatevaluelen = sizeof (privatevalue);
	uchar_t		privateprime[MAX_KEY_ATTR_BUFLEN];
	uint32_t	privateprimelen = sizeof (privateprime);
	uchar_t		*value;
	uint32_t	valuelen;
	uint32_t	keylen;
	uchar_t		*buf = NULL;
	CK_RV		rv;
	BIG_ERR_CODE	brv;
	BIGNUM		bnprime;
	BIGNUM		bnpublic;
	BIGNUM		bnprivate;
	BIGNUM		bnsecret;


	rv = soft_get_private_attr(basekey, CKA_VALUE, privatevalue,
	    &privatevaluelen);
	if (rv != CKR_OK) {
		return (rv);
	}

	rv = soft_get_private_attr(basekey, CKA_PRIME, privateprime,
	    &privateprimelen);
	if (rv != CKR_OK) {
		goto ret0;
	}

	if ((brv = big_init(&bnprime, (privateprimelen + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret0;
	}

	bytestring2bignum(&bnprime, privateprime, privateprimelen);

	if ((brv = big_init(&bnprivate, (privatevaluelen + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret1;
	}

	bytestring2bignum(&bnprivate, privatevalue, privatevaluelen);

#ifdef	__sparcv9
	/* LINTED */
	if ((brv = big_init(&bnpublic, (int)(publicvaluelen + 3)/4)) !=
	    BIG_OK) {
#else	/* !__sparcv9 */
	if ((brv = big_init(&bnpublic, (publicvaluelen + 3)/4)) != BIG_OK) {
#endif	/* __sparcv9 */
		rv = convert_rv(brv);
		goto ret2;
	}

	bytestring2bignum(&bnpublic, (uchar_t *)publicvalue, publicvaluelen);

	if ((brv = big_init(&bnsecret, (privateprimelen + 3)/4)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret3;
	}

	if ((brv = big_modexp(&bnsecret, &bnpublic, &bnprivate, &bnprime,
	    NULL)) != BIG_OK) {
		rv = convert_rv(brv);
		goto ret4;
	}

	if ((buf = malloc((privateprimelen + 3) & ~3)) == NULL) {
		rv = CKR_HOST_MEMORY;
		goto ret4;
	}

	value = buf;
	valuelen = bnsecret.len * (int)sizeof (uint32_t);
	bignum2bytestring(value, &bnsecret, valuelen);

	switch (secretkey->key_type) {

	case CKK_DES:
		keylen = DES_KEYSIZE;
		break;
	case CKK_DES2:
		keylen = DES2_KEYSIZE;
		break;
	case CKK_DES3:
		keylen = DES3_KEYSIZE;
		break;
	case CKK_RC4:
	case CKK_AES:
	case CKK_GENERIC_SECRET:
#ifdef	__sparcv9
		/* LINTED */
		keylen = (uint32_t)OBJ_SEC_VALUE_LEN(secretkey);
#else	/* !__sparcv9 */
		keylen = OBJ_SEC_VALUE_LEN(secretkey);
#endif	/* __sparcv9 */
		break;
	}

	if (keylen == 0) {
		/*
		 * keylen == 0 only if CKA_VALUE_LEN did not specify.
		 */
		keylen = valuelen;
	}
	/*
	 * Note: No need to have "default:" case here since invalid key type
	 * if any has been detected at function soft_build_secret_key_object()
	 * before it gets here.
	 */

	if (keylen > valuelen) {
		rv = CKR_ATTRIBUTE_VALUE_INVALID;
		goto ret5;
	}

	if ((OBJ_SEC_VALUE(secretkey) = malloc(keylen)) == NULL) {
		rv = CKR_HOST_MEMORY;
		goto ret5;
	}
	OBJ_SEC_VALUE_LEN(secretkey) = keylen;

	/*
	 * The truncation removes bytes from the leading end of the
	 * secret value.
	 */
	(void) memcpy(OBJ_SEC_VALUE(secretkey), (value + valuelen - keylen),
	    keylen);

ret5:
	free(buf);
ret4:
	big_finish(&bnsecret);
ret3:
	big_finish(&bnpublic);
ret2:
	big_finish(&bnprivate);
ret1:
	big_finish(&bnprime);
ret0:
	return (rv);
}