root/usr/src/lib/pkcs11/pkcs11_tpm/common/tpm_specific.c
/*
 * 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);
}