/*
 * The Initial Developer of the Original Code is International
 * Business Machines Corporation. Portions created by IBM
 * Corporation are Copyright (C) 2005 International Business
 * Machines Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Common Public License as published by
 * IBM Corporation; either version 1 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Common Public License for more details.
 *
 * You should have received a copy of the Common Public License
 * along with this program; if not, a copy can be viewed at
 * http://www.opensource.org/licenses/cpl1.0.php.
 */

/* (C) COPYRIGHT International Business Machines Corp. 2001, 2002, 2005 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "tpmtok_int.h"

static CK_SLOT_INFO    slot_info;

// Function:  dlist_add_as_first()
//
// Adds the specified node to the start of the list
//
// Returns:  pointer to the start of the list
//
DL_NODE *
dlist_add_as_first(DL_NODE *list, void *data)
{
	DL_NODE *node = NULL;

	if (! data)
		return (list);
	node = (DL_NODE *)malloc(sizeof (DL_NODE));
	if (! node)
		return (NULL);
	node->data = data;
	node->prev = NULL;
	node->next = list;
	if (list)
		list->prev = node;

	return (node);
}


// Function:  dlist_add_as_last()
//
// Adds the specified node to the end of the list
//
// Returns:  pointer to the start of the list
//
DL_NODE *
dlist_add_as_last(DL_NODE *list, void *data) {
	DL_NODE *node = NULL;

	if (! data)
		return (list);
	node = (DL_NODE *)malloc(sizeof (DL_NODE));
	if (! node)
		return (NULL);
	node->data = data;
	node->next = NULL;

	if (! list) {
		node->prev = NULL;
		return (node);
	} else {
		DL_NODE *temp = dlist_get_last(list);
		temp->next = node;
		node->prev = temp;

		return (list);
	}
}


// Function:  dlist_find()
//
DL_NODE *
dlist_find(DL_NODE *list, void *data)
{
	DL_NODE *node = list;

	while (node && node->data != data)
	node = node->next;

	return (node);
}


// Function:  dlist_get_first()
//
// Returns the last node in the list or NULL if list is empty
//
DL_NODE *
dlist_get_first(DL_NODE *list) {
	DL_NODE *temp = list;

	if (! list)
		return (NULL);
	while (temp->prev != NULL)
	temp = temp->prev;

	return (temp);
}


// Function:  dlist_get_last()
//
// Returns the last node in the list or NULL if list is empty
//
DL_NODE *
dlist_get_last(DL_NODE *list) {
	DL_NODE *temp = list;

	if (! list)
		return (NULL);
	while (temp->next != NULL)
	temp = temp->next;

	return (temp);
}


//
//
CK_ULONG
dlist_length(DL_NODE *list) {
	DL_NODE  *temp = list;
	CK_ULONG  len  = 0;

	while (temp) {
		len++;
		temp = temp->next;
	}

	return (len);
}


//
//
DL_NODE *
dlist_next(DL_NODE *node)
{
	if (! node)
		return (NULL);
	return (node->next);
}


//
//
DL_NODE *
dlist_prev(DL_NODE *node) {
	if (! node)
		return (NULL);
	return (node->prev);
}


//
//
void
dlist_purge(DL_NODE *list) {
	DL_NODE *node;

	if (! list)
		return;
	do {
		node = list->next;
		free(list);
		list = node;
	} while (list);
}

// Function:  dlist_remove_node()
//
// Attempts to remove the specified node from the list.  The caller is
// responsible for freeing the data associated with the node prior to
// calling this routine
//
DL_NODE *
dlist_remove_node(DL_NODE *list, DL_NODE *node) {
	DL_NODE *temp  = list;

	if (! list || ! node)
		return (NULL);
	// special case:  removing head of the list
	//
	if (list == node) {
		temp = list->next;
		if (temp)
			temp->prev = NULL;

		free(list);
		return (temp);
	}

	// we have no guarantee that the node is in the list
	// so search through the list to find it
	//
	while ((temp != NULL) && (temp->next != node))
	temp = temp->next;

	if (temp != NULL) {
		DL_NODE *next = node->next;

		temp->next = next;
		if (next)
			next->prev = temp;

		free(node);
	}

	return (list);
}

extern void set_perm(int);

void
CreateXProcLock(void *xproc)
{
	pthread_mutexattr_t  mtxattr;

	(void) pthread_mutexattr_init(&mtxattr);
	(void) pthread_mutexattr_setpshared(&mtxattr, PTHREAD_PROCESS_SHARED);
	(void) pthread_mutex_init((pthread_mutex_t *)xproc, &mtxattr);
}

int
DestroyXProcLock(void *xproc)
{
	return (pthread_mutex_destroy((pthread_mutex_t *)xproc));
}

int
XProcLock(void *xproc)
{
	return (pthread_mutex_lock((pthread_mutex_t *)xproc));
}

int
XProcUnLock(void *xproc)
{
	return (pthread_mutex_unlock((pthread_mutex_t *)xproc));
}

//
//
// is_attribute_defined()
//
// determine whether the specified attribute is defined by Cryptoki
//
CK_BBOOL
is_attribute_defined(CK_ATTRIBUTE_TYPE type)
{
	if (type >= CKA_VENDOR_DEFINED)
		return (TRUE);
	switch (type) {
		case  CKA_CLASS:
		case  CKA_TOKEN:
		case  CKA_PRIVATE:
		case  CKA_LABEL:
		case  CKA_APPLICATION:
		case  CKA_VALUE:
		case  CKA_CERTIFICATE_TYPE:
		case  CKA_ISSUER:
		case  CKA_SERIAL_NUMBER:
		case  CKA_KEY_TYPE:
		case  CKA_SUBJECT:
		case  CKA_ID:
		case  CKA_SENSITIVE:
		case  CKA_ENCRYPT:
		case  CKA_DECRYPT:
		case  CKA_WRAP:
		case  CKA_UNWRAP:
		case  CKA_SIGN:
		case  CKA_SIGN_RECOVER:
		case  CKA_VERIFY:
		case  CKA_VERIFY_RECOVER:
		case  CKA_DERIVE:
		case  CKA_START_DATE:
		case  CKA_END_DATE:
		case  CKA_MODULUS:
		case  CKA_MODULUS_BITS:
		case  CKA_PUBLIC_EXPONENT:
		case  CKA_PRIVATE_EXPONENT:
		case  CKA_PRIME_1:
		case  CKA_PRIME_2:
		case  CKA_EXPONENT_1:
		case  CKA_EXPONENT_2:
		case  CKA_COEFFICIENT:
		case  CKA_PRIME:
		case  CKA_SUBPRIME:
		case  CKA_BASE:
		case  CKA_VALUE_BITS:
		case  CKA_VALUE_LEN:
		case  CKA_EXTRACTABLE:
		case  CKA_LOCAL:
		case  CKA_NEVER_EXTRACTABLE:
		case  CKA_ALWAYS_SENSITIVE:
		case  CKA_MODIFIABLE:
		case  CKA_ECDSA_PARAMS:
		case  CKA_EC_POINT:
		case  CKA_HW_FEATURE_TYPE:
		case  CKA_HAS_RESET:
		case  CKA_RESET_ON_INIT:
		case  CKA_KEY_GEN_MECHANISM:
		case  CKA_PRIME_BITS:
		case  CKA_SUBPRIME_BITS:
		case  CKA_OBJECT_ID:
		case  CKA_AC_ISSUER:
		case  CKA_OWNER:
		case  CKA_ATTR_TYPES:
		case  CKA_TRUSTED:
		return (TRUE);
	}

	return (FALSE);
}

void
init_slot_info(TOKEN_DATA *td)
{
	/*
	 * Much of the token info is pulled from the TPM itself when
	 * C_Initialize is called.
	 */
	(void) (void) memset(&slot_info.slotDescription, ' ',
	    sizeof (slot_info.slotDescription) - 1);
	(void) (void) memset(&slot_info.manufacturerID,  ' ',
	    sizeof (slot_info.manufacturerID) - 1);

	(void) (void) memcpy(&slot_info.slotDescription,
	    "PKCS#11 Interface for TPM",
	    strlen("PKCS#11 Interface for TPM"));

	(void) (void) memcpy(&slot_info.manufacturerID,
	    td->token_info.manufacturerID,
	    strlen((char *)td->token_info.manufacturerID));

	slot_info.hardwareVersion = nv_token_data->token_info.hardwareVersion;
	slot_info.firmwareVersion = nv_token_data->token_info.firmwareVersion;
	slot_info.flags = CKF_TOKEN_PRESENT | CKF_HW_SLOT;
}

/*ARGSUSED*/
void
copy_slot_info(CK_SLOT_ID slotID, CK_SLOT_INFO_PTR sinfo)
{
	if (sinfo != NULL)
		(void) memcpy(sinfo, &slot_info, sizeof (slot_info));
}

static void
init_token_info(TOKEN_DATA *td)
{
	CK_TOKEN_INFO    *token_info = NULL;

	token_info = &td->token_info;

	(void) memset(token_info->model, ' ',
	    sizeof (token_info->model));
	(void) memset(token_info->serialNumber, ' ',
	    sizeof (token_info->serialNumber));

	//
	// I don't see any API support for changing the clock so
	// we will use the system clock for the token's clock.
	//
	token_info->flags = CKF_RNG | CKF_LOGIN_REQUIRED | CKF_CLOCK_ON_TOKEN |
	    CKF_SO_PIN_TO_BE_CHANGED;

	if (memcmp(td->user_pin_sha, "00000000000000000000",
	    SHA1_DIGEST_LENGTH) != 0)
		token_info->flags |= CKF_USER_PIN_INITIALIZED;
	else
		token_info->flags |= CKF_USER_PIN_TO_BE_CHANGED;

	// For the release, we made these
	// values as CK_UNAVAILABLE_INFORMATION
	//
	token_info->ulMaxSessionCount    = (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulSessionCount	= (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulMaxRwSessionCount  = (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulRwSessionCount	= (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulMaxPinLen	  = MAX_PIN_LEN;
	token_info->ulMinPinLen	  = MIN_PIN_LEN;
	token_info->ulTotalPublicMemory  = (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulFreePublicMemory   = (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulTotalPrivateMemory = (CK_ULONG)CK_UNAVAILABLE_INFORMATION;
	token_info->ulFreePrivateMemory  = (CK_ULONG)CK_UNAVAILABLE_INFORMATION;

	(void) memset(token_info->utcTime, ' ', sizeof (token_info->utcTime));
}

CK_RV
init_token_data(TSS_HCONTEXT hContext, TOKEN_DATA *td) {
	CK_RV rc;

	(void) memset((char *)td, 0, sizeof (nv_token_data));
	//
	// the normal USER pin is not set when the token is initialized
	//
	(void) memcpy(td->user_pin_sha, "00000000000000000000",
	    SHA1_DIGEST_LENGTH);
	(void) memcpy(td->so_pin_sha, default_so_pin_sha,
	    SHA1_DIGEST_LENGTH);

	(void) memset(user_pin_md5, 0x0, MD5_DIGEST_LENGTH);
	(void) memcpy(so_pin_md5, default_so_pin_md5, MD5_DIGEST_LENGTH);

	(void) memcpy(td->next_token_object_name, "00000000", 8);

	td->tweak_vector.allow_key_mods   = TRUE;

	init_token_info(td);

	rc = token_get_tpm_info(hContext, td);
	if (rc != CKR_OK)
		return (rc);

	rc = save_token_data(td);

	return (rc);
}

// Function:  compute_next_token_obj_name()
//
// Given a token object name (8 bytes in the range [0 - 9A - Z])
// increment by one adjusting as necessary
//
// This gives us a namespace of 36^8 = 2, 821, 109, 907, 456
// objects before wrapping around.
//
CK_RV
compute_next_token_obj_name(CK_BYTE *current, CK_BYTE *next) {
	int val[8];
	int i;

	if (! current || ! next) {
		return (CKR_FUNCTION_FAILED);
	}
	// Convert to integral base 36
	//
	for (i = 0; i < 8; i++) {
		if (current[i] >= '0' && current[i] <= '9')
			val[i] = current[i] - '0';

		if (current[i] >= 'A' && current[i] <= 'Z')
			val[i] = current[i] - 'A' + 10;
	}

	val[0]++;

	i = 0;

	while (val[i] > 35) {
		val[i] = 0;

		if (i + 1 < 8) {
			val[i + 1]++;
			i++;
		} else {
			val[0]++;
			i = 0;   // start pass 2
		}
	}

	// now, convert back to [0 - 9A - Z]
	//
	for (i = 0; i < 8; i++) {
		if (val[i] < 10)
			next[i] = '0' + val[i];
		else
			next[i] = 'A' + val[i] - 10;
	}

	return (CKR_OK);
}


//
//
CK_RV
build_attribute(CK_ATTRIBUTE_TYPE  type,
	CK_BYTE	   *data,
	CK_ULONG	   data_len,
	CK_ATTRIBUTE	**attrib) {
	CK_ATTRIBUTE *attr = NULL;

	attr = (CK_ATTRIBUTE *)malloc(sizeof (CK_ATTRIBUTE) + data_len);
	if (! attr) {
		return (CKR_DEVICE_MEMORY);
	}
	attr->type  = type;
	attr->ulValueLen = data_len;

	if (data_len > 0) {
		attr->pValue = (CK_BYTE *)attr + sizeof (CK_ATTRIBUTE);
		(void) memcpy(attr->pValue, data, data_len);
	}
	else
		attr->pValue = NULL;

	 *attrib = attr;

	return (CKR_OK);
}

CK_RV
add_pkcs_padding(CK_BYTE  * ptr,
	UINT32   block_size,
	UINT32   data_len,
	UINT32   total_len)
{
	UINT32 i, pad_len;
	CK_BYTE  pad_value;

	pad_len = block_size - (data_len % block_size);
	pad_value = (CK_BYTE)pad_len;

	if (data_len + pad_len > total_len) {
		return (CKR_FUNCTION_FAILED);
	}
	for (i = 0; i < pad_len; i++)
		ptr[i] = pad_value;

	return (CKR_OK);
}

CK_RV
strip_pkcs_padding(
	CK_BYTE *ptr,
	UINT32  total_len,
	UINT32  *data_len)
{
	CK_BYTE  pad_value;

	pad_value = ptr[total_len - 1];

	/* We have 'pad_value' bytes of 'pad_value' appended to the end */
	*data_len = total_len - pad_value;

	return (CKR_OK);
}

CK_RV
remove_leading_zeros(CK_ATTRIBUTE *attr)
{
	CK_BYTE   *ptr = NULL;
	CK_ULONG   new_len, i;

	ptr = attr->pValue;

	for (i = 0; i < attr->ulValueLen; i++) {
		if (ptr[i] != 0x0)
			break;
	}

	new_len = attr->ulValueLen - i;

	(void) memcpy(ptr, ptr + i, new_len);
	attr->ulValueLen = new_len;

	return (CKR_OK);
}

CK_RV
parity_is_odd(CK_BYTE b) {
	b = ((b >> 4) ^ b) & 0x0f;
	b = ((b >> 2) ^ b) & 0x03;
	b = ((b >> 1) ^ b) & 0x01;

	if (b == 1)
		return (TRUE);
	else
		return (FALSE);
}

CK_RV
attach_shm() {
	if (global_shm != NULL)
		return (CKR_OK);

	global_shm = (LW_SHM_TYPE *)calloc(1, sizeof (LW_SHM_TYPE));
	if (global_shm == NULL) {
		return (CKR_HOST_MEMORY);
	}
	CreateXProcLock(&global_shm->mutex);

	xproclock = (void *)&global_shm->mutex;

	return (CKR_OK);
}

CK_RV
detach_shm()
{
	if (global_shm != NULL) {
		free(global_shm);
		global_shm = NULL;
	}

	return (CKR_OK);
}

CK_RV
compute_sha(CK_BYTE  *data,
	CK_ULONG_32   len,
	CK_BYTE  * hash)
{
	SHA1_CTX	ctx;

	SHA1Init(&ctx);

	SHA1Update(&ctx, data, len);

	SHA1Final(hash, &ctx);
	return (CKR_OK);
}