pk11tokens.c revision 47e946e784719ae402ace34695f67b0e6e76ae5c
/*
* 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 != 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);
}
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);
}