/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Key Management Functions
 * (as defined in PKCS#11 spec section 11.14)
 */

#include "metaGlobal.h"


/*
 * meta_GenerateKey
 *
 */
CK_RV
meta_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
    CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, CK_OBJECT_HANDLE_PTR phKey)
{
	CK_RV rv;
	meta_session_t *session;
	meta_object_t *key = NULL;

	if (pMechanism == NULL || phKey == NULL)
		return (CKR_ARGUMENTS_BAD);

	rv = meta_handle2session(hSession, &session);
	if (rv != CKR_OK)
		return (rv);


	rv = meta_object_alloc(session, &key);
	if (rv != CKR_OK)
		goto finish;

	rv = meta_generate_keys(session, pMechanism, pTemplate, ulCount, key,
	    NULL, 0, NULL);
	if (rv != CKR_OK)
		goto finish;

	meta_object_activate(key);

	*phKey = (CK_OBJECT_HANDLE) key;

finish:
	if (rv != CKR_OK) {
		if (key)
			(void) meta_object_dealloc(session, key, B_TRUE);
	}

	REFRELEASE(session);

	return (rv);
}


/*
 * meta_GenerateKeyPair
 *
 */
CK_RV
meta_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
    CK_ATTRIBUTE_PTR pPublicKeyTemplate, CK_ULONG ulPublicKeyAttributeCount,
    CK_ATTRIBUTE_PTR pPrivateKeyTemplate, CK_ULONG ulPrivateKeyAttributeCount,
    CK_OBJECT_HANDLE_PTR phPublicKey, CK_OBJECT_HANDLE_PTR phPrivateKey)
{
	CK_RV rv;
	meta_session_t *session;
	meta_object_t *key1 = NULL, *key2 = NULL;

	if (pMechanism == NULL || phPublicKey == NULL || phPrivateKey == NULL)
		return (CKR_ARGUMENTS_BAD);

	rv = meta_handle2session(hSession, &session);
	if (rv != CKR_OK)
		return (rv);


	rv = meta_object_alloc(session, &key1);
	if (rv != CKR_OK)
		goto finish;

	rv = meta_object_alloc(session, &key2);
	if (rv != CKR_OK)
		goto finish;

	rv = meta_generate_keys(session, pMechanism,
	    pPublicKeyTemplate, ulPublicKeyAttributeCount, key1,
	    pPrivateKeyTemplate, ulPrivateKeyAttributeCount, key2);
	if (rv != CKR_OK)
		goto finish;

	meta_object_activate(key1);
	meta_object_activate(key2);

	*phPublicKey = (CK_OBJECT_HANDLE) key1;
	*phPrivateKey = (CK_OBJECT_HANDLE) key2;

finish:
	if (rv != CKR_OK) {
		if (key1)
			(void) meta_object_dealloc(session, key1, B_TRUE);
		if (key2)
			(void) meta_object_dealloc(session, key2, B_TRUE);
	}

	REFRELEASE(session);

	return (rv);
}


/*
 * meta_WrapKey
 *
 */
CK_RV
meta_WrapKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
    CK_OBJECT_HANDLE hWrappingKey, CK_OBJECT_HANDLE hKey,
    CK_BYTE_PTR pWrappedKey, CK_ULONG_PTR pulWrappedKeyLen)
{
	CK_RV rv;
	meta_session_t *session;
	meta_object_t *wrappingKey, *inputKey;

	if (pMechanism == NULL || pulWrappedKeyLen == NULL)
		return (CKR_ARGUMENTS_BAD);

	rv = meta_handle2session(hSession, &session);
	if (rv != CKR_OK)
		return (rv);

	rv = meta_handle2object(hKey, &inputKey);
	if (rv != CKR_OK) {
		REFRELEASE(session);
		return (rv);
	}

	rv = meta_handle2object(hWrappingKey, &wrappingKey);
	if (rv != CKR_OK) {
		OBJRELEASE(inputKey);
		REFRELEASE(session);
		return (rv);
	}

	rv = meta_wrap_key(session, pMechanism, wrappingKey,
	    inputKey, pWrappedKey, pulWrappedKeyLen);

finish:
	OBJRELEASE(inputKey);
	OBJRELEASE(wrappingKey);
	REFRELEASE(session);

	return (rv);
}


/*
 * meta_UnwrapKey
 *
 */
CK_RV
meta_UnwrapKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
    CK_OBJECT_HANDLE hUnwrappingKey, CK_BYTE_PTR pWrappedKey,
    CK_ULONG ulWrappedKeyLen, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey)
{
	CK_RV rv;
	meta_session_t *session;
	meta_object_t *unwrappingKey, *outputKey = NULL;

	if (pMechanism == NULL || pWrappedKey == NULL || phKey == NULL)
		return (CKR_ARGUMENTS_BAD);

	rv = meta_handle2session(hSession, &session);
	if (rv != CKR_OK)
		return (rv);

	rv = meta_handle2object(hUnwrappingKey, &unwrappingKey);
	if (rv != CKR_OK) {
		REFRELEASE(session);
		return (rv);
	}

	rv = meta_object_alloc(session, &outputKey);
	if (rv != CKR_OK)
		goto finish;

	(void) get_template_boolean(CKA_TOKEN, pTemplate, ulAttributeCount,
	    &(outputKey->isToken));

	rv = meta_unwrap_key(session, pMechanism, unwrappingKey,
	    pWrappedKey, ulWrappedKeyLen,
	    pTemplate, ulAttributeCount, outputKey);
	if (rv != CKR_OK)
		goto finish;

	meta_object_activate(outputKey);

	*phKey = (CK_OBJECT_HANDLE) outputKey;

finish:
	if (rv != CKR_OK) {
		if (outputKey)
			(void) meta_object_dealloc(session, outputKey, B_TRUE);
	}

	OBJRELEASE(unwrappingKey);
	REFRELEASE(session);

	return (rv);
}


/*
 * meta_DeriveKey
 *
 * This function is a bit gross because of PKCS#11 kludges that pass extra
 * object handles in some mechanism parameters. It probably needs to be
 * broken up into more managable pieces.
 */
CK_RV
meta_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
    CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey)
{
	CK_RV rv;
	CK_MECHANISM *pMech = pMechanism;
	meta_session_t *session;
	meta_object_t *basekey1 = NULL, *basekey2 = NULL;
	meta_object_t *newKey1 = NULL, *newKey2 = NULL, *newKey3 = NULL,
	    *newKey4 = NULL;
	boolean_t ssl_keys = B_FALSE;
	boolean_t tlsprf = B_FALSE;

	CK_MECHANISM metaMech;
	CK_OBJECT_HANDLE *phBaseKey2 = NULL;
	CK_X9_42_DH2_DERIVE_PARAMS x942_params, *x9_tmpptr;
	CK_ECDH2_DERIVE_PARAMS ecdh_params, *ec_tmpptr;
	CK_SSL3_KEY_MAT_OUT *ssl_key_mat;
	CK_SSL3_KEY_MAT_PARAMS *keyparams;

	if (pMech == NULL) {
		return (CKR_ARGUMENTS_BAD);
	}

	/*
	 * Special case: Normally, the caller must always provide storage
	 * for the derived key handle at phKey. Two (related) mechanisms
	 * are special, in that multiple keys are instead returned via
	 * pMech->pParameter. In these cases the spec says (see 12.38.4
	 * and 12.39.4) that phKey should be a NULL pointer, as it is not used.
	 */
	switch (pMech->mechanism) {
	case CKM_SSL3_KEY_AND_MAC_DERIVE:
	case CKM_TLS_KEY_AND_MAC_DERIVE:
		keyparams = (CK_SSL3_KEY_MAT_PARAMS*)pMech->pParameter;

		if ((keyparams == NULL) || (pMech->ulParameterLen
		    != sizeof (CK_SSL3_KEY_MAT_PARAMS)))
			return (CKR_ARGUMENTS_BAD);

		ssl_key_mat = keyparams->pReturnedKeyMaterial;
		if (ssl_key_mat == NULL)
			return (CKR_ARGUMENTS_BAD);

		ssl_keys = B_TRUE;
		break;

	case CKM_TLS_PRF:
		tlsprf = B_TRUE;
		break;

	default:
		if (phKey == NULL)
			return (CKR_ARGUMENTS_BAD);
	};

	rv = meta_handle2session(hSession, &session);
	if (rv != CKR_OK)
		return (rv);

	rv = meta_handle2object(hBaseKey, &basekey1);
	if (rv != CKR_OK)
		goto finish;


	/*
	 * A few oddball mechanisms pass a 2nd object handle in the parameters.
	 * Here we validate that handle, and create a duplicate copy of the
	 * mechanism and parameters. This is done because the application
	 * does not expect these values to be changing, and could be using the
	 * same data in multiple threads (eg concurrent calls to C_DeriveKey).
	 * We copy the data to make sure there are no MT-Safe problems.
	 */
	switch (pMech->mechanism) {

	case CKM_ECMQV_DERIVE:
		/* uses CK_ECDH2_DERIVE_PARAMS struct as the parameter */

		if ((pMech->pParameter == NULL) || (pMech->ulParameterLen
		    != sizeof (CK_ECDH2_DERIVE_PARAMS))) {
			rv = CKR_ARGUMENTS_BAD;
			goto finish;
		}

		/* Duplicate the mechanism and paramaters */
		ec_tmpptr = (CK_ECDH2_DERIVE_PARAMS *)pMech->pParameter;
		ecdh_params = *ec_tmpptr;
		metaMech = *pMech;
		metaMech.pParameter = &ecdh_params;
		pMech = &metaMech;

		/* Get the key the application is providing */
		phBaseKey2 = &ecdh_params.hPrivateData;
		break;

	case CKM_X9_42_DH_HYBRID_DERIVE:
	case CKM_X9_42_MQV_DERIVE:
		/* both use CK_X9_42_DH2_DERIVE_PARAMS as the parameter */

		if ((pMech->pParameter == NULL) || (pMech->ulParameterLen
		    != sizeof (CK_X9_42_DH2_DERIVE_PARAMS))) {
			rv = CKR_ARGUMENTS_BAD;
			goto finish;
		}

		/* Duplicate the mechanism and paramaters */
		x9_tmpptr = (CK_X9_42_DH2_DERIVE_PARAMS *)pMech->pParameter;
		x942_params = *x9_tmpptr;
		metaMech = *pMech;
		metaMech.pParameter  = &x942_params;
		pMech = &metaMech;

		/* Get the key the application is providing */
		phBaseKey2 = &x942_params.hPrivateData;
		break;

	case CKM_CONCATENATE_BASE_AND_KEY:
		/* uses a CK_OBJECT_HANDLE as the parameter */

		if ((pMech->pParameter == NULL) || (pMech->ulParameterLen
		    != sizeof (CK_OBJECT_HANDLE))) {
			rv = CKR_ARGUMENTS_BAD;
			goto finish;
		}

		/* Duplicate the mechanism and paramaters */
		metaMech = *pMech;
		pMech = &metaMech;

		/* Get the key the application is providing */
		phBaseKey2 = (CK_OBJECT_HANDLE *) &metaMech.pParameter;
		break;

	default:
		/* nothing special to do. */
		break;
	}

	if (phBaseKey2) {
		rv = meta_handle2object(*phBaseKey2, &basekey2);
		if (rv != CKR_OK)
			goto finish;
	}

	/*
	 * Allocate meta objects to store the derived key(s). Normally just
	 * a single key is created, but the SSL/TLS mechanisms generate four.
	 */
	rv = meta_object_alloc(session, &newKey1);
	if (rv != CKR_OK)
		goto finish;

	if (ssl_keys) {
		rv = meta_object_alloc(session, &newKey2);
		if (rv != CKR_OK)
			goto finish;
		rv = meta_object_alloc(session, &newKey3);
		if (rv != CKR_OK)
			goto finish;
		rv = meta_object_alloc(session, &newKey4);
		if (rv != CKR_OK)
			goto finish;
	}


	/* Perform the actual key derive operation. */
	rv = meta_derive_key(session, pMech, basekey1, basekey2, phBaseKey2,
	    pTemplate, ulAttributeCount, newKey1, newKey2, newKey3, newKey4);
	if (rv != CKR_OK)
		goto finish;

	if (tlsprf) {
		(void) meta_object_dealloc(session, newKey1, B_TRUE);
		newKey1 = NULL;
		/* phKey isn't used (is NULL) for mechanism CKM_TLS_PRF. */

	} else {
		/* Make derived key(s) active and visible to other threads. */
		meta_object_activate(newKey1);
		if (ssl_keys) {
			meta_object_activate(newKey2);
			meta_object_activate(newKey3);
			meta_object_activate(newKey4);

			ssl_key_mat->hClientMacSecret
			    = (CK_OBJECT_HANDLE) newKey1;
			ssl_key_mat->hServerMacSecret
			    = (CK_OBJECT_HANDLE) newKey2;
			ssl_key_mat->hClientKey = (CK_OBJECT_HANDLE) newKey3;
			ssl_key_mat->hServerKey = (CK_OBJECT_HANDLE) newKey4;
			/* phKey isn't used (is NULL) for these SSL/TLS mechs */

		} else {
			*phKey = (CK_OBJECT_HANDLE) newKey1;
		}
	}

finish:
	if (rv != CKR_OK) {
		if (newKey1)
			(void) meta_object_dealloc(session, newKey1, B_TRUE);
		if (newKey2)
			(void) meta_object_dealloc(session, newKey2, B_TRUE);
		if (newKey3)
			(void) meta_object_dealloc(session, newKey3, B_TRUE);
		if (newKey4)
			(void) meta_object_dealloc(session, newKey4, B_TRUE);
	}

	if (basekey1)
		OBJRELEASE(basekey1);
	if (basekey2)
		OBJRELEASE(basekey2);
	REFRELEASE(session);

	return (rv);
}