root/usr/src/lib/pkcs11/pkcs11_tpm/common/utility.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.
 */

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

#include "tpmtok_int.h"

static CK_SLOT_INFO    slot_info;

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

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

        return (node);
}


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

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

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

                return (list);
        }
}


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

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

        return (node);
}


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

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

        return (temp);
}


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

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

        return (temp);
}


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

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

        return (len);
}


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


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


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

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

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

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

                free(list);
                return (temp);
        }

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

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

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

                free(node);
        }

        return (list);
}

extern void set_perm(int);

void
CreateXProcLock(void *xproc)
{
        pthread_mutexattr_t  mtxattr;

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

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

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

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

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

        return (FALSE);
}

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

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

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

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

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

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

        token_info = &td->token_info;

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

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

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

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

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

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

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

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

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

        td->tweak_vector.allow_key_mods   = TRUE;

        init_token_info(td);

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

        rc = save_token_data(td);

        return (rc);
}

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

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

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

        val[0]++;

        i = 0;

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

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

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

        return (CKR_OK);
}


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

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

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

         *attrib = attr;

        return (CKR_OK);
}

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

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

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

        return (CKR_OK);
}

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

        pad_value = ptr[total_len - 1];

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

        return (CKR_OK);
}

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

        ptr = attr->pValue;

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

        new_len = attr->ulValueLen - i;

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

        return (CKR_OK);
}

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

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

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

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

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

        return (CKR_OK);
}

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

        return (CKR_OK);
}

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

        SHA1Init(&ctx);

        SHA1Update(&ctx, data, len);

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