/*
 * 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.
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2012 Milan Jurik. All rights reserved.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 * Copyright 2018 Jason King
 */

#include <pthread.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <uuid/uuid.h>
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
#include <syslog.h>

#include <sys/crypto/common.h>	/* For CRYPTO_BYTES2BITS */
#include <rsa_impl.h>
#include <padding.h>

#include <tss/platform.h>
#include <tss/tss_defines.h>
#include <tss/tss_typedef.h>
#include <tss/tss_structs.h>
#include <tss/tss_error.h>
#include <tss/tcs_error.h>
#include <tss/tspi.h>
#include <trousers/trousers.h>

#include "tpmtok_int.h"
#include "tpmtok_defs.h"

#define	MAX_RSA_KEYLENGTH 512

extern void stlogit(char *fmt, ...);

CK_RV token_rng(TSS_HCONTEXT, CK_BYTE *,  CK_ULONG);
int tok_slot2local(CK_SLOT_ID);
CK_RV token_specific_session(CK_SLOT_ID);
CK_RV token_specific_final(TSS_HCONTEXT);

CK_RV
token_specific_rsa_decrypt(
	TSS_HCONTEXT,
	CK_BYTE *,
	CK_ULONG,
	CK_BYTE *,
	CK_ULONG *,
	OBJECT *);

CK_RV
token_specific_rsa_encrypt(
	TSS_HCONTEXT,
	CK_BYTE *,
	CK_ULONG,
	CK_BYTE *,
	CK_ULONG *,
	OBJECT *);

CK_RV
token_specific_rsa_sign(
	TSS_HCONTEXT,
	CK_BYTE *,
	CK_ULONG,
	CK_BYTE *,
	CK_ULONG *,
	OBJECT *);

CK_RV
token_specific_rsa_verify(TSS_HCONTEXT, CK_BYTE *,
    CK_ULONG, CK_BYTE *, CK_ULONG, OBJECT *);

CK_RV
token_specific_rsa_generate_keypair(TSS_HCONTEXT,
	TEMPLATE *,
	TEMPLATE *);

CK_RV
token_specific_sha_init(DIGEST_CONTEXT *);

CK_RV
token_specific_sha_update(DIGEST_CONTEXT *,
	CK_BYTE *,
	CK_ULONG);

CK_RV
token_specific_sha_final(DIGEST_CONTEXT *,
	CK_BYTE *,
	CK_ULONG *);

CK_RV token_specific_login(TSS_HCONTEXT, CK_USER_TYPE, CK_CHAR_PTR, CK_ULONG);
CK_RV token_specific_logout(TSS_HCONTEXT);
CK_RV token_specific_init_pin(TSS_HCONTEXT, CK_CHAR_PTR, CK_ULONG);
CK_RV token_specific_set_pin(ST_SESSION_HANDLE, CK_CHAR_PTR,
	CK_ULONG, CK_CHAR_PTR, CK_ULONG);
CK_RV token_specific_verify_so_pin(TSS_HCONTEXT, CK_CHAR_PTR, CK_ULONG);

static CK_RV
token_specific_init(char *, CK_SLOT_ID, TSS_HCONTEXT *);

struct token_specific_struct token_specific = {
	"TPM_Debug",
	&token_specific_init,
	NULL,
	&token_rng,
	&token_specific_session,
	&token_specific_final,
	&token_specific_rsa_decrypt,
	&token_specific_rsa_encrypt,
	&token_specific_rsa_sign,
	&token_specific_rsa_verify,
	&token_specific_rsa_generate_keypair,
	NULL,
	NULL,
	NULL,
	&token_specific_login,
	&token_specific_logout,
	&token_specific_init_pin,
	&token_specific_set_pin,
	&token_specific_verify_so_pin
};

/* The context we'll use globally to connect to the TSP */

/* TSP key handles */
TSS_HKEY hPublicRootKey = NULL_HKEY;
TSS_HKEY hPublicLeafKey = NULL_HKEY;
TSS_HKEY hPrivateRootKey = NULL_HKEY;
TSS_HKEY hPrivateLeafKey = NULL_HKEY;

TSS_UUID publicRootKeyUUID;
TSS_UUID publicLeafKeyUUID;
TSS_UUID privateRootKeyUUID;
TSS_UUID privateLeafKeyUUID;

/* TSP policy handles */
TSS_HPOLICY hDefaultPolicy = NULL_HPOLICY;

/* PKCS#11 key handles */
int not_initialized = 0;

CK_BYTE current_user_pin_sha[SHA1_DIGEST_LENGTH];
CK_BYTE current_so_pin_sha[SHA1_DIGEST_LENGTH];

static TPM_CAP_VERSION_INFO tpmvinfo;

static CK_RV
verify_user_pin(TSS_HCONTEXT, CK_BYTE *);

static TSS_RESULT
tss_assign_secret_key_policy(TSS_HCONTEXT, TSS_FLAG, TSS_HKEY, CK_CHAR *);

static TSS_RESULT
set_legacy_key_params(TSS_HKEY);

static void
local_uuid_clear(TSS_UUID *uuid)
{
	if (uuid == NULL)
		return;
	(void) memset(uuid, 0, sizeof (TSS_UUID));
}


/* convert from TSS_UUID to uuid_t */
static void
tss_uuid_convert_from(TSS_UUID *uu, uuid_t ptr)
{
	uint_t		tmp;
	uchar_t		*out = ptr;

	tmp = ntohl(uu->ulTimeLow);
	out[3] = (uchar_t)tmp;
	tmp >>= 8;
	out[2] = (uchar_t)tmp;
	tmp >>= 8;
	out[1] = (uchar_t)tmp;
	tmp >>= 8;
	out[0] = (uchar_t)tmp;

	tmp = ntohs(uu->usTimeMid);
	out[5] = (uchar_t)tmp;
	tmp >>= 8;
	out[4] = (uchar_t)tmp;

	tmp = ntohs(uu->usTimeHigh);
	out[7] = (uchar_t)tmp;
	tmp >>= 8;
	out[6] = (uchar_t)tmp;

	tmp = uu->bClockSeqHigh;
	out[8] = (uchar_t)tmp;
	tmp = uu->bClockSeqLow;
	out[9] = (uchar_t)tmp;

	(void) memcpy(out+10, uu->rgbNode, 6);
}

/* convert from uuid_t to TSS_UUID */
static void
tss_uuid_convert_to(TSS_UUID *uuid, uuid_t in)
{
	uchar_t		*ptr;
	uint32_t	ltmp;
	uint16_t	stmp;

	ptr = in;

	ltmp = *ptr++;
	ltmp = (ltmp << 8) | *ptr++;
	ltmp = (ltmp << 8) | *ptr++;
	ltmp = (ltmp << 8) | *ptr++;
	uuid->ulTimeLow = ntohl(ltmp);

	stmp = *ptr++;
	stmp = (stmp << 8) | *ptr++;
	uuid->usTimeMid = ntohs(stmp);

	stmp = *ptr++;
	stmp = (stmp << 8) | *ptr++;
	uuid->usTimeHigh = ntohs(stmp);

	uuid->bClockSeqHigh = *ptr++;

	uuid->bClockSeqLow = *ptr++;

	(void) memcpy(uuid->rgbNode, ptr, 6);
}

static void
local_uuid_copy(TSS_UUID *dst, TSS_UUID *src)
{
	uuid_t udst, usrc;

	tss_uuid_convert_from(dst, udst);
	tss_uuid_convert_from(src, usrc);

	uuid_copy(udst, usrc);

	tss_uuid_convert_to(dst, udst);
}

static void
local_uuid_generate(TSS_UUID *uu)
{
	uuid_t newuuid;

	uuid_generate(newuuid);

	tss_uuid_convert_to(uu, newuuid);
}

static int
local_copy_file(char *dst, char *src)
{
	FILE *fdest, *fsrc;
	char line[BUFSIZ];

	fdest = fopen(dst, "w");
	if (fdest == NULL)
		return (-1);

	fsrc = fopen(src, "r");
	if (fsrc == NULL) {
		(void) fclose(fdest);
		return (-1);
	}

	while (fread(line, sizeof (line), 1, fsrc))
		(void) fprintf(fdest, "%s\n", line);
	(void) fclose(fsrc);
	(void) fclose(fdest);
	return (0);
}

static int
remove_uuid(char *keyname)
{
	int ret = 0;
	FILE *fp, *newfp;
	char fname[MAXPATHLEN];
	char line[BUFSIZ], key[BUFSIZ], idstr[BUFSIZ];
	char *tmpfname;
	char *p = get_tpm_keystore_path();

	if (p == NULL)
		return (-1);

	(void) snprintf(fname, sizeof (fname),
	    "%s/%s", p, TPMTOK_UUID_INDEX_FILENAME);

	fp = fopen(fname, "r");
	if (fp == NULL) {
		return (-1);
	}

	tmpfname = tempnam("/tmp", "tpmtok");
	newfp = fopen(tmpfname, "w+");
	if (newfp == NULL) {
		free(tmpfname);
		(void) fclose(fp);
		return (-1);
	}

	while (!feof(fp)) {
		(void) fgets(line, sizeof (line), fp);
		if (sscanf(line, "%1024s %1024s", key, idstr) == 2) {
			if (strcmp(key, keyname))
				(void) fprintf(newfp, "%s\n", line);
		}
	}

	(void) fclose(fp);
	(void) fclose(newfp);
	if (local_copy_file(fname, tmpfname) == 0)
		(void) unlink(tmpfname);

	free(tmpfname);

	return (ret);
}

static int
find_uuid(char *keyname, TSS_UUID *uu)
{
	int ret = 0, found = 0;
	FILE *fp = NULL;
	char fname[MAXPATHLEN];
	char line[BUFSIZ], key[BUFSIZ], idstr[BUFSIZ];
	uuid_t uuid;
	char *p = get_tpm_keystore_path();

	if (p == NULL)
		return (-1);

	tss_uuid_convert_from(uu, uuid);

	(void) snprintf(fname, sizeof (fname),
	    "%s/%s", p, TPMTOK_UUID_INDEX_FILENAME);

	/* Open UUID Index file */
	fp = fopen(fname, "r");
	if (fp == NULL) {
		if (errno == ENOENT) {
			/* initialize the file */
			fp = fopen(fname, "w");
			if (fp != NULL)
				(void) fclose(fp);
		}
		return (-1);
	}

	while (!feof(fp)) {
		(void) fgets(line, sizeof (line), fp);
		if (sscanf(line, "%1024s %1024s", key, idstr) == 2) {
			if (strcmp(key, keyname) == 0) {
				ret = uuid_parse(idstr, uuid);
				if (ret == 0) {
					found = 1;
					tss_uuid_convert_to(uu,
					    uuid);
				}
				break;
			}
		}
	}
	(void) fclose(fp);

	if (!found)
		ret = -1;
	return (ret);
}

static int
local_uuid_is_null(TSS_UUID *uu)
{
	uuid_t uuid;
	int nulluuid;

	tss_uuid_convert_from(uu, uuid);

	nulluuid = uuid_is_null(uuid);
	return (nulluuid);
}

static int
add_uuid(char *keyname, TSS_UUID *uu)
{
	FILE *fp = NULL;
	char fname[MAXPATHLEN];
	char idstr[BUFSIZ];
	uuid_t uuid;
	char *p = get_tpm_keystore_path();

	if (p == NULL)
		return (-1);

	tss_uuid_convert_from(uu, uuid);

	if (uuid_is_null(uuid))
		return (-1);

	uuid_unparse(uuid, idstr);

	(void) snprintf(fname, sizeof (fname),
	    "%s/%s", p, TPMTOK_UUID_INDEX_FILENAME);

	fp = fopen(fname, "a");
	if (fp == NULL)
		return (-1);

	(void) fprintf(fp, "%s %s\n", keyname, idstr);
	(void) fclose(fp);

	return (0);
}


static UINT32
util_get_keysize_flag(CK_ULONG size)
{
	switch (size) {
		case 512:
			return (TSS_KEY_SIZE_512);
		case 1024:
			return (TSS_KEY_SIZE_1024);
		case 2048:
			return (TSS_KEY_SIZE_2048);
		default:
			break;
	}

	return (0);
}

/* make sure the public exponent attribute is 65537 */
static CK_ULONG
util_check_public_exponent(TEMPLATE *tmpl)
{
	CK_BBOOL flag;
	CK_ATTRIBUTE *publ_exp_attr;
	CK_BYTE pubexp_bytes[] = { 1, 0, 1 };
	CK_ULONG publ_exp, rc = 1;

	flag = template_attribute_find(tmpl, CKA_PUBLIC_EXPONENT,
	    &publ_exp_attr);
	if (!flag) {
		LogError1("Couldn't find public exponent attribute");
		return (CKR_TEMPLATE_INCOMPLETE);
	}

	switch (publ_exp_attr->ulValueLen) {
		case 3:
			rc = memcmp(pubexp_bytes, publ_exp_attr->pValue, 3);
			break;
		case sizeof (CK_ULONG):
			publ_exp = *((CK_ULONG *)publ_exp_attr->pValue);
			if (publ_exp == 65537)
				rc = 0;
			break;
		default:
			break;
	}

	return (rc);
}

TSS_RESULT
set_public_modulus(TSS_HCONTEXT hContext, TSS_HKEY hKey,
	unsigned long size_n, unsigned char *n)
{
	UINT64 offset;
	UINT32 blob_size;
	BYTE *blob, pub_blob[1024];
	TCPA_PUBKEY pub_key;
	TSS_RESULT result;

	/* Get the TCPA_PUBKEY blob from the key object. */
	result = Tspi_GetAttribData(hKey, TSS_TSPATTRIB_KEY_BLOB,
	    TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, &blob_size, &blob);
	if (result != TSS_SUCCESS) {
		stlogit("Tspi_GetAttribData failed: rc=0x%0x - %s\n",
		    result, Trspi_Error_String(result));
		return (result);
	}

	offset = 0;
	result = Trspi_UnloadBlob_PUBKEY(&offset, blob, &pub_key);
	if (result != TSS_SUCCESS) {
		stlogit("Trspi_UnloadBlob_PUBKEY failed: rc=0x%0x - %s\n",
		    result, Trspi_Error_String(result));
		return (result);
	}

	Tspi_Context_FreeMemory(hContext, blob);
	/* Free the first dangling reference, putting 'n' in its place */
	free(pub_key.pubKey.key);
	pub_key.pubKey.keyLength = size_n;
	pub_key.pubKey.key = n;

	offset = 0;
	Trspi_LoadBlob_PUBKEY(&offset, pub_blob, &pub_key);

	/* Free the second dangling reference */
	free(pub_key.algorithmParms.parms);

	/* set the public key data in the TSS object */
	result = Tspi_SetAttribData(hKey, TSS_TSPATTRIB_KEY_BLOB,
	    TSS_TSPATTRIB_KEYBLOB_PUBLIC_KEY, (UINT32)offset, pub_blob);
	if (result != TSS_SUCCESS) {
		stlogit("Tspi_SetAttribData failed: rc=0x%0x - %s\n",
		    result, Trspi_Error_String(result));
		return (result);
	}

	return (result);
}

/*
 * Get details about the TPM to put into the token_info structure.
 */
CK_RV
token_get_tpm_info(TSS_HCONTEXT hContext, TOKEN_DATA *td)
{
	TSS_RESULT result;
	TPM_CAPABILITY_AREA capArea = TSS_TPMCAP_VERSION_VAL;
	UINT32 datalen;
	BYTE *data;
	TSS_HTPM hTPM;

	if ((result = Tspi_Context_GetTpmObject(hContext, &hTPM))) {
		stlogit("Tspi_Context_GetTpmObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}
	if ((result = Tspi_TPM_GetCapability(hTPM,
	    capArea, 0, NULL, &datalen, &data)) != 0 || datalen == 0 ||
	    data == NULL) {
		stlogit("Tspi_Context_GetCapability: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}
	if (datalen > sizeof (tpmvinfo)) {
		Tspi_Context_FreeMemory(hContext, data);
		return (CKR_FUNCTION_FAILED);
	}

	(void) memcpy(&tpmvinfo, (void *)data, datalen);

	bzero(td->token_info.manufacturerID,
	    sizeof (td->token_info.manufacturerID));

	(void) memset(td->token_info.manufacturerID,  ' ',
	    sizeof (td->token_info.manufacturerID) - 1);

	(void) memcpy(td->token_info.manufacturerID,
	    tpmvinfo.tpmVendorID, sizeof (tpmvinfo.tpmVendorID));

	(void) memset(td->token_info.label, ' ',
	    sizeof (td->token_info.label) - 1);

	(void) memcpy(td->token_info.label, "TPM", 3);

	td->token_info.hardwareVersion.major = tpmvinfo.version.major;
	td->token_info.hardwareVersion.minor = tpmvinfo.version.minor;
	td->token_info.firmwareVersion.major = tpmvinfo.version.revMajor;
	td->token_info.firmwareVersion.minor = tpmvinfo.version.revMinor;

	Tspi_Context_FreeMemory(hContext, data);
	return (CKR_OK);
}

/*ARGSUSED*/
CK_RV
token_specific_session(CK_SLOT_ID  slotid)
{
	return (CKR_OK);
}

CK_RV
token_rng(TSS_HCONTEXT hContext, CK_BYTE *output, CK_ULONG bytes)
{
	TSS_RESULT rc;
	TSS_HTPM hTPM;
	BYTE *random_bytes = NULL;

	if ((rc = Tspi_Context_GetTpmObject(hContext, &hTPM))) {
		stlogit("Tspi_Context_GetTpmObject: 0x%0x - %s",
		    rc, Trspi_Error_String(rc));
		return (CKR_FUNCTION_FAILED);
	}

	if ((rc = Tspi_TPM_GetRandom(hTPM, bytes, &random_bytes))) {
		stlogit("Tspi_TPM_GetRandom: 0x%0x - %s",
		    rc, Trspi_Error_String(rc));
		return (CKR_FUNCTION_FAILED);
	}

	(void) memcpy(output, random_bytes, bytes);
	Tspi_Context_FreeMemory(hContext, random_bytes);

	return (CKR_OK);
}

TSS_RESULT
open_tss_context(TSS_HCONTEXT *pContext)
{
	TSS_RESULT result;

	if ((result = Tspi_Context_Create(pContext))) {
		stlogit("Tspi_Context_Create: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	if ((result = Tspi_Context_Connect(*pContext, NULL))) {
		stlogit("Tspi_Context_Connect: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		Tspi_Context_Close(*pContext);
		*pContext = 0;
		return (CKR_FUNCTION_FAILED);
	}
	return (result);
}

/*ARGSUSED*/
static CK_RV
token_specific_init(char *Correlator, CK_SLOT_ID SlotNumber,
    TSS_HCONTEXT *hContext)
{
	TSS_RESULT result;

	result = open_tss_context(hContext);
	if (result)
		return (CKR_FUNCTION_FAILED);

	if ((result = Tspi_Context_GetDefaultPolicy(*hContext,
	    &hDefaultPolicy))) {
		stlogit("Tspi_Context_GetDefaultPolicy: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	local_uuid_clear(&publicRootKeyUUID);
	local_uuid_clear(&privateRootKeyUUID);
	local_uuid_clear(&publicLeafKeyUUID);
	local_uuid_clear(&privateLeafKeyUUID);

	result = token_get_tpm_info(*hContext, nv_token_data);
	return (result);
}

/*
 * Given a modulus and prime from an RSA key, create a TSS_HKEY object by
 * wrapping the RSA key with a key from the TPM (SRK or other previously stored
 * key).
 */
static CK_RV
token_wrap_sw_key(
	TSS_HCONTEXT hContext,
	int size_n,
	unsigned char *n,
	int size_p,
	unsigned char *p,
	TSS_HKEY hParentKey,
	TSS_FLAG initFlags,
	TSS_HKEY *phKey)
{
	TSS_RESULT result;
	UINT32 key_size;

	key_size = util_get_keysize_flag(size_n * 8);
	if (initFlags == 0) {
		return (CKR_FUNCTION_FAILED);
	}

	/* create the TSS key object */
	result = Tspi_Context_CreateObject(hContext, TSS_OBJECT_TYPE_RSAKEY,
	    TSS_KEY_MIGRATABLE | initFlags | key_size, phKey);
	if (result != TSS_SUCCESS) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	result = set_public_modulus(hContext, *phKey, size_n, n);
	if (result != TSS_SUCCESS) {
		Tspi_Context_CloseObject(hContext, *phKey);
		*phKey = NULL_HKEY;
		return (CKR_FUNCTION_FAILED);
	}

	/* set the private key data in the TSS object */
	result = Tspi_SetAttribData(*phKey, TSS_TSPATTRIB_KEY_BLOB,
	    TSS_TSPATTRIB_KEYBLOB_PRIVATE_KEY, size_p, p);
	if (result != TSS_SUCCESS) {
		stlogit("Tspi_SetAttribData: 0x%x - %s",
		    result, Trspi_Error_String(result));
		Tspi_Context_CloseObject(hContext, *phKey);
		*phKey = NULL_HKEY;
		return (CKR_FUNCTION_FAILED);
	}

	result = tss_assign_secret_key_policy(hContext, TSS_POLICY_MIGRATION,
	    *phKey, NULL);

	if (TPMTOK_TSS_KEY_TYPE(initFlags) == TSS_KEY_TYPE_LEGACY) {
		if ((result = Tspi_SetAttribUint32(*phKey,
		    TSS_TSPATTRIB_KEY_INFO, TSS_TSPATTRIB_KEYINFO_ENCSCHEME,
		    TSS_ES_RSAESPKCSV15))) {
			stlogit("Tspi_SetAttribUint32: 0x%0x - %s\n",
			    result, Trspi_Error_String(result));
			Tspi_Context_CloseObject(hContext, *phKey);
			return (CKR_FUNCTION_FAILED);
		}

		if ((result = Tspi_SetAttribUint32(*phKey,
		    TSS_TSPATTRIB_KEY_INFO, TSS_TSPATTRIB_KEYINFO_SIGSCHEME,
		    TSS_SS_RSASSAPKCS1V15_DER))) {
			stlogit("Tspi_SetAttribUint32: 0x%0x - %s\n",
			    result, Trspi_Error_String(result));
			Tspi_Context_CloseObject(hContext, *phKey);
			return (CKR_FUNCTION_FAILED);
		}
	}

	result = Tspi_Key_WrapKey(*phKey, hParentKey, NULL_HPCRS);
	if (result != TSS_SUCCESS) {
		stlogit("Tspi_Key_WrapKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		Tspi_Context_CloseObject(hContext, *phKey);
		*phKey = NULL_HKEY;
		return (CKR_FUNCTION_FAILED);
	}

	return (CKR_OK);
}

/*
 * Create a TPM key blob for an imported key. This function is only called when
 * a key is in active use, so any failure should trickle through.
 */
static CK_RV
token_wrap_key_object(TSS_HCONTEXT hContext,
	CK_OBJECT_HANDLE ckObject,
	TSS_HKEY hParentKey, TSS_HKEY *phKey)
{
	CK_RV		rc = CKR_OK;
	CK_ATTRIBUTE	*attr = NULL, *new_attr, *prime_attr;
	CK_ULONG	class, key_type;
	OBJECT		*obj;

	TSS_RESULT	result;
	TSS_FLAG	initFlags = 0;
	BYTE		*rgbBlob;
	UINT32		ulBlobLen;

	if ((rc = object_mgr_find_in_map1(hContext, ckObject, &obj))) {
		return (rc);
	}

	/* if the object isn't a key, fail */
	if (template_attribute_find(obj->template, CKA_KEY_TYPE,
	    &attr) == FALSE) {
		return (CKR_TEMPLATE_INCOMPLETE);
	}

	key_type = *((CK_ULONG *)attr->pValue);

	if (key_type != CKK_RSA) {
		return (CKR_TEMPLATE_INCONSISTENT);
	}

	if (template_attribute_find(obj->template, CKA_CLASS,
	    &attr) == FALSE) {
		return (CKR_TEMPLATE_INCOMPLETE);
	}

	class = *((CK_ULONG *)attr->pValue);

	if (class == CKO_PRIVATE_KEY) {
		/*
		 * In order to create a full TSS key blob using a PKCS#11
		 * private key object, we need one of the two primes, the
		 * modulus and the private exponent and we need the public
		 * exponent to be correct.
		 */

		/*
		 * Check the least likely attribute to exist first, the
		 * primes.
		 */
		if (template_attribute_find(obj->template, CKA_PRIME_1,
		    &prime_attr) == FALSE) {
			if (template_attribute_find(obj->template,
			    CKA_PRIME_2, &prime_attr) == FALSE) {
				return (CKR_TEMPLATE_INCOMPLETE);
			}
		}

		/* Make sure the public exponent is usable */
		if ((rc = util_check_public_exponent(obj->template))) {
			return (CKR_TEMPLATE_INCONSISTENT);
		}

		/* get the modulus */
		if (template_attribute_find(obj->template, CKA_MODULUS,
		    &attr) == FALSE) {
			return (CKR_TEMPLATE_INCOMPLETE);
		}

		/* make sure the key size is usable */
		initFlags = util_get_keysize_flag(attr->ulValueLen * 8);
		if (initFlags == 0) {
			return (CKR_TEMPLATE_INCONSISTENT);
		}

		/* generate the software based key */
		if ((rc = token_wrap_sw_key(hContext,
		    (int)attr->ulValueLen, attr->pValue,
		    (int)prime_attr->ulValueLen, prime_attr->pValue,
		    hParentKey, TSS_KEY_TYPE_LEGACY | TSS_KEY_NO_AUTHORIZATION,
		    phKey))) {
			return (rc);
		}
	} else if (class == CKO_PUBLIC_KEY) {
		/* Make sure the public exponent is usable */
		if ((util_check_public_exponent(obj->template))) {
			return (CKR_TEMPLATE_INCONSISTENT);
		}

		/* grab the modulus to put into the TSS key object */
		if (template_attribute_find(obj->template,
		    CKA_MODULUS, &attr) == FALSE) {
			return (CKR_TEMPLATE_INCONSISTENT);
		}

		/* make sure the key size is usable */
		initFlags = util_get_keysize_flag(attr->ulValueLen * 8);
		if (initFlags == 0) {
			return (CKR_TEMPLATE_INCONSISTENT);
		}

		initFlags |= TSS_KEY_MIGRATABLE | TSS_KEY_NO_AUTHORIZATION |
		    TSS_KEY_TYPE_LEGACY;

		if ((result = Tspi_Context_CreateObject(hContext,
		    TSS_OBJECT_TYPE_RSAKEY, initFlags, phKey))) {
			stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (result);
		}

		if ((result = set_public_modulus(hContext, *phKey,
		    attr->ulValueLen, attr->pValue))) {
			Tspi_Context_CloseObject(hContext, *phKey);
			*phKey = NULL_HKEY;
			return (CKR_FUNCTION_FAILED);
		}
		result = tss_assign_secret_key_policy(hContext,
		    TSS_POLICY_MIGRATION, *phKey, NULL);
		if (result) {
			Tspi_Context_CloseObject(hContext, *phKey);
			*phKey = NULL_HKEY;
			return (CKR_FUNCTION_FAILED);
		}

		result = set_legacy_key_params(*phKey);
		if (result) {
			Tspi_Context_CloseObject(hContext, *phKey);
			*phKey = NULL_HKEY;
			return (CKR_FUNCTION_FAILED);
		}
	} else {
		return (CKR_FUNCTION_FAILED);
	}

	/* grab the entire key blob to put into the PKCS#11 object */
	if ((result = Tspi_GetAttribData(*phKey, TSS_TSPATTRIB_KEY_BLOB,
	    TSS_TSPATTRIB_KEYBLOB_BLOB, &ulBlobLen, &rgbBlob))) {
		stlogit("Tspi_GetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* insert the key blob into the object */
	if ((rc = build_attribute(CKA_IBM_OPAQUE, rgbBlob, ulBlobLen,
	    &new_attr))) {
		Tspi_Context_FreeMemory(hContext, rgbBlob);
		return (rc);
	}
	(void) template_update_attribute(obj->template, new_attr);
	Tspi_Context_FreeMemory(hContext, rgbBlob);

	/*
	 * If this is a token object, save it with the new attribute
	 * so that we don't have to go down this path again.
	 */
	if (!object_is_session_object(obj)) {
		rc = save_token_object(hContext, obj);
	}

	return (rc);
}

static TSS_RESULT
tss_assign_secret_key_policy(TSS_HCONTEXT hContext, TSS_FLAG policyType,
    TSS_HKEY hKey, CK_CHAR *passHash)
{
	TSS_RESULT result;
	TSS_HPOLICY hPolicy;

	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_POLICY, policyType, &hPolicy))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}
	if ((result = Tspi_Policy_AssignToObject(hPolicy, hKey))) {
		stlogit("Tspi_Policy_AssignToObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}
	if (passHash == NULL) {
		result = Tspi_Policy_SetSecret(hPolicy, TSS_SECRET_MODE_NONE,
		    0, NULL);
	} else {
		result = Tspi_Policy_SetSecret(hPolicy, TSS_SECRET_MODE_SHA1,
		    SHA1_DIGEST_LENGTH, passHash);
	}
	if (result != TSS_SUCCESS) {
		stlogit("Tspi_Policy_SetSecret: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}
done:
	if (result != TSS_SUCCESS)
		Tspi_Context_CloseObject(hContext, hPolicy);
	return (result);
}

/*
 * Take a key from the TSS store (on-disk) and load it into the TPM, wrapped
 * by an already TPM-resident key and protected with a PIN (optional).
 */
static CK_RV
token_load_key(
	TSS_HCONTEXT hContext,
	CK_OBJECT_HANDLE ckKey,
	TSS_HKEY hParentKey,
	CK_CHAR_PTR passHash,
	TSS_HKEY *phKey)
{
	TSS_RESULT result;
	CK_RV rc;

	/*
	 * The key blob wasn't found, load the parts of the key
	 * from the object DB and create a new key object that
	 * gets loaded into the TPM, wrapped with the parent key.
	 */
	if ((rc = token_wrap_key_object(hContext, ckKey,
	    hParentKey, phKey))) {
		return (rc);
	}

	/*
	 * Assign the PIN hash (optional) to the newly loaded key object,
	 * if this PIN is incorrect, the TPM will not be able to decrypt
	 * the private key and use it.
	 */
	result = tss_assign_secret_key_policy(hContext, TSS_POLICY_USAGE,
	    *phKey, passHash);

	return (result);
}

/*
 * Load the SRK into the TPM by referencing its well-known UUID and using the
 * default SRK PIN (20 bytes of 0x00).
 *
 * NOTE - if the SRK PIN is changed by an administrative tool, this code will
 * fail because it assumes that the well-known PIN is still being used.
 */
static TSS_RESULT
token_load_srk(TSS_HCONTEXT hContext, TSS_HKEY *hSRK)
{
	TSS_HPOLICY hPolicy;
	TSS_RESULT result;
	TSS_UUID SRK_UUID = TSS_UUID_SRK;
	BYTE wellKnown[] = TSS_WELL_KNOWN_SECRET;
	TSS_HTPM hTPM;

	if ((result = Tspi_Context_GetTpmObject(hContext, &hTPM))) {
		stlogit("Tspi_Context_GetTpmObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* load the SRK */
	if ((result = Tspi_Context_LoadKeyByUUID(hContext,
	    TSS_PS_TYPE_SYSTEM, SRK_UUID, hSRK))) {
		stlogit("Tspi_Context_LoadKeyByUUID: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}
	if ((result = Tspi_GetPolicyObject(*hSRK, TSS_POLICY_USAGE,
	    &hPolicy))) {
		stlogit("Tspi_GetPolicyObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}
	if ((result = Tspi_Policy_SetSecret(hPolicy, TSS_SECRET_MODE_SHA1,
	    sizeof (wellKnown), wellKnown))) {
		stlogit("Tspi_Policy_SetSecret: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}

done:
	return (result);
}

static TSS_RESULT
tss_find_and_load_key(TSS_HCONTEXT hContext,
	char *keyid, TSS_UUID *uuid, TSS_HKEY hParent,
	BYTE *hash, TSS_HKEY *hKey)
{
	TSS_RESULT result;

	if (local_uuid_is_null(uuid) &&
	    find_uuid(keyid, uuid)) {
		/* The UUID was not created or saved yet */
		return (1);
	}
	result = Tspi_Context_GetKeyByUUID(hContext,
	    TSS_PS_TYPE_USER, *uuid, hKey);
	if (result) {
		stlogit("Tspi_Context_GetKeyByUUID: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}

	if (hash != NULL) {
		result = tss_assign_secret_key_policy(hContext,
		    TSS_POLICY_USAGE, *hKey, (CK_BYTE *)hash);
		if (result)
			return (result);
	}

	result = Tspi_Key_LoadKey(*hKey, hParent);
	if (result)
		stlogit("Tspi_Key_LoadKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));

	return (result);
}

static TSS_RESULT
token_load_public_root_key(TSS_HCONTEXT hContext)
{
	TSS_RESULT result;
	TSS_HKEY hSRK;

	if (hPublicRootKey != NULL_HKEY)
		return (TSS_SUCCESS);

	if ((result = token_load_srk(hContext, &hSRK))) {
		return (result);
	}

	result = tss_find_and_load_key(hContext,
	    TPMTOK_PUBLIC_ROOT_KEY_ID,
	    &publicRootKeyUUID, hSRK, NULL, &hPublicRootKey);
	if (result)
		return (result);

	return (result);
}

static TSS_RESULT
set_legacy_key_params(TSS_HKEY hKey)
{
	TSS_RESULT result;

	if ((result = Tspi_SetAttribUint32(hKey,
	    TSS_TSPATTRIB_KEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_ENCSCHEME,
	    TSS_ES_RSAESPKCSV15))) {
		stlogit("Tspi_SetAttribUint32: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}

	if ((result = Tspi_SetAttribUint32(hKey,
	    TSS_TSPATTRIB_KEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_SIGSCHEME,
	    TSS_SS_RSASSAPKCS1V15_DER))) {
		stlogit("Tspi_SetAttribUint32: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}

	return (result);
}

static TSS_RESULT
tss_generate_key(TSS_HCONTEXT hContext, TSS_FLAG initFlags, BYTE *passHash,
	TSS_HKEY hParentKey, TSS_HKEY *phKey)
{
	TSS_RESULT	result;
	TSS_HPOLICY	hMigPolicy;

	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_RSAKEY, initFlags, phKey))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}
	result = tss_assign_secret_key_policy(hContext, TSS_POLICY_USAGE,
	    *phKey, passHash);

	if (result) {
		Tspi_Context_CloseObject(hContext, *phKey);
		return (result);
	}

	if (TPMTOK_TSS_KEY_MIG_TYPE(initFlags) == TSS_KEY_MIGRATABLE) {
		if ((result = Tspi_Context_CreateObject(hContext,
		    TSS_OBJECT_TYPE_POLICY, TSS_POLICY_MIGRATION,
		    &hMigPolicy))) {
			stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			Tspi_Context_CloseObject(hContext, *phKey);
			return (result);
		}

		if (passHash == NULL) {
			result = Tspi_Policy_SetSecret(hMigPolicy,
			    TSS_SECRET_MODE_NONE, 0, NULL);
		} else {
			result = Tspi_Policy_SetSecret(hMigPolicy,
			    TSS_SECRET_MODE_SHA1, 20, passHash);
		}

		if (result != TSS_SUCCESS) {
			stlogit("Tspi_Policy_SetSecret: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			Tspi_Context_CloseObject(hContext, *phKey);
			Tspi_Context_CloseObject(hContext, hMigPolicy);
			return (result);
		}

		if ((result = Tspi_Policy_AssignToObject(hMigPolicy, *phKey))) {
			stlogit("Tspi_Policy_AssignToObject: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			Tspi_Context_CloseObject(hContext, *phKey);
			Tspi_Context_CloseObject(hContext, hMigPolicy);
			return (result);
		}
	}

	if (TPMTOK_TSS_KEY_TYPE(initFlags) == TSS_KEY_TYPE_LEGACY) {
		result = set_legacy_key_params(*phKey);
		if (result) {
			Tspi_Context_CloseObject(hContext, *phKey);
			Tspi_Context_CloseObject(hContext, hMigPolicy);
			return (result);
		}
	}

	if ((result = Tspi_Key_CreateKey(*phKey, hParentKey, 0))) {
		stlogit("Tspi_Key_CreateKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		Tspi_Context_CloseObject(hContext, *phKey);
		Tspi_Context_CloseObject(hContext, hMigPolicy);
	}

	return (result);
}

static TSS_RESULT
tss_change_auth(
	TSS_HCONTEXT hContext,
	TSS_HKEY hObjectToChange, TSS_HKEY hParentObject,
	TSS_UUID objUUID, TSS_UUID parentUUID,
	CK_CHAR *passHash)
{
	TSS_RESULT result;
	TSS_HPOLICY hPolicy;
	TSS_HKEY oldkey;

	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE, &hPolicy))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}

	if ((result = Tspi_Policy_SetSecret(hPolicy, TSS_SECRET_MODE_SHA1,
	    SHA1_DIGEST_LENGTH, passHash))) {
		stlogit("Tspi_Policy_SetSecret: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}

	if ((result = Tspi_ChangeAuth(hObjectToChange, hParentObject,
	    hPolicy))) {
		stlogit("Tspi_ChangeAuth: 0x%0x - %s",
		    result, Trspi_Error_String(result));
	}
	/*
	 * Update the PS key by unregistering the key UUID and then
	 * re-registering with the same UUID.  This forces the updated
	 * auth data associated with the key to be stored in PS so
	 * the new PIN can be used next time.
	 */
	if ((result = Tspi_Context_UnregisterKey(hContext,
	    TSS_PS_TYPE_USER, objUUID, &oldkey)))
		stlogit("Tspi_Context_UnregisterKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));

	if ((result = Tspi_Context_RegisterKey(hContext, hObjectToChange,
	    TSS_PS_TYPE_USER, objUUID, TSS_PS_TYPE_USER, parentUUID)))
		stlogit("Tspi_Context_RegisterKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));

	return (result);
}

static CK_RV
token_generate_leaf_key(TSS_HCONTEXT hContext,
	int key_type, CK_CHAR_PTR passHash, TSS_HKEY *phKey)
{
	CK_RV		rc = CKR_FUNCTION_FAILED;
	TSS_RESULT	result;
	TSS_HKEY	hParentKey;
	TSS_UUID	newuuid, parentUUID;
	char		*keyid;
	TSS_FLAG	initFlags = TSS_KEY_MIGRATABLE |
	    TSS_KEY_TYPE_BIND | TSS_KEY_SIZE_2048  | TSS_KEY_AUTHORIZATION;

	switch (key_type) {
		case TPMTOK_PUBLIC_LEAF_KEY:
			hParentKey = hPublicRootKey;
			keyid = TPMTOK_PUBLIC_LEAF_KEY_ID;
			local_uuid_copy(&parentUUID, &publicRootKeyUUID);
			break;
		case TPMTOK_PRIVATE_LEAF_KEY:
			hParentKey = hPrivateRootKey;
			keyid = TPMTOK_PRIVATE_LEAF_KEY_ID;
			local_uuid_copy(&parentUUID, &privateRootKeyUUID);
			break;
		default:
			stlogit("Unknown key type 0x%0x", key_type);
			goto done;
	}

	if (result = tss_generate_key(hContext, initFlags, passHash,
	    hParentKey, phKey)) {
		return (rc);
	}

	/*
	 * - generate newUUID
	 * - Tspi_Context_RegisterKey(hContext, hPrivateRootKey,
	 *   USER, newUUID, USER, parentUUID);
	 * - store newUUID
	 */
	(void) local_uuid_generate(&newuuid);

	result = Tspi_Context_RegisterKey(hContext, *phKey,
	    TSS_PS_TYPE_USER, newuuid,
	    TSS_PS_TYPE_USER, parentUUID);
	if (result == TSS_SUCCESS) {
		int ret;
		/*
		 * Add the UUID to the token UUID index.
		 */
		ret = add_uuid(keyid, &newuuid);

		if (ret)
			result = Tspi_Context_UnregisterKey(hContext,
			    TSS_PS_TYPE_USER, newuuid, phKey);
		else
			rc = CKR_OK;
	}

done:
	return (rc);
}

/*
 * PINs are verified by attempting to bind/unbind random data using a
 * TPM resident key that has the PIN being tested assigned as its "secret".
 * If the PIN is incorrect, the unbind operation will fail.
 */
static CK_RV
token_verify_pin(TSS_HCONTEXT hContext, TSS_HKEY hKey)
{
	TSS_HENCDATA hEncData;
	UINT32 ulUnboundDataLen;
	BYTE *rgbUnboundData = NULL;
	BYTE rgbData[16];
	TSS_RESULT result;
	CK_RV rc = CKR_FUNCTION_FAILED;

	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_BIND, &hEncData))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}

	/* Use some random data */
	rc = token_rng(hContext, rgbData, sizeof (rgbData));
	if (rc)
		goto done;

	if ((result = Tspi_Data_Bind(hEncData, hKey,
	    sizeof (rgbData), rgbData))) {
		stlogit("Tspi_Data_Bind: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	}

	/* unbind the junk data to test the key's auth data */
	result = Tspi_Data_Unbind(hEncData, hKey, &ulUnboundDataLen,
	    &rgbUnboundData);
	if (result == TPM_E_AUTHFAIL) {
		rc = CKR_PIN_INCORRECT;
		stlogit("Tspi_Data_Unbind: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		goto done;
	} else if (result != TSS_SUCCESS) {
		stlogit("Tspi_Data_Unbind: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		rc = CKR_FUNCTION_FAILED;
		goto done;
	}

	if (memcmp(rgbUnboundData, rgbData, ulUnboundDataLen))
		rc = CKR_PIN_INCORRECT;
	else
		rc = CKR_OK;

done:
	if (rgbUnboundData != NULL)
		Tspi_Context_FreeMemory(hContext, rgbUnboundData);
	Tspi_Context_CloseObject(hContext, hEncData);
	return (rc);
}

static CK_RV
token_create_private_tree(TSS_HCONTEXT hContext, CK_BYTE *pinHash)
{
	CK_RV		rc;
	TSS_RESULT	result;
	int		ret;
	TSS_FLAG initFlags = TSS_KEY_SIZE_2048 |
	    TSS_KEY_NO_AUTHORIZATION | TSS_KEY_TYPE_STORAGE;
	TSS_UUID SRK_UUID = TSS_UUID_SRK;
	TSS_HKEY hSRK;

	if (token_load_srk(hContext, &hSRK))
		return (CKR_FUNCTION_FAILED);

	/*
	 * - create UUID privateRootKeyUUID
	 * - Tspi_Context_RegisterKey(hContext, hPrivateRootKey,
	 *   USER, privateRootKeyUUID, system, UUID_SRK);
	 * - store privateRootKeyUUID in users private token space.
	 */
	if ((result = tss_generate_key(hContext, initFlags, NULL, hSRK,
	    &hPrivateRootKey))) {
		return (result);
	}
	if (local_uuid_is_null(&privateRootKeyUUID))
		local_uuid_generate(&privateRootKeyUUID);

	result = Tspi_Context_RegisterKey(hContext, hPrivateRootKey,
	    TSS_PS_TYPE_USER, privateRootKeyUUID,
	    TSS_PS_TYPE_SYSTEM, SRK_UUID);

	if (result) {
		local_uuid_clear(&privateRootKeyUUID);
		return (result);
	}

	ret = add_uuid(TPMTOK_PRIVATE_ROOT_KEY_ID, &privateRootKeyUUID);
	if (ret) {
		result = Tspi_Context_UnregisterKey(hContext,
		    TSS_PS_TYPE_USER, privateRootKeyUUID,
		    &hPrivateRootKey);
		return (CKR_FUNCTION_FAILED);
	}

	if ((result = Tspi_Key_LoadKey(hPrivateRootKey, hSRK))) {
		stlogit("Tspi_Key_LoadKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		Tspi_Context_CloseObject(hContext, hPrivateRootKey);

		(void) remove_uuid(TPMTOK_PRIVATE_ROOT_KEY_ID);
		local_uuid_clear(&privateRootKeyUUID);

		hPrivateRootKey = NULL_HKEY;
		return (CKR_FUNCTION_FAILED);
	}


	/* generate the private leaf key */
	if ((rc = token_generate_leaf_key(hContext,
	    TPMTOK_PRIVATE_LEAF_KEY,
	    pinHash, &hPrivateLeafKey))) {
		return (rc);
	}

	if ((result = Tspi_Key_LoadKey(hPrivateLeafKey, hPrivateRootKey))) {
		stlogit("Tspi_Key_LoadKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));

		(void) Tspi_Context_UnregisterKey(hContext,
		    TSS_PS_TYPE_USER, privateLeafKeyUUID,
		    &hPrivateLeafKey);
		(void) remove_uuid(TPMTOK_PRIVATE_LEAF_KEY_ID);
		local_uuid_clear(&privateLeafKeyUUID);

		(void) Tspi_Context_UnregisterKey(hContext,
		    TSS_PS_TYPE_USER, privateRootKeyUUID,
		    &hPrivateRootKey);
		(void) remove_uuid(TPMTOK_PRIVATE_ROOT_KEY_ID);
		local_uuid_clear(&privateRootKeyUUID);

		Tspi_Context_CloseObject(hContext, hPrivateRootKey);
		hPrivateRootKey = NULL_HKEY;

		Tspi_Context_CloseObject(hContext, hPrivateLeafKey);
		hPrivateRootKey = NULL_HKEY;

		return (CKR_FUNCTION_FAILED);
	}
	return (rc);
}

static CK_RV
token_create_public_tree(TSS_HCONTEXT hContext, CK_BYTE *pinHash)
{
	CK_RV		rc;
	TSS_RESULT	result;
	int		ret;
	TSS_FLAG initFlags = TSS_KEY_SIZE_2048 |
	    TSS_KEY_NO_AUTHORIZATION | TSS_KEY_TYPE_STORAGE;
	TSS_UUID srk_uuid = TSS_UUID_SRK;
	TSS_HKEY hSRK;

	if (token_load_srk(hContext, &hSRK))
		return (CKR_FUNCTION_FAILED);

	/*
	 * - create publicRootKeyUUID
	 * - Tspi_Context_RegisterKey(hContext, hPublicRootKey,
	 *   USER, publicRootKeyUUID, system, UUID_SRK);
	 * - store publicRootKeyUUID in users private token space.
	 */
	if ((result = tss_generate_key(hContext, initFlags, NULL, hSRK,
	    &hPublicRootKey))) {
		return (CKR_FUNCTION_FAILED);
	}
	if (local_uuid_is_null(&publicRootKeyUUID))
		local_uuid_generate(&publicRootKeyUUID);

	result = Tspi_Context_RegisterKey(hContext, hPublicRootKey,
	    TSS_PS_TYPE_USER, publicRootKeyUUID,
	    TSS_PS_TYPE_SYSTEM, srk_uuid);

	if (result) {
		local_uuid_clear(&publicRootKeyUUID);
		return (CKR_FUNCTION_FAILED);
	}

	ret = add_uuid(TPMTOK_PUBLIC_ROOT_KEY_ID, &publicRootKeyUUID);
	if (ret) {
		result = Tspi_Context_UnregisterKey(hContext,
		    TSS_PS_TYPE_USER, publicRootKeyUUID,
		    &hPublicRootKey);
		/* does result matter here? */
		return (CKR_FUNCTION_FAILED);
	}

	/* Load the newly created publicRootKey into the TPM using the SRK */
	if ((result = Tspi_Key_LoadKey(hPublicRootKey, hSRK))) {
		stlogit("Tspi_Key_LoadKey: 0x%x - %s", result,
		    Trspi_Error_String(result));
		Tspi_Context_CloseObject(hContext, hPublicRootKey);
		hPublicRootKey = NULL_HKEY;
		return (CKR_FUNCTION_FAILED);
	}

	/* create the SO's leaf key */
	if ((rc = token_generate_leaf_key(hContext, TPMTOK_PUBLIC_LEAF_KEY,
	    pinHash, &hPublicLeafKey))) {
		return (rc);
	}

	if ((result = Tspi_Key_LoadKey(hPublicLeafKey, hPublicRootKey))) {
		stlogit("Tspi_Key_LoadKey: 0x%0x - %s",
		    result, Trspi_Error_String(result));

		/* Unregister keys and clear UUIDs */
		(void) Tspi_Context_UnregisterKey(hContext,
		    TSS_PS_TYPE_USER, publicLeafKeyUUID,
		    &hPublicLeafKey);
		(void) remove_uuid(TPMTOK_PUBLIC_LEAF_KEY_ID);

		(void) Tspi_Context_UnregisterKey(hContext,
		    TSS_PS_TYPE_USER, publicRootKeyUUID,
		    &hPublicRootKey);
		(void) remove_uuid(TPMTOK_PUBLIC_ROOT_KEY_ID);

		Tspi_Context_CloseObject(hContext, hPublicRootKey);
		hPublicRootKey = NULL_HKEY;

		Tspi_Context_CloseObject(hContext, hPublicLeafKey);
		hPublicLeafKey = NULL_HKEY;

		return (CKR_FUNCTION_FAILED);
	}

	return (rc);
}

CK_RV
token_specific_login(
	TSS_HCONTEXT hContext,
	CK_USER_TYPE userType,
	CK_CHAR_PTR pPin,
	CK_ULONG ulPinLen)
{
	CK_RV rc;
	CK_BYTE hash_sha[SHA1_DIGEST_LENGTH];
	TSS_RESULT result;
	TSS_HKEY hSRK;

	/* Make sure the SRK is loaded into the TPM */
	if ((result = token_load_srk(hContext, &hSRK))) {
		return (CKR_FUNCTION_FAILED);
	}

	if ((rc = compute_sha(pPin, ulPinLen, hash_sha))) {
		return (CKR_FUNCTION_FAILED);
	}

	if (userType == CKU_USER) {
		/*
		 * If the public root key doesn't exist yet,
		 * the SO hasn't init'd the token.
		 */
		if ((result = token_load_public_root_key(hContext))) {
			if (result == TPM_E_DECRYPT_ERROR) {
				return (CKR_USER_PIN_NOT_INITIALIZED);
			}
		}

		/*
		 * - find privateRootKeyUUID
		 * - load by UUID (SRK parent)
		 */
		if (local_uuid_is_null(&privateRootKeyUUID) &&
		    find_uuid(TPMTOK_PRIVATE_ROOT_KEY_ID,
		    &privateRootKeyUUID)) {
				if (memcmp(hash_sha,
				    default_user_pin_sha,
				    SHA1_DIGEST_LENGTH))
					return (CKR_PIN_INCORRECT);

				not_initialized = 1;
				return (CKR_OK);
		}

		if ((rc = verify_user_pin(hContext, hash_sha))) {
			return (rc);
		}

		(void) memcpy(current_user_pin_sha, hash_sha,
		    SHA1_DIGEST_LENGTH);

		rc = load_private_token_objects(hContext);
		if (rc == CKR_OK) {
			(void) XProcLock(xproclock);
			global_shm->priv_loaded = TRUE;
			(void) XProcUnLock(xproclock);
		}
	} else {
		/*
		 * SO login logic:
		 *
		 * - find publicRootKey UUID
		 * - load by UUID wrap with hSRK from above
		 */
		if (local_uuid_is_null(&publicRootKeyUUID) &&
		    find_uuid(TPMTOK_PUBLIC_ROOT_KEY_ID,
		    &publicRootKeyUUID)) {
				if (memcmp(hash_sha,
				    default_so_pin_sha,
				    SHA1_DIGEST_LENGTH))
					return (CKR_PIN_INCORRECT);

				not_initialized = 1;
				return (CKR_OK);

		}
		if (hPublicRootKey == NULL_HKEY) {
			result = tss_find_and_load_key(
			    hContext,
			    TPMTOK_PUBLIC_ROOT_KEY_ID,
			    &publicRootKeyUUID, hSRK, NULL,
			    &hPublicRootKey);

			if (result)
				return (CKR_FUNCTION_FAILED);
		}

		/* find, load the public leaf key */
		if (hPublicLeafKey == NULL_HKEY) {
			result = tss_find_and_load_key(
			    hContext,
			    TPMTOK_PUBLIC_LEAF_KEY_ID,
			    &publicLeafKeyUUID, hPublicRootKey, hash_sha,
			    &hPublicLeafKey);
			if (result)
				return (CKR_FUNCTION_FAILED);
		}

		if ((rc = token_verify_pin(hContext, hPublicLeafKey))) {
			return (rc);
		}

		(void) memcpy(current_so_pin_sha, hash_sha, SHA1_DIGEST_LENGTH);
	}

	return (rc);
}

CK_RV
token_specific_logout(TSS_HCONTEXT hContext)
{
	if (hPrivateLeafKey != NULL_HKEY) {
		Tspi_Key_UnloadKey(hPrivateLeafKey);
		hPrivateLeafKey = NULL_HKEY;
	} else if (hPublicLeafKey != NULL_HKEY) {
		Tspi_Key_UnloadKey(hPublicLeafKey);
		hPublicLeafKey = NULL_HKEY;
	}

	local_uuid_clear(&publicRootKeyUUID);
	local_uuid_clear(&publicLeafKeyUUID);
	local_uuid_clear(&privateRootKeyUUID);
	local_uuid_clear(&privateLeafKeyUUID);

	(void) memset(current_so_pin_sha, 0, SHA1_DIGEST_LENGTH);
	(void) memset(current_user_pin_sha, 0, SHA1_DIGEST_LENGTH);

	(void) object_mgr_purge_private_token_objects(hContext);

	return (CKR_OK);
}

/*ARGSUSED*/
CK_RV
token_specific_init_pin(TSS_HCONTEXT hContext,
	CK_CHAR_PTR pPin, CK_ULONG ulPinLen)
{
	/*
	 * Since the SO must log in before calling C_InitPIN, we will
	 * be able to return (CKR_OK) automatically here.
	 * This is because the USER key structure is created at the
	 * time of their first login, not at C_InitPIN time.
	 */
	return (CKR_OK);
}

static CK_RV
check_pin_properties(CK_USER_TYPE userType, CK_BYTE *pinHash,
	CK_ULONG ulPinLen)
{
	/* make sure the new PIN is different */
	if (userType == CKU_USER) {
		if (!memcmp(pinHash, default_user_pin_sha,
		    SHA1_DIGEST_LENGTH)) {
			LogError1("new PIN must not be the default");
			return (CKR_PIN_INVALID);
		}
	} else {
		if (!memcmp(pinHash, default_so_pin_sha,
		    SHA1_DIGEST_LENGTH)) {
			LogError1("new PIN must not be the default");
			return (CKR_PIN_INVALID);
		}
	}

	if (ulPinLen > MAX_PIN_LEN || ulPinLen < MIN_PIN_LEN) {
		LogError1("New PIN is out of size range");
		return (CKR_PIN_LEN_RANGE);
	}

	return (CKR_OK);
}

/*
 * This function is called from set_pin only, where a non-logged-in public
 * session can provide the user pin which must be verified. This function
 * assumes that the pin has already been set once, so there's no migration
 * path option or checking of the default user pin.
 */
static CK_RV
verify_user_pin(TSS_HCONTEXT hContext, CK_BYTE *hash_sha)
{
	CK_RV rc;
	TSS_RESULT result;
	TSS_HKEY hSRK;

	if (token_load_srk(hContext, &hSRK))
		return (CKR_FUNCTION_FAILED);

	/*
	 * Verify the user by loading the privateLeafKey
	 * into the TPM (if it's not already) and then
	 * call the verify_pin operation.
	 *
	 * The hashed PIN is assigned to the private leaf key.
	 * If it is incorrect (not the same as the one originally
	 * used when the key was created), the verify operation
	 * will fail.
	 */
	if (hPrivateRootKey == NULL_HKEY) {
		result = tss_find_and_load_key(
		    hContext,
		    TPMTOK_PRIVATE_ROOT_KEY_ID,
		    &privateRootKeyUUID, hSRK, NULL, &hPrivateRootKey);
		if (result)
			return (CKR_FUNCTION_FAILED);
	}

	if (hPrivateLeafKey == NULL_HKEY) {
		result = tss_find_and_load_key(
		    hContext,
		    TPMTOK_PRIVATE_LEAF_KEY_ID,
		    &privateLeafKeyUUID, hPrivateRootKey, hash_sha,
		    &hPrivateLeafKey);

		if (result)
			return (CKR_FUNCTION_FAILED);
	}

	/*
	 * Verify that the PIN is correct by attempting to wrap/unwrap some
	 * random data.
	 */
	if ((rc = token_verify_pin(hContext, hPrivateLeafKey))) {
		return (rc);
	}

	return (CKR_OK);
}

CK_RV
token_specific_set_pin(ST_SESSION_HANDLE session,
	CK_CHAR_PTR pOldPin, CK_ULONG ulOldPinLen,
	CK_CHAR_PTR pNewPin, CK_ULONG ulNewPinLen)
{
	SESSION		*sess = session_mgr_find(session.sessionh);
	CK_BYTE		oldpin_hash[SHA1_DIGEST_LENGTH];
	CK_BYTE		newpin_hash[SHA1_DIGEST_LENGTH];
	CK_RV		rc;
	TSS_HKEY	hSRK;

	if (!sess) {
		return (CKR_SESSION_HANDLE_INVALID);
	}

	if ((rc = compute_sha(pOldPin, ulOldPinLen, oldpin_hash))) {
		return (CKR_FUNCTION_FAILED);
	}
	if ((rc = compute_sha(pNewPin, ulNewPinLen, newpin_hash))) {
		return (CKR_FUNCTION_FAILED);
	}

	if (token_load_srk(sess->hContext, &hSRK)) {
		return (CKR_FUNCTION_FAILED);
	}

	/*
	 * From the PKCS#11 2.20 spec: "C_SetPIN modifies the PIN of
	 * the user that is currently logged in, or the CKU_USER PIN
	 * if the session is not logged in."
	 * A non R/W session fails with CKR_SESSION_READ_ONLY.
	 */
	if (sess->session_info.state == CKS_RW_USER_FUNCTIONS ||
	    sess->session_info.state == CKS_RW_PUBLIC_SESSION) {
		if (not_initialized) {
			if (memcmp(oldpin_hash, default_user_pin_sha,
			    SHA1_DIGEST_LENGTH)) {
				return (CKR_PIN_INCORRECT);
			}

			if ((rc = check_pin_properties(CKU_USER, newpin_hash,
			    ulNewPinLen))) {
				return (rc);
			}

			if ((rc = token_create_private_tree(sess->hContext,
			    newpin_hash))) {
				return (CKR_FUNCTION_FAILED);
			}

			nv_token_data->token_info.flags &=
			    ~(CKF_USER_PIN_TO_BE_CHANGED);
			nv_token_data->token_info.flags |=
			    CKF_USER_PIN_INITIALIZED;

			nv_token_data->token_info.flags &=
			    ~(CKF_USER_PIN_TO_BE_CHANGED);
			nv_token_data->token_info.flags |=
			    CKF_USER_PIN_INITIALIZED;

			return (save_token_data(nv_token_data));
		}

		if (sess->session_info.state == CKS_RW_USER_FUNCTIONS) {
			/* if we're already logged in, just verify the hash */
			if (memcmp(current_user_pin_sha, oldpin_hash,
			    SHA1_DIGEST_LENGTH)) {
				return (CKR_PIN_INCORRECT);
			}
		} else {
			if ((rc = verify_user_pin(sess->hContext,
			    oldpin_hash))) {
				return (rc);
			}
		}

		if ((rc = check_pin_properties(CKU_USER, newpin_hash,
		    ulNewPinLen)))
			return (rc);

		/* change the auth on the TSS object */
		if (tss_change_auth(sess->hContext,
		    hPrivateLeafKey, hPrivateRootKey,
		    privateLeafKeyUUID, privateRootKeyUUID,
		    newpin_hash))
			return (CKR_FUNCTION_FAILED);

	} else if (sess->session_info.state == CKS_RW_SO_FUNCTIONS) {
		if (not_initialized) {
			if (memcmp(default_so_pin_sha, oldpin_hash,
			    SHA1_DIGEST_LENGTH))
				return (CKR_PIN_INCORRECT);

			if ((rc = check_pin_properties(CKU_SO,
			    newpin_hash, ulNewPinLen)))
				return (rc);

			if ((rc = token_create_public_tree(sess->hContext,
			    newpin_hash)))
				return (CKR_FUNCTION_FAILED);

			nv_token_data->token_info.flags &=
			    ~(CKF_SO_PIN_TO_BE_CHANGED);

			return (save_token_data(nv_token_data));
		}

		if (memcmp(current_so_pin_sha, oldpin_hash,
		    SHA1_DIGEST_LENGTH))
			return (CKR_PIN_INCORRECT);

		if ((rc = check_pin_properties(CKU_SO, newpin_hash,
		    ulNewPinLen)))
			return (rc);

		/* change auth on the SO's leaf key */
		if (tss_change_auth(sess->hContext,
		    hPublicLeafKey, hPublicRootKey,
		    publicLeafKeyUUID, publicRootKeyUUID,
		    newpin_hash))
			return (CKR_FUNCTION_FAILED);

	} else {
		rc = CKR_SESSION_READ_ONLY;
	}

	return (rc);
}

/* only called at token init time */
CK_RV
token_specific_verify_so_pin(TSS_HCONTEXT hContext, CK_CHAR_PTR pPin,
    CK_ULONG ulPinLen)
{
	CK_BYTE hash_sha[SHA1_DIGEST_LENGTH];
	CK_RV rc;
	TSS_RESULT result;
	TSS_HKEY hSRK;

	if ((rc = compute_sha(pPin, ulPinLen, hash_sha))) {
		return (CKR_FUNCTION_FAILED);
	}
	if ((rc = token_load_srk(hContext, &hSRK))) {
		return (CKR_FUNCTION_FAILED);
	}

	/*
	 * TRYME INSTEAD:
	 * - find publicRootKeyUUID
	 * - Load publicRootKey by UUID (SRK parent)
	 * - find publicLeafKeyUUID
	 * - Load publicLeafKey by UUID (publicRootKey parent)
	 * - set password policy on publicLeafKey
	 */
	if (local_uuid_is_null(&publicRootKeyUUID) &&
	    find_uuid(TPMTOK_PUBLIC_ROOT_KEY_ID, &publicRootKeyUUID)) {
		/*
		 * The SO hasn't set their PIN yet, compare the
		 * login pin with the hard-coded value.
		 */
		if (memcmp(default_so_pin_sha, hash_sha,
		    SHA1_DIGEST_LENGTH)) {
			return (CKR_PIN_INCORRECT);
		}
		return (CKR_OK);
	}

	result = Tspi_Context_GetKeyByUUID(hContext,
	    TSS_PS_TYPE_USER, publicRootKeyUUID, &hPublicRootKey);

	if (result)
		return (CKR_FUNCTION_FAILED);

	result = Tspi_Key_LoadKey(hPublicRootKey, hSRK);
	if (result)
		return (CKR_FUNCTION_FAILED);

	if (local_uuid_is_null(&publicLeafKeyUUID) &&
	    find_uuid(TPMTOK_PUBLIC_LEAF_KEY_ID, &publicLeafKeyUUID))
		return (CKR_FUNCTION_FAILED);

	result = Tspi_Context_GetKeyByUUID(hContext,
	    TSS_PS_TYPE_USER, publicLeafKeyUUID, &hPublicLeafKey);
	if (result)
		return (CKR_FUNCTION_FAILED);

	result = tss_assign_secret_key_policy(hContext, TSS_POLICY_USAGE,
	    hPublicLeafKey, hash_sha);
	if (result)
		return (CKR_FUNCTION_FAILED);

	result = Tspi_Key_LoadKey(hPublicLeafKey, hPublicRootKey);
	if (result)
		return (CKR_FUNCTION_FAILED);

	/* If the hash given is wrong, the verify will fail */
	if ((rc = token_verify_pin(hContext, hPublicLeafKey))) {
		return (rc);
	}

	return (CKR_OK);
}

CK_RV
token_specific_final(TSS_HCONTEXT hContext)
{
	if (hPublicRootKey != NULL_HKEY) {
		Tspi_Context_CloseObject(hContext, hPublicRootKey);
		hPublicRootKey = NULL_HKEY;
	}
	if (hPublicLeafKey != NULL_HKEY) {
		Tspi_Context_CloseObject(hContext, hPublicLeafKey);
		hPublicLeafKey = NULL_HKEY;
	}
	if (hPrivateRootKey != NULL_HKEY) {
		Tspi_Context_CloseObject(hContext, hPrivateRootKey);
		hPrivateRootKey = NULL_HKEY;
	}
	if (hPrivateLeafKey != NULL_HKEY) {
		Tspi_Context_CloseObject(hContext, hPrivateLeafKey);
		hPrivateLeafKey = NULL_HKEY;
	}
	return (CKR_OK);
}

/*
 * Wrap the 20 bytes of auth data and store in an attribute of the two
 * keys.
 */
static CK_RV
token_wrap_auth_data(TSS_HCONTEXT hContext,
	CK_BYTE *authData, TEMPLATE *publ_tmpl,
	TEMPLATE *priv_tmpl)
{
	CK_RV		rc;
	CK_ATTRIBUTE	*new_attr;

	TSS_RESULT	ret;
	TSS_HKEY	hParentKey;
	TSS_HENCDATA	hEncData;
	BYTE		*blob;
	UINT32		blob_size;

	if ((hPrivateLeafKey == NULL_HKEY) && (hPublicLeafKey == NULL_HKEY)) {
		return (CKR_FUNCTION_FAILED);
	} else if (hPublicLeafKey != NULL_HKEY) {
		hParentKey = hPublicLeafKey;
	} else {
		hParentKey = hPrivateLeafKey;
	}

	/* create the encrypted data object */
	if ((ret = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_BIND, &hEncData))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    ret, Trspi_Error_String(ret));
		return (CKR_FUNCTION_FAILED);
	}

	if ((ret = Tspi_Data_Bind(hEncData, hParentKey, SHA1_DIGEST_LENGTH,
	    authData))) {
		stlogit("Tspi_Data_Bind: 0x%0x - %s",
		    ret, Trspi_Error_String(ret));
		return (CKR_FUNCTION_FAILED);
	}

	/* pull the encrypted data out of the encrypted data object */
	if ((ret = Tspi_GetAttribData(hEncData, TSS_TSPATTRIB_ENCDATA_BLOB,
	    TSS_TSPATTRIB_ENCDATABLOB_BLOB, &blob_size, &blob))) {
		stlogit("Tspi_SetAttribData: 0x%0x - %s",
		    ret, Trspi_Error_String(ret));
		return (CKR_FUNCTION_FAILED);
	}

	if ((rc = build_attribute(CKA_ENC_AUTHDATA, blob, blob_size,
	    &new_attr))) {
		return (rc);
	}
	(void) template_update_attribute(publ_tmpl, new_attr);

	if ((rc = build_attribute(CKA_ENC_AUTHDATA, blob,
	    blob_size, &new_attr))) {
		return (rc);
	}
	(void) template_update_attribute(priv_tmpl, new_attr);

	return (rc);
}

static CK_RV
token_unwrap_auth_data(TSS_HCONTEXT hContext, CK_BYTE *encAuthData,
	CK_ULONG encAuthDataLen, TSS_HKEY hKey,
	BYTE **authData)
{
	TSS_RESULT	result;
	TSS_HENCDATA	hEncData;
	BYTE		*buf;
	UINT32		buf_size;

	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_BIND, &hEncData))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	if ((result = Tspi_SetAttribData(hEncData,
	    TSS_TSPATTRIB_ENCDATA_BLOB, TSS_TSPATTRIB_ENCDATABLOB_BLOB,
	    encAuthDataLen, encAuthData))) {
		stlogit("Tspi_SetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* unbind the data, receiving the plaintext back */
	if ((result = Tspi_Data_Unbind(hEncData, hKey, &buf_size, &buf))) {
		stlogit("Tspi_Data_Unbind: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	if (buf_size != SHA1_DIGEST_LENGTH) {
		return (CKR_FUNCTION_FAILED);
	}

	*authData = buf;

	return (CKR_OK);
}

CK_RV
token_specific_rsa_generate_keypair(
	TSS_HCONTEXT hContext,
	TEMPLATE  *publ_tmpl,
	TEMPLATE  *priv_tmpl)
{
	CK_ATTRIBUTE	*attr = NULL;
	CK_ULONG	mod_bits = 0;
	CK_BBOOL	flag;
	CK_RV		rc;

	TSS_FLAG	initFlags = 0;
	BYTE		authHash[SHA1_DIGEST_LENGTH];
	BYTE		*authData = NULL;
	TSS_HKEY	hKey = NULL_HKEY;
	TSS_HKEY	hParentKey = NULL_HKEY;
	TSS_RESULT	result;
	UINT32		ulBlobLen;
	BYTE		*rgbBlob;

	/* Make sure the public exponent is usable */
	if ((util_check_public_exponent(publ_tmpl))) {
		return (CKR_TEMPLATE_INCONSISTENT);
	}

	flag = template_attribute_find(publ_tmpl, CKA_MODULUS_BITS, &attr);
	if (!flag) {
		return (CKR_TEMPLATE_INCOMPLETE);
	}
	mod_bits = *(CK_ULONG *)attr->pValue;

	if ((initFlags = util_get_keysize_flag(mod_bits)) == 0) {
		return (CKR_KEY_SIZE_RANGE);
	}

	/*
	 * If we're not logged in, hPrivateLeafKey and hPublicLeafKey
	 * should be NULL.
	 */
	if ((hPrivateLeafKey == NULL_HKEY) &&
	    (hPublicLeafKey == NULL_HKEY)) {
		/* public session, wrap key with the PRK */
		initFlags |= TSS_KEY_TYPE_LEGACY |
		    TSS_KEY_NO_AUTHORIZATION | TSS_KEY_MIGRATABLE;

		if ((result = token_load_public_root_key(hContext))) {
			return (CKR_FUNCTION_FAILED);
		}

		hParentKey = hPublicRootKey;
	} else if (hPrivateLeafKey != NULL_HKEY) {
		/* logged in USER session */
		initFlags |= TSS_KEY_TYPE_LEGACY |
		    TSS_KEY_AUTHORIZATION | TSS_KEY_MIGRATABLE;

		/* get a random SHA1 hash for the auth data */
		if ((rc = token_rng(hContext, authHash, SHA1_DIGEST_LENGTH))) {
			return (CKR_FUNCTION_FAILED);
		}

		authData = authHash;
		hParentKey = hPrivateRootKey;
	} else {
		/* logged in SO session */
		initFlags |= TSS_KEY_TYPE_LEGACY |
		    TSS_KEY_AUTHORIZATION | TSS_KEY_MIGRATABLE;

		/* get a random SHA1 hash for the auth data */
		if ((rc = token_rng(hContext, authHash, SHA1_DIGEST_LENGTH))) {
			return (CKR_FUNCTION_FAILED);
		}

		authData = authHash;
		hParentKey = hPublicRootKey;
	}

	if ((result = tss_generate_key(hContext, initFlags, authData,
	    hParentKey, &hKey))) {
		return (result);
	}

	if ((result = Tspi_GetAttribData(hKey, TSS_TSPATTRIB_KEY_BLOB,
	    TSS_TSPATTRIB_KEYBLOB_BLOB, &ulBlobLen, &rgbBlob))) {
		stlogit("Tspi_GetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	if ((rc = build_attribute(CKA_IBM_OPAQUE, rgbBlob,
	    ulBlobLen, &attr))) {
		Tspi_Context_FreeMemory(hContext, rgbBlob);
		return (rc);
	}
	(void) template_update_attribute(priv_tmpl, attr);
	if ((rc = build_attribute(CKA_IBM_OPAQUE, rgbBlob,
	    ulBlobLen, &attr))) {
		Tspi_Context_FreeMemory(hContext, rgbBlob);
		return (rc);
	}
	(void) template_update_attribute(publ_tmpl, attr);

	Tspi_Context_FreeMemory(hContext, rgbBlob);

	/* grab the public key to put into the public key object */
	if ((result = Tspi_GetAttribData(hKey, TSS_TSPATTRIB_RSAKEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &ulBlobLen, &rgbBlob))) {
		stlogit("Tspi_GetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}

	/* add the public key blob to the object template */
	if ((rc = build_attribute(CKA_MODULUS, rgbBlob, ulBlobLen, &attr))) {
		Tspi_Context_FreeMemory(hContext, rgbBlob);
		return (rc);
	}
	(void) template_update_attribute(publ_tmpl, attr);

	/* add the public key blob to the object template */
	if ((rc = build_attribute(CKA_MODULUS, rgbBlob, ulBlobLen, &attr))) {
		Tspi_Context_FreeMemory(hContext, rgbBlob);
		return (rc);
	}
	(void) template_update_attribute(priv_tmpl, attr);
	Tspi_Context_FreeMemory(hContext, rgbBlob);

	/* wrap the authdata and put it into an object */
	if (authData != NULL) {
		rc = token_wrap_auth_data(hContext, authData, publ_tmpl,
		    priv_tmpl);
	}

	return (rc);
}

static CK_RV
token_rsa_load_key(
	TSS_HCONTEXT hContext,
	OBJECT *key_obj,
	TSS_HKEY *phKey)
{
	TSS_RESULT result;
	TSS_HPOLICY hPolicy = NULL_HPOLICY;
	TSS_HKEY	hParentKey;
	BYTE		*authData = NULL;
	CK_ATTRIBUTE	*attr;
	CK_RV		rc;
	CK_OBJECT_HANDLE handle;
	CK_ULONG	class;

	if (hPrivateLeafKey != NULL_HKEY) {
		hParentKey = hPrivateRootKey;
	} else {
		if ((result = token_load_public_root_key(hContext)))
			return (CKR_FUNCTION_FAILED);

		hParentKey = hPublicRootKey;
	}

	*phKey = 0;
	if (template_attribute_find(key_obj->template, CKA_CLASS,
	    &attr) == FALSE) {
		return (CKR_TEMPLATE_INCOMPLETE);
	}
	class = *((CK_ULONG *)attr->pValue);

	rc = template_attribute_find(key_obj->template,
	    CKA_IBM_OPAQUE, &attr);
	/*
	 * A public key cannot use the OPAQUE data attribute so they
	 * must be created in software.  A private key may not yet
	 * have its "opaque" data defined and needs to be created
	 * and loaded so it can be used inside the TPM.
	 */
	if (class == CKO_PUBLIC_KEY || rc == FALSE) {
		rc = object_mgr_find_in_map2(hContext, key_obj, &handle);
		if (rc != CKR_OK)
			return (CKR_FUNCTION_FAILED);

		if ((rc = token_load_key(hContext,
		    handle, hParentKey, NULL, phKey))) {
			return (rc);
		}
	}
	/*
	 * If this is a private key, get the blob and load it in the TPM.
	 * If it is public, the key is already loaded in software.
	 */
	if (class == CKO_PRIVATE_KEY) {
		/* If we already have a handle, just load it */
		if (*phKey != 0) {
			result = Tspi_Key_LoadKey(*phKey, hParentKey);
			if (result) {
				stlogit("Tspi_Context_LoadKeyByBlob: "
				    "0x%0x - %s",
				    result, Trspi_Error_String(result));
				return (CKR_FUNCTION_FAILED);
			}
		} else {
			/* try again to get the CKA_IBM_OPAQUE attr */
			if ((rc = template_attribute_find(key_obj->template,
			    CKA_IBM_OPAQUE, &attr)) == FALSE) {
				return (rc);
			}
			if ((result = Tspi_Context_LoadKeyByBlob(hContext,
			    hParentKey, attr->ulValueLen, attr->pValue,
			    phKey))) {
				stlogit("Tspi_Context_LoadKeyByBlob: "
				    "0x%0x - %s",
				    result, Trspi_Error_String(result));
				return (CKR_FUNCTION_FAILED);
			}
		}
	}

	/* auth data may be required */
	if (template_attribute_find(key_obj->template, CKA_ENC_AUTHDATA,
	    &attr) == TRUE && attr) {
		if ((hPrivateLeafKey == NULL_HKEY) &&
		    (hPublicLeafKey == NULL_HKEY)) {
			return (CKR_FUNCTION_FAILED);
		} else if (hPublicLeafKey != NULL_HKEY) {
			hParentKey = hPublicLeafKey;
		} else {
			hParentKey = hPrivateLeafKey;
		}

		if ((result = token_unwrap_auth_data(hContext,
		    attr->pValue, attr->ulValueLen,
		    hParentKey, &authData))) {
			return (CKR_FUNCTION_FAILED);
		}

		if ((result = Tspi_GetPolicyObject(*phKey,
		    TSS_POLICY_USAGE, &hPolicy))) {
			stlogit("Tspi_GetPolicyObject: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (CKR_FUNCTION_FAILED);
		}

		/*
		 * If the policy handle returned is the same as the
		 * context's default policy, then a new policy must
		 * be created and assigned to the key. Otherwise, just set the
		 * secret in the policy.
		 */
		if (hPolicy == hDefaultPolicy) {
			if ((result = Tspi_Context_CreateObject(hContext,
			    TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE,
			    &hPolicy))) {
				stlogit("Tspi_Context_CreateObject: "
				    "0x%0x - %s",
				    result, Trspi_Error_String(result));
				return (CKR_FUNCTION_FAILED);
			}

			if ((result = Tspi_Policy_SetSecret(hPolicy,
			    TSS_SECRET_MODE_SHA1,
			    SHA1_DIGEST_LENGTH, authData))) {
				stlogit("Tspi_Policy_SetSecret: "
				    "0x%0x - %s",
				    result, Trspi_Error_String(result));
				return (CKR_FUNCTION_FAILED);
			}

			if ((result = Tspi_Policy_AssignToObject(hPolicy,
			    *phKey))) {
				stlogit("Tspi_Policy_AssignToObject: "
				    "0x%0x - %s",
				    result, Trspi_Error_String(result));
				return (CKR_FUNCTION_FAILED);
			}
		} else if ((result = Tspi_Policy_SetSecret(hPolicy,
		    TSS_SECRET_MODE_SHA1, SHA1_DIGEST_LENGTH, authData))) {
			stlogit("Tspi_Policy_SetSecret: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (CKR_FUNCTION_FAILED);
		}

		Tspi_Context_FreeMemory(hContext, authData);
	}

	return (CKR_OK);
}

CK_RV
tpm_decrypt_data(
	TSS_HCONTEXT hContext,
	TSS_HKEY    hKey,
	CK_BYTE   * in_data,
	CK_ULONG    in_data_len,
	CK_BYTE   * out_data,
	CK_ULONG  * out_data_len)
{
	TSS_RESULT result;
	TSS_HENCDATA	hEncData = NULL_HENCDATA;
	UINT32		buf_size = 0, modLen;
	BYTE		*buf = NULL, *modulus = NULL;
	CK_ULONG	chunklen, remain, outlen;

	/* push the data into the encrypted data object */
	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_BIND, &hEncData))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/*
	 * Figure out the modulus size so we can break the data
	 * into smaller chunks if necessary.
	 */
	if ((result = Tspi_GetAttribData(hKey, TSS_TSPATTRIB_RSAKEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &modLen, &modulus))) {
		stlogit("Tspi_GetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}
	/* we don't need the actual modulus */
	Tspi_Context_FreeMemory(hContext, modulus);

	chunklen = (in_data_len > modLen ? modLen : in_data_len);
	remain = in_data_len;
	outlen = 0;

	while (remain > 0) {
		if ((result = Tspi_SetAttribData(hEncData,
		    TSS_TSPATTRIB_ENCDATA_BLOB,
		    TSS_TSPATTRIB_ENCDATABLOB_BLOB,
		    chunklen, in_data))) {
			stlogit("Tspi_SetAttribData: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (CKR_FUNCTION_FAILED);
		}

		/* unbind the data, receiving the plaintext back */
		if ((result = Tspi_Data_Unbind(hEncData, hKey,
		    &buf_size, &buf))) {
			stlogit("Tspi_Data_Unbind: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (CKR_FUNCTION_FAILED);
		}

		if (*out_data_len < buf_size + outlen) {
			Tspi_Context_FreeMemory(hContext, buf);
			return (CKR_BUFFER_TOO_SMALL);
		}

		(void) memcpy(out_data + outlen, buf, buf_size);

		outlen += buf_size;
		in_data += chunklen;
		remain -= chunklen;

		Tspi_Context_FreeMemory(hContext, buf);
		if (chunklen > remain)
			chunklen = remain;
	}
	*out_data_len = outlen;
	return (CKR_OK);
}

CK_RV
token_specific_rsa_decrypt(
	TSS_HCONTEXT hContext,
	CK_BYTE   * in_data,
	CK_ULONG    in_data_len,
	CK_BYTE   * out_data,
	CK_ULONG  * out_data_len,
	OBJECT	  * key_obj)
{
	CK_RV		rc;
	TSS_HKEY	hKey;

	if ((rc = token_rsa_load_key(hContext, key_obj, &hKey))) {
		return (rc);
	}

	rc = tpm_decrypt_data(hContext, hKey, in_data, in_data_len,
	    out_data, out_data_len);

	return (rc);
}

CK_RV
token_specific_rsa_verify(
	TSS_HCONTEXT hContext,
	CK_BYTE   * in_data,
	CK_ULONG    in_data_len,
	CK_BYTE   * sig,
	CK_ULONG    sig_len,
	OBJECT	  * key_obj)
{
	TSS_RESULT	result;
	TSS_HHASH	hHash;
	TSS_HKEY	hKey;
	CK_RV		rc;

	if ((rc = token_rsa_load_key(hContext, key_obj, &hKey))) {
		return (rc);
	}

	/* Create the hash object we'll use to sign */
	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_HASH, TSS_HASH_OTHER, &hHash))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* Insert the data into the hash object */
	if ((result = Tspi_Hash_SetHashValue(hHash, in_data_len,
	    in_data))) {
		stlogit("Tspi_Hash_SetHashValue: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* Verify */
	result = Tspi_Hash_VerifySignature(hHash, hKey, sig_len, sig);
	if (result != TSS_SUCCESS &&
	    TPMTOK_TSS_ERROR_CODE(result) != TSS_E_FAIL) {
		stlogit("Tspi_Hash_VerifySignature: 0x%0x - %s",
		    result, Trspi_Error_String(result));
	}

	if (TPMTOK_TSS_ERROR_CODE(result) == TSS_E_FAIL) {
		rc = CKR_SIGNATURE_INVALID;
	} else {
		rc = CKR_OK;
	}

	return (rc);
}

CK_RV
token_specific_rsa_sign(
	TSS_HCONTEXT hContext,
	CK_BYTE   * in_data,
	CK_ULONG    in_data_len,
	CK_BYTE   * out_data,
	CK_ULONG  * out_data_len,
	OBJECT	  * key_obj)
{
	TSS_RESULT	result;
	TSS_HHASH	hHash;
	BYTE		*sig;
	UINT32		sig_len;
	TSS_HKEY	hKey;
	CK_RV		rc;

	if ((rc = token_rsa_load_key(hContext, key_obj, &hKey))) {
		return (rc);
	}

	/* Create the hash object we'll use to sign */
	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_HASH, TSS_HASH_OTHER, &hHash))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* Insert the data into the hash object */
	if ((result = Tspi_Hash_SetHashValue(hHash, in_data_len,
	    in_data))) {
		stlogit("Tspi_Hash_SetHashValue: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	/* Sign */
	if ((result = Tspi_Hash_Sign(hHash, hKey, &sig_len, &sig))) {
		stlogit("Tspi_Hash_Sign: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_DATA_LEN_RANGE);
	}

	if (sig_len > *out_data_len) {
		Tspi_Context_FreeMemory(hContext, sig);
		return (CKR_BUFFER_TOO_SMALL);
	}

	(void) memcpy(out_data, sig, sig_len);
	*out_data_len = sig_len;
	Tspi_Context_FreeMemory(hContext, sig);

	return (CKR_OK);
}

CK_RV
tpm_encrypt_data(
	TSS_HCONTEXT hContext,
	TSS_HKEY hKey,
	CK_BYTE *in_data,
	CK_ULONG in_data_len,
	CK_BYTE *out_data,
	CK_ULONG *out_data_len)
{
	TSS_RESULT	result;
	TSS_HENCDATA	hEncData;
	BYTE		*dataBlob, *modulus;
	UINT32		dataBlobSize, modLen;
	CK_ULONG	chunklen, remain;
	CK_ULONG	outlen;
	UINT32		keyusage, scheme, maxsize;

	if ((result = Tspi_Context_CreateObject(hContext,
	    TSS_OBJECT_TYPE_ENCDATA, TSS_ENCDATA_BIND, &hEncData))) {
		stlogit("Tspi_Context_CreateObject: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}
	/*
	 * Figure out the modulus size so we can break the data
	 * into smaller chunks if necessary.
	 */
	if ((result = Tspi_GetAttribData(hKey, TSS_TSPATTRIB_RSAKEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &modLen, &modulus))) {
		stlogit("Tspi_GetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (result);
	}
	/* we don't need the actual modulus */
	Tspi_Context_FreeMemory(hContext, modulus);

	/*
	 * According to TSS spec for Tspi_Data_Bind (4.3.4.21.5),
	 * Max input data size varies depending on the key type and
	 * encryption scheme.
	 */
	if ((result = Tspi_GetAttribUint32(hKey, TSS_TSPATTRIB_KEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_USAGE, &keyusage))) {
		stlogit("Cannot find USAGE: %s\n",
		    Trspi_Error_String(result));
		return (result);
	}
	if ((result = Tspi_GetAttribUint32(hKey, TSS_TSPATTRIB_KEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_ENCSCHEME, &scheme))) {
		stlogit("Cannot find ENCSCHEME: %s\n",
		    Trspi_Error_String(result));
		return (result);
	}
	switch (scheme) {
		case TSS_ES_RSAESPKCSV15:
			if (keyusage == TSS_KEYUSAGE_BIND)
				maxsize = 16;
			else /* legacy */
				maxsize = 11;
			break;
		case TSS_ES_RSAESOAEP_SHA1_MGF1:
			maxsize = 47;
			break;
		default:
			maxsize = 0;
	}

	modLen -= maxsize;

	chunklen = (in_data_len > modLen ? modLen : in_data_len);
	remain = in_data_len;
	outlen = 0;
	while (remain > 0) {
		if ((result = Tspi_Data_Bind(hEncData, hKey,
		    chunklen, in_data))) {
			stlogit("Tspi_Data_Bind: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (CKR_FUNCTION_FAILED);
		}

		if ((result = Tspi_GetAttribData(hEncData,
		    TSS_TSPATTRIB_ENCDATA_BLOB,
		    TSS_TSPATTRIB_ENCDATABLOB_BLOB,
		    &dataBlobSize, &dataBlob))) {
			stlogit("Tspi_GetAttribData: 0x%0x - %s",
			    result, Trspi_Error_String(result));
			return (CKR_FUNCTION_FAILED);
		}

		if (outlen + dataBlobSize > *out_data_len) {
			Tspi_Context_FreeMemory(hContext, dataBlob);
			return (CKR_DATA_LEN_RANGE);
		}

		(void) memcpy(out_data + outlen,
		    dataBlob, dataBlobSize);

		outlen += dataBlobSize;
		in_data += chunklen;
		remain -= chunklen;

		if (chunklen > remain)
			chunklen = remain;

		Tspi_Context_FreeMemory(hContext, dataBlob);
	}
	*out_data_len = outlen;

	return (CKR_OK);
}

CK_RV
token_specific_rsa_encrypt(
	TSS_HCONTEXT hContext,
	CK_BYTE   * in_data,
	CK_ULONG    in_data_len,
	CK_BYTE   * out_data,
	CK_ULONG  * out_data_len,
	OBJECT	  * key_obj)
{
	TSS_HKEY	hKey;
	CK_RV		rc;

	if ((rc = token_rsa_load_key(hContext, key_obj, &hKey))) {
		return (rc);
	}

	rc  = tpm_encrypt_data(hContext, hKey, in_data, in_data_len,
	    out_data, out_data_len);

	return (rc);
}

/*
 * RSA Verify Recover
 *
 * Public key crypto is done in software, not by the TPM.
 * We use libsoftcrypto and perform the RSA operations ourselves similar
 * to how pkcs11_softtoken performs the operation.
 */
CK_RV
token_specific_rsa_verify_recover(
	TSS_HCONTEXT	hContext,
	CK_BYTE_PTR	pSignature,
	CK_ULONG	ulSignatureLen,
	CK_BYTE_PTR	pData,
	CK_ULONG_PTR	pulDataLen,
	OBJECT		*key_obj)
{
	TSS_HKEY	hKey;
	TSS_RESULT	result;
	CK_RV		rc;
	BYTE		*modulus;
	UINT32		modLen;
	RSAbytekey	rsa = { 0 };
	uchar_t		exp[] = { 0x01, 0x00, 0x01 };
	CK_BYTE		plain_data[MAX_RSA_KEYLENGTH];
	size_t		data_len;

	if ((rc = token_rsa_load_key(hContext, key_obj, &hKey))) {
		return (rc);
	}

	if ((result = Tspi_GetAttribData(hKey, TSS_TSPATTRIB_RSAKEY_INFO,
	    TSS_TSPATTRIB_KEYINFO_RSA_MODULUS, &modLen, &modulus))) {
		stlogit("Tspi_GetAttribData: 0x%0x - %s",
		    result, Trspi_Error_String(result));
		return (CKR_FUNCTION_FAILED);
	}

	if (ulSignatureLen != modLen) {
		rc = CKR_SIGNATURE_LEN_RANGE;
		goto end;
	}

	rsa.modulus = modulus;
	rsa.modulus_bits = CRYPTO_BYTES2BITS(modLen);
	rsa.pubexpo = exp;
	rsa.pubexpo_bytes = sizeof (exp);

	if ((rc = rsa_encrypt(&rsa, pSignature, modLen, plain_data)) != CKR_OK)
		goto end;

	data_len = modLen;
	if ((rc = pkcs1_decode(PKCS1_VERIFY, plain_data, &data_len)) != CKR_OK)
		goto end;

	(void) memcpy(pData, &plain_data[modLen - data_len], data_len);
	*pulDataLen = data_len;

end:
	Tspi_Context_FreeMemory(hContext, modulus);
	return (rc);
}