view usr/src/lib/libkmf/libkmf/common/pk11tokens.c @ 5051:cbbb7c8b40a9

PSARC 2007/426 KMFAPI Interface Taxonomy Change PSARC 2007/465 pktool symmetric key enhancements 6546405 KMF Interfaces need to be extensible 6547894 pktool should be more detailed 6590232 pktool should import and export generic keys
author wyllys
date Fri, 14 Sep 2007 12:13:39 -0700
parents 3d0d37b7e5af
children d661a471a5c3
line wrap: on
line source

/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#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)
		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 != NULL) {
		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);
}