root/usr/src/lib/libkmf/libkmf/common/pk11tokens.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <security/cryptoki.h>
#include <kmfapi.h>
#include <kmfapiP.h>
#include <cryptoutil.h>

/*
 * memcmp_pad_max() is a specialized version of memcmp() which
 * compares two pieces of data up to a maximum length.  If the
 * the two data match up the maximum length, they are considered
 * matching.  Trailing blanks do not cause the match to fail if
 * one of the data is shorted.
 *
 * Examples of matches:
 *      "one"           |
 *      "one      "     |
 *                      ^maximum length
 *
 *      "Number One     |  X"   (X is beyond maximum length)
 *      "Number One   " |
 *                      ^maximum length
 *
 * Examples of mismatches:
 *      " one"
 *      "one"
 *
 *      "Number One    X|"
 *      "Number One     |"
 *                      ^maximum length
 */
static int
memcmp_pad_max(void *d1, uint_t d1_len, void *d2, uint_t d2_len, uint_t max_sz)
{
        uint_t          len, extra_len;
        char            *marker;

        /* No point in comparing anything beyond max_sz */
        if (d1_len > max_sz)
                d1_len = max_sz;
        if (d2_len > max_sz)
                d2_len = max_sz;

        /* Find shorter of the two data. */
        if (d1_len <= d2_len) {
                len = d1_len;
                extra_len = d2_len;
                marker = d2;
        } else {        /* d1_len > d2_len */
                len = d2_len;
                extra_len = d1_len;
                marker = d1;
        }

        /* Have a match in the shortest length of data? */
        if (memcmp(d1, d2, len) != 0)
                /* CONSTCOND */
                return (1);

        /* If the rest of longer data is nulls or blanks, call it a match. */
        while (len < extra_len && marker[len])
                if (!isspace(marker[len++]))
                        /* CONSTCOND */
                        return (1);
        return (0);
}

static KMF_RETURN
kmf_get_token_slots(KMF_HANDLE *handle, CK_SLOT_ID_PTR *slot_list,
    CK_ULONG *slot_count)
{

        KMF_RETURN      kmf_rv = KMF_OK;
        CK_RV           ck_rv = CKR_OK;
        CK_ULONG        tmp_count = 0;
        CK_SLOT_ID_PTR  tmp_list = NULL_PTR, tmp2_list = NULL_PTR;

        ck_rv = C_GetSlotList(1, NULL_PTR, &tmp_count);
        if (ck_rv == CKR_CRYPTOKI_NOT_INITIALIZED) {
                ck_rv = C_Initialize(NULL);
                if ((ck_rv != CKR_OK) &&
                    (ck_rv != CKR_CRYPTOKI_ALREADY_INITIALIZED))
                        return (KMF_ERR_UNINITIALIZED);
                if (ck_rv == CKR_CRYPTOKI_ALREADY_INITIALIZED)
                        ck_rv = CKR_OK;

                ck_rv = C_GetSlotList(1, NULL_PTR, &tmp_count);
        }
        if (ck_rv != CKR_OK) {
                if (handle != NULL) {
                        handle->lasterr.kstype = KMF_KEYSTORE_PK11TOKEN;
                        handle->lasterr.errcode = ck_rv;
                }
                return (KMF_ERR_INTERNAL);
        }

        if (tmp_count == 0) {
                *slot_list = NULL_PTR;
                *slot_count = 0;
                return (KMF_OK);
        }

        /* Allocate initial space for the slot list. */
        if ((tmp_list = (CK_SLOT_ID_PTR) malloc(tmp_count *
            sizeof (CK_SLOT_ID))) == NULL)
                return (KMF_ERR_MEMORY);

        /* Then get the slot list itself. */
        for (;;) {
                ck_rv = C_GetSlotList(1, tmp_list, &tmp_count);
                if (ck_rv == CKR_OK) {
                        *slot_list = tmp_list;
                        *slot_count = tmp_count;
                        kmf_rv = KMF_OK;
                        break;
                }

                if (ck_rv != CKR_BUFFER_TOO_SMALL) {
                        free(tmp_list);
                        if (handle != NULL) {
                                handle->lasterr.kstype = KMF_KEYSTORE_PK11TOKEN;
                                handle->lasterr.errcode = ck_rv;
                        }
                        kmf_rv = KMF_ERR_INTERNAL;
                        break;
                }

                /*
                 * If the number of slots grew, try again. This
                 * is to be consistent with pktool in ONNV.
                 */
                if ((tmp2_list = (CK_SLOT_ID_PTR) realloc(tmp_list,
                    tmp_count * sizeof (CK_SLOT_ID))) == NULL) {
                        free(tmp_list);
                        kmf_rv = KMF_ERR_MEMORY;
                        break;
                }
                tmp_list = tmp2_list;
        }

        return (kmf_rv);
}

/*
 * Returns pointer to either null-terminator or next unescaped colon.  The
 * string to be extracted starts at the beginning and goes until one character
 * before this pointer.  If NULL is returned, the string itself is NULL.
 */
static char *
find_unescaped_colon(char *str)
{
        char *end;

        if (str == NULL)
                return (NULL);

        while ((end = strchr(str, ':')) != NULL) {
                if (end != str && *(end-1) != '\\')
                        return (end);
                str = end + 1;          /* could point to null-terminator */
        }
        if (end == NULL)
                end = strchr(str, '\0');
        return (end);
}

/*
 * Compresses away any characters escaped with backslash from given string.
 * The string is altered in-place.  Example, "ab\:\\e" becomes "ab:\e".
 */
static void
unescape_str(char *str)
{
        boolean_t       escaped = B_FALSE;
        char            *mark;

        if (str == NULL)
                return;

        for (mark = str; *str != '\0'; str++) {
                if (*str != '\\' || escaped == B_TRUE) {
                        *mark++ = *str;
                        escaped = B_FALSE;
                } else {
                        escaped = B_TRUE;
                }
        }
        *mark = '\0';
}


/*
 * Given a colon-separated token specifier, this functions splits it into
 * its label, manufacturer ID (if any), and serial number (if any).  Literal
 * colons within the label/manuf/serial can be escaped with a backslash.
 * Fields can left blank and trailing colons can be omitted, however leading
 * colons are required as placeholders.  For example, these are equivalent:
 *      (a) "lbl", "lbl:", "lbl::"      (b) "lbl:man", "lbl:man:"
 * but these are not:
 *      (c) "man", ":man"       (d) "ser", "::ser"
 * Furthermore, the token label is required always.
 *
 * The buffer containing the token specifier is altered by replacing the
 * colons to null-terminators, and pointers returned are pointers into this
 * string.  No new memory is allocated.
 */
static int
parse_token_spec(char *token_spec, char **token_name, char **manuf_id,
        char **serial_no)
{
        char    *mark;

        if (token_spec == NULL || *token_spec == '\0') {
                return (-1);
        }

        *token_name = NULL;
        *manuf_id = NULL;
        *serial_no = NULL;

        /* Token label (required) */
        mark = find_unescaped_colon(token_spec);
        *token_name = token_spec;
        if (*mark != '\0')
                *mark++ = '\0';         /* mark points to next field, if any */
        unescape_str(*token_name);

        if (*(*token_name) == '\0') {   /* token label is required */
                return (-1);
        }

        if (*mark == '\0' || *(mark+1) == '\0')         /* no more fields */
                return (0);
        token_spec = mark;

        /* Manufacturer identifier (optional) */
        mark = find_unescaped_colon(token_spec);
        *manuf_id = token_spec;
        if (*mark != '\0')
                *mark++ = '\0';         /* mark points to next field, if any */
        unescape_str(*manuf_id);

        if (*mark == '\0' || *(mark+1) == '\0')         /* no more fields */
                return (0);
        token_spec = mark;

        /* Serial number (optional) */
        mark = find_unescaped_colon(token_spec);
        *serial_no = token_spec;
        if (*mark != '\0')
                *mark++ = '\0';         /* null-terminate, just in case */
        unescape_str(*serial_no);

        return (0);
}

/*
 * Find slots that match a token identifier.  Token labels take the
 * form of:
 *      token_name:manufacturer:serial_number
 * manufacterer and serial number are optional.  If used, the fields
 * are delimited by the colon ':' character.
 */
KMF_RETURN
kmf_pk11_token_lookup(KMF_HANDLE_T handle, char *label, CK_SLOT_ID *slot_id)
{
        KMF_RETURN      kmf_rv = KMF_OK;
        CK_RV           rv;
        CK_SLOT_ID_PTR  slot_list = NULL;
        CK_TOKEN_INFO   token_info;
        CK_ULONG        slot_count = 0;
        int             i;
        uint_t          len, max_sz;
        boolean_t       metaslot_status_enabled;
        boolean_t       metaslot_migrate_enabled;
        char    *metaslot_slot_info;
        char    *metaslot_token_info;
        char    *tmplabel = NULL;
        char    *token_name = NULL;
        char    *manuf_id = NULL;
        char    *serial_no = NULL;
        boolean_t       tok_match = B_FALSE;
        boolean_t       man_match = B_FALSE;
        boolean_t       ser_match = B_FALSE;

        if (slot_id == NULL || label == NULL || !strlen(label))
                return (KMF_ERR_BAD_PARAMETER);

        if (handle == NULL) {
                rv = C_Initialize(NULL);
                if ((rv != CKR_OK) &&
                    (rv != CKR_CRYPTOKI_ALREADY_INITIALIZED)) {
                        return (KMF_ERR_UNINITIALIZED);
                }
        }

        /*
         * Parse token specifier into token_name, manuf_id, serial_no.
         * Token_name is required; manuf_id and serial_no are optional.
         */
        tmplabel = strdup(label);
        if (tmplabel == NULL)
                return (KMF_ERR_MEMORY);

        if (parse_token_spec(tmplabel, &token_name, &manuf_id,
            &serial_no) < 0) {
                free(tmplabel);
                return (KMF_ERR_BAD_PARAMETER);
        }

        /* Get a list of all slots with tokens present. */
        kmf_rv = kmf_get_token_slots(handle, &slot_list, &slot_count);
        if (kmf_rv != KMF_OK) {
                free(tmplabel);
                return (kmf_rv);
        }

        /* If there are no such slots, the desired token won't be found. */
        if (slot_count == 0) {
                free(tmplabel);
                return (KMF_ERR_TOKEN_NOT_PRESENT);
        }

        /* Search the slot list for the token. */
        for (i = 0; i < slot_count; i++) {
                if (C_GetTokenInfo(slot_list[i], &token_info) != CKR_OK) {
                        continue;
                }

                /* See if the token label matches. */
                len = strlen(token_name);
                max_sz = sizeof (token_info.label);
                if (memcmp_pad_max(&(token_info.label), max_sz, token_name,
                    len, max_sz) == 0)
                        tok_match = B_TRUE;
                /*
                 * If manufacturer id was given, see if it actually matches.
                 * If no manufacturer id was given, assume match is true.
                 */
                if (manuf_id) {
                        len = strlen(manuf_id);
                        max_sz = sizeof ((char *)(token_info.manufacturerID));
                        if (memcmp_pad_max(&(token_info.manufacturerID), max_sz,
                            manuf_id, len, max_sz) == 0)
                                man_match = B_TRUE;
                } else {
                        man_match = B_TRUE;
                }

                /*
                 * If serial number was given, see if it actually matches.
                 * If no serial number was given, assume match is true.
                 */
                if (serial_no) {
                        len = strlen(serial_no);
                        max_sz = sizeof ((char *)(token_info.serialNumber));
                        if (memcmp_pad_max(&(token_info.serialNumber), max_sz,
                            serial_no, len, max_sz) == 0)
                                ser_match = B_TRUE;
                } else {
                        ser_match = B_TRUE;
                }

                if (tok_match && man_match && ser_match)
                        break;          /* found it! */
        }

        if (i < slot_count) {
                /* found the desired token from the slotlist */
                *slot_id = slot_list[i];
                free(slot_list);
                free(tmplabel);
                return (KMF_OK);
        }

        /*
         * If we didn't find the token from the slotlist, check if this token
         * is the one currently hidden by the metaslot. If that's case,
         * we can just use the metaslot, the slot 0.
         */
        kmf_rv = get_metaslot_info(&metaslot_status_enabled,
            &metaslot_migrate_enabled, &metaslot_slot_info,
            &metaslot_token_info);
        if (kmf_rv) {
                /*
                 * Failed to get the metaslot info.  This usually means that
                 * metaslot is disabled from the system.
                 */
                kmf_rv = KMF_ERR_TOKEN_NOT_PRESENT;
        } else {
                max_sz = strlen(metaslot_token_info);
                if (memcmp_pad_max(metaslot_token_info, max_sz, token_name, len,
                    max_sz) == 0) {
                        *slot_id = slot_list[0];
                } else {
                        kmf_rv = KMF_ERR_TOKEN_NOT_PRESENT;
                }
                free(metaslot_slot_info);
                free(metaslot_token_info);
        }

        free(slot_list);
        free(tmplabel);
        return (kmf_rv);
}

KMF_RETURN
kmf_set_token_pin(KMF_HANDLE_T handle,
        int num_attr,
        KMF_ATTRIBUTE *attrlist)
{
        KMF_RETURN ret = KMF_OK;
        KMF_PLUGIN *plugin;
        KMF_ATTRIBUTE_TESTER required_attrs[] = {
                {KMF_KEYSTORE_TYPE_ATTR, FALSE, 1, sizeof (KMF_KEYSTORE_TYPE)},
                {KMF_CREDENTIAL_ATTR, FALSE, sizeof (KMF_CREDENTIAL),
                        sizeof (KMF_CREDENTIAL)},
                {KMF_NEWPIN_ATTR, FALSE, sizeof (KMF_CREDENTIAL),
                        sizeof (KMF_CREDENTIAL)},
        };

        int num_req_attrs = sizeof (required_attrs) /
            sizeof (KMF_ATTRIBUTE_TESTER);
        uint32_t len;
        KMF_KEYSTORE_TYPE kstype;

        if (handle == NULL)
                return (KMF_ERR_BAD_PARAMETER);

        CLEAR_ERROR(handle, ret);
        if (ret != KMF_OK)
                return (ret);

        ret = test_attributes(num_req_attrs, required_attrs,
            0, NULL, num_attr, attrlist);
        if (ret != KMF_OK)
                return (ret);

        len = sizeof (kstype);
        ret = kmf_get_attr(KMF_KEYSTORE_TYPE_ATTR, attrlist, num_attr,
            &kstype, &len);
        if (ret != KMF_OK)
                return (ret);

        plugin = FindPlugin(handle, kstype);
        if (plugin != NULL) {
                if (plugin->funclist->SetTokenPin != NULL)
                        return (plugin->funclist->SetTokenPin(handle, num_attr,
                            attrlist));
                else
                        return (KMF_ERR_FUNCTION_NOT_FOUND);
        }
        return (KMF_ERR_PLUGIN_NOTFOUND);
}

/*
 * Name: kmf_select_token
 *
 * Description:
 *   This function enables the user of PKCS#11 plugin to select a
 *   particular PKCS#11 token. Valid token label are required in order to
 *   successfully complete this function.
 *   All subsequent KMF APIs, which specify PKCS#11 keystore as
 *   the backend, will be performed at the selected token.
 *
 * Parameters:
 *   label(input) - pointer to the token label
 *
 * Returns:
 *   A KMF_RETURN value indicating success or specifying a particular
 *   error condition.
 *   The value KMF_OK indicates success. All other values represent
 *   an error condition.
 */
KMF_RETURN
kmf_select_token(KMF_HANDLE_T handle, char *label, int readonly)
{
        KMF_RETURN kmf_rv = KMF_OK;
        CK_RV ck_rv = CKR_OK;
        CK_SLOT_ID slot_id;
        CK_SESSION_HANDLE hSession;
        CK_FLAGS        openflags;

        CLEAR_ERROR(handle, kmf_rv);
        if (kmf_rv != KMF_OK)
                return (kmf_rv);

        if (label == NULL) {
                return (KMF_ERR_BAD_PARAMETER);
        }

        kmf_rv = init_pk11();
        if (kmf_rv != KMF_OK) {
                return (kmf_rv);
        }

        /* Only one token can be active per thread */
        if (handle->pk11handle != 0) {
                return (KMF_ERR_TOKEN_SELECTED);
        }

        /* Find the token with matching label */
        kmf_rv = kmf_pk11_token_lookup(handle, label, &slot_id);
        if (kmf_rv != KMF_OK) {
                return (kmf_rv);
        }

        openflags = CKF_SERIAL_SESSION;
        if (!readonly)
                openflags |= CKF_RW_SESSION;

        /* Open a session then log the user into the token */
        ck_rv = C_OpenSession(slot_id, openflags, NULL, NULL, &hSession);
        if (ck_rv != CKR_OK) {
                handle->lasterr.kstype = KMF_KEYSTORE_PK11TOKEN;
                handle->lasterr.errcode = ck_rv;
                return (KMF_ERR_INTERNAL);
        }

        handle->pk11handle = hSession;

        return (kmf_rv);
}

CK_SESSION_HANDLE
kmf_get_pk11_handle(KMF_HANDLE_T kmfh)
{
        return (kmfh->pk11handle);
}

KMF_RETURN
kmf_pk11_init_token(KMF_HANDLE_T handle,
        char *currlabel, char *newlabel,
        CK_UTF8CHAR_PTR sopin, CK_ULONG sopinlen)
{
        KMF_RETURN ret = KMF_OK;
        CK_RV ckrv;
        CK_SLOT_ID slot_id = 0;

        CLEAR_ERROR(handle, ret);
        if (ret != KMF_OK)
                return (ret);

        /*
         * It is best to try and lookup tokens by label.
         */
        if (currlabel != NULL) {
                ret = kmf_pk11_token_lookup(handle, currlabel, &slot_id);
                if (ret != KMF_OK)
                        return (ret);
        } else {
                /* We can't determine which slot to initialize */
                return (KMF_ERR_TOKEN_NOT_PRESENT);
        }

        /* Initialize and set the new label (if given) */
        ckrv = C_InitToken(slot_id, sopin, sopinlen,
            (CK_UTF8CHAR_PTR)(newlabel ? newlabel : currlabel));

        if (ckrv != CKR_OK) {
                if (ckrv == CKR_PIN_INCORRECT)
                        return (KMF_ERR_AUTH_FAILED);
                else
                        return (KMF_ERR_INTERNAL);
        }

        return (ret);
}