1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 *
21 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
22 * Use is subject to license terms.
23 */
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <ctype.h>
29#include <security/cryptoki.h>
30#include <kmfapi.h>
31#include <kmfapiP.h>
32#include <cryptoutil.h>
33
34/*
35 * memcmp_pad_max() is a specialized version of memcmp() which
36 * compares two pieces of data up to a maximum length.  If the
37 * the two data match up the maximum length, they are considered
38 * matching.  Trailing blanks do not cause the match to fail if
39 * one of the data is shorted.
40 *
41 * Examples of matches:
42 *	"one"           |
43 *	"one      "     |
44 *	                ^maximum length
45 *
46 *	"Number One     |  X"	(X is beyond maximum length)
47 *	"Number One   " |
48 *	                ^maximum length
49 *
50 * Examples of mismatches:
51 *	" one"
52 *	"one"
53 *
54 *	"Number One    X|"
55 *	"Number One     |"
56 *	                ^maximum length
57 */
58static int
59memcmp_pad_max(void *d1, uint_t d1_len, void *d2, uint_t d2_len, uint_t max_sz)
60{
61	uint_t		len, extra_len;
62	char		*marker;
63
64	/* No point in comparing anything beyond max_sz */
65	if (d1_len > max_sz)
66		d1_len = max_sz;
67	if (d2_len > max_sz)
68		d2_len = max_sz;
69
70	/* Find shorter of the two data. */
71	if (d1_len <= d2_len) {
72		len = d1_len;
73		extra_len = d2_len;
74		marker = d2;
75	} else {	/* d1_len > d2_len */
76		len = d2_len;
77		extra_len = d1_len;
78		marker = d1;
79	}
80
81	/* Have a match in the shortest length of data? */
82	if (memcmp(d1, d2, len) != 0)
83		/* CONSTCOND */
84		return (1);
85
86	/* If the rest of longer data is nulls or blanks, call it a match. */
87	while (len < extra_len && marker[len])
88		if (!isspace(marker[len++]))
89			/* CONSTCOND */
90			return (1);
91	return (0);
92}
93
94static KMF_RETURN
95kmf_get_token_slots(KMF_HANDLE *handle, CK_SLOT_ID_PTR *slot_list,
96    CK_ULONG *slot_count)
97{
98
99	KMF_RETURN	kmf_rv = KMF_OK;
100	CK_RV		ck_rv = CKR_OK;
101	CK_ULONG	tmp_count = 0;
102	CK_SLOT_ID_PTR	tmp_list = NULL_PTR, tmp2_list = NULL_PTR;
103
104	ck_rv = C_GetSlotList(1, NULL_PTR, &tmp_count);
105	if (ck_rv == CKR_CRYPTOKI_NOT_INITIALIZED) {
106		ck_rv = C_Initialize(NULL);
107		if ((ck_rv != CKR_OK) &&
108		    (ck_rv != CKR_CRYPTOKI_ALREADY_INITIALIZED))
109			return (KMF_ERR_UNINITIALIZED);
110		if (ck_rv == CKR_CRYPTOKI_ALREADY_INITIALIZED)
111			ck_rv = CKR_OK;
112
113		ck_rv = C_GetSlotList(1, NULL_PTR, &tmp_count);
114	}
115	if (ck_rv != CKR_OK) {
116		if (handle != NULL) {
117			handle->lasterr.kstype = KMF_KEYSTORE_PK11TOKEN;
118			handle->lasterr.errcode = ck_rv;
119		}
120		return (KMF_ERR_INTERNAL);
121	}
122
123	if (tmp_count == 0) {
124		*slot_list = NULL_PTR;
125		*slot_count = 0;
126		return (KMF_OK);
127	}
128
129	/* Allocate initial space for the slot list. */
130	if ((tmp_list = (CK_SLOT_ID_PTR) malloc(tmp_count *
131	    sizeof (CK_SLOT_ID))) == NULL)
132		return (KMF_ERR_MEMORY);
133
134	/* Then get the slot list itself. */
135	for (;;) {
136		ck_rv = C_GetSlotList(1, tmp_list, &tmp_count);
137		if (ck_rv == CKR_OK) {
138			*slot_list = tmp_list;
139			*slot_count = tmp_count;
140			kmf_rv = KMF_OK;
141			break;
142		}
143
144		if (ck_rv != CKR_BUFFER_TOO_SMALL) {
145			free(tmp_list);
146			if (handle != NULL) {
147				handle->lasterr.kstype = KMF_KEYSTORE_PK11TOKEN;
148				handle->lasterr.errcode = ck_rv;
149			}
150			kmf_rv = KMF_ERR_INTERNAL;
151			break;
152		}
153
154		/*
155		 * If the number of slots grew, try again. This
156		 * is to be consistent with pktool in ONNV.
157		 */
158		if ((tmp2_list = (CK_SLOT_ID_PTR) realloc(tmp_list,
159		    tmp_count * sizeof (CK_SLOT_ID))) == NULL) {
160			free(tmp_list);
161			kmf_rv = KMF_ERR_MEMORY;
162			break;
163		}
164		tmp_list = tmp2_list;
165	}
166
167	return (kmf_rv);
168}
169
170/*
171 * Returns pointer to either null-terminator or next unescaped colon.  The
172 * string to be extracted starts at the beginning and goes until one character
173 * before this pointer.  If NULL is returned, the string itself is NULL.
174 */
175static char *
176find_unescaped_colon(char *str)
177{
178	char *end;
179
180	if (str == NULL)
181		return (NULL);
182
183	while ((end = strchr(str, ':')) != NULL) {
184		if (end != str && *(end-1) != '\\')
185			return (end);
186		str = end + 1;		/* could point to null-terminator */
187	}
188	if (end == NULL)
189		end = strchr(str, '\0');
190	return (end);
191}
192
193/*
194 * Compresses away any characters escaped with backslash from given string.
195 * The string is altered in-place.  Example, "ab\:\\e" becomes "ab:\e".
196 */
197static void
198unescape_str(char *str)
199{
200	boolean_t	escaped = B_FALSE;
201	char		*mark;
202
203	if (str == NULL)
204		return;
205
206	for (mark = str; *str != '\0'; str++) {
207		if (*str != '\\' || escaped == B_TRUE) {
208			*mark++ = *str;
209			escaped = B_FALSE;
210		} else {
211			escaped = B_TRUE;
212		}
213	}
214	*mark = '\0';
215}
216
217
218/*
219 * Given a colon-separated token specifier, this functions splits it into
220 * its label, manufacturer ID (if any), and serial number (if any).  Literal
221 * colons within the label/manuf/serial can be escaped with a backslash.
222 * Fields can left blank and trailing colons can be omitted, however leading
223 * colons are required as placeholders.  For example, these are equivalent:
224 *	(a) "lbl", "lbl:", "lbl::"	(b) "lbl:man", "lbl:man:"
225 * but these are not:
226 *	(c) "man", ":man"	(d) "ser", "::ser"
227 * Furthermore, the token label is required always.
228 *
229 * The buffer containing the token specifier is altered by replacing the
230 * colons to null-terminators, and pointers returned are pointers into this
231 * string.  No new memory is allocated.
232 */
233static int
234parse_token_spec(char *token_spec, char **token_name, char **manuf_id,
235	char **serial_no)
236{
237	char	*mark;
238
239	if (token_spec == NULL || *token_spec == '\0') {
240		return (-1);
241	}
242
243	*token_name = NULL;
244	*manuf_id = NULL;
245	*serial_no = NULL;
246
247	/* Token label (required) */
248	mark = find_unescaped_colon(token_spec);
249	*token_name = token_spec;
250	if (*mark != '\0')
251		*mark++ = '\0';		/* mark points to next field, if any */
252	unescape_str(*token_name);
253
254	if (*(*token_name) == '\0') {	/* token label is required */
255		return (-1);
256	}
257
258	if (*mark == '\0' || *(mark+1) == '\0')		/* no more fields */
259		return (0);
260	token_spec = mark;
261
262	/* Manufacturer identifier (optional) */
263	mark = find_unescaped_colon(token_spec);
264	*manuf_id = token_spec;
265	if (*mark != '\0')
266		*mark++ = '\0';		/* mark points to next field, if any */
267	unescape_str(*manuf_id);
268
269	if (*mark == '\0' || *(mark+1) == '\0')		/* no more fields */
270		return (0);
271	token_spec = mark;
272
273	/* Serial number (optional) */
274	mark = find_unescaped_colon(token_spec);
275	*serial_no = token_spec;
276	if (*mark != '\0')
277		*mark++ = '\0';		/* null-terminate, just in case */
278	unescape_str(*serial_no);
279
280	return (0);
281}
282
283/*
284 * Find slots that match a token identifier.  Token labels take the
285 * form of:
286 *	token_name:manufacturer:serial_number
287 * manufacterer and serial number are optional.  If used, the fields
288 * are delimited by the colon ':' character.
289 */
290KMF_RETURN
291kmf_pk11_token_lookup(KMF_HANDLE_T handle, char *label, CK_SLOT_ID *slot_id)
292{
293	KMF_RETURN	kmf_rv = KMF_OK;
294	CK_RV		rv;
295	CK_SLOT_ID_PTR	slot_list = NULL;
296	CK_TOKEN_INFO	token_info;
297	CK_ULONG	slot_count = 0;
298	int		i;
299	uint_t		len, max_sz;
300	boolean_t 	metaslot_status_enabled;
301	boolean_t 	metaslot_migrate_enabled;
302	char	*metaslot_slot_info;
303	char	*metaslot_token_info;
304	char	*tmplabel = NULL;
305	char	*token_name = NULL;
306	char	*manuf_id = NULL;
307	char	*serial_no = NULL;
308	boolean_t	tok_match = B_FALSE;
309	boolean_t	man_match = B_FALSE;
310	boolean_t	ser_match = B_FALSE;
311
312	if (slot_id == NULL || label == NULL || !strlen(label))
313		return (KMF_ERR_BAD_PARAMETER);
314
315	if (handle == NULL) {
316		rv = C_Initialize(NULL);
317		if ((rv != CKR_OK) &&
318		    (rv != CKR_CRYPTOKI_ALREADY_INITIALIZED)) {
319			return (KMF_ERR_UNINITIALIZED);
320		}
321	}
322
323	/*
324	 * Parse token specifier into token_name, manuf_id, serial_no.
325	 * Token_name is required; manuf_id and serial_no are optional.
326	 */
327	tmplabel = strdup(label);
328	if (tmplabel == NULL)
329		return (KMF_ERR_MEMORY);
330
331	if (parse_token_spec(tmplabel, &token_name, &manuf_id,
332	    &serial_no) < 0) {
333		free(tmplabel);
334		return (KMF_ERR_BAD_PARAMETER);
335	}
336
337	/* Get a list of all slots with tokens present. */
338	kmf_rv = kmf_get_token_slots(handle, &slot_list, &slot_count);
339	if (kmf_rv != KMF_OK) {
340		free(tmplabel);
341		return (kmf_rv);
342	}
343
344	/* If there are no such slots, the desired token won't be found. */
345	if (slot_count == 0) {
346		free(tmplabel);
347		return (KMF_ERR_TOKEN_NOT_PRESENT);
348	}
349
350	/* Search the slot list for the token. */
351	for (i = 0; i < slot_count; i++) {
352		if (C_GetTokenInfo(slot_list[i], &token_info) != CKR_OK) {
353			continue;
354		}
355
356		/* See if the token label matches. */
357		len = strlen(token_name);
358		max_sz = sizeof (token_info.label);
359		if (memcmp_pad_max(&(token_info.label), max_sz, token_name,
360		    len, max_sz) == 0)
361			tok_match = B_TRUE;
362		/*
363		 * If manufacturer id was given, see if it actually matches.
364		 * If no manufacturer id was given, assume match is true.
365		 */
366		if (manuf_id) {
367			len = strlen(manuf_id);
368			max_sz = sizeof ((char *)(token_info.manufacturerID));
369			if (memcmp_pad_max(&(token_info.manufacturerID), max_sz,
370			    manuf_id, len, max_sz) == 0)
371				man_match = B_TRUE;
372		} else {
373			man_match = B_TRUE;
374		}
375
376		/*
377		 * If serial number was given, see if it actually matches.
378		 * If no serial number was given, assume match is true.
379		 */
380		if (serial_no) {
381			len = strlen(serial_no);
382			max_sz = sizeof ((char *)(token_info.serialNumber));
383			if (memcmp_pad_max(&(token_info.serialNumber), max_sz,
384			    serial_no, len, max_sz) == 0)
385				ser_match = B_TRUE;
386		} else {
387			ser_match = B_TRUE;
388		}
389
390		if (tok_match && man_match && ser_match)
391			break;		/* found it! */
392	}
393
394	if (i < slot_count) {
395		/* found the desired token from the slotlist */
396		*slot_id = slot_list[i];
397		free(slot_list);
398		free(tmplabel);
399		return (KMF_OK);
400	}
401
402	/*
403	 * If we didn't find the token from the slotlist, check if this token
404	 * is the one currently hidden by the metaslot. If that's case,
405	 * we can just use the metaslot, the slot 0.
406	 */
407	kmf_rv = get_metaslot_info(&metaslot_status_enabled,
408	    &metaslot_migrate_enabled, &metaslot_slot_info,
409	    &metaslot_token_info);
410	if (kmf_rv) {
411		/*
412		 * Failed to get the metaslot info.  This usually means that
413		 * metaslot is disabled from the system.
414		 */
415		kmf_rv = KMF_ERR_TOKEN_NOT_PRESENT;
416	} else {
417		max_sz = strlen(metaslot_token_info);
418		if (memcmp_pad_max(metaslot_token_info, max_sz, token_name, len,
419		    max_sz) == 0) {
420			*slot_id = slot_list[0];
421		} else {
422			kmf_rv = KMF_ERR_TOKEN_NOT_PRESENT;
423		}
424		free(metaslot_slot_info);
425		free(metaslot_token_info);
426	}
427
428	free(slot_list);
429	free(tmplabel);
430	return (kmf_rv);
431}
432
433KMF_RETURN
434kmf_set_token_pin(KMF_HANDLE_T handle,
435	int num_attr,
436	KMF_ATTRIBUTE *attrlist)
437{
438	KMF_RETURN ret = KMF_OK;
439	KMF_PLUGIN *plugin;
440	KMF_ATTRIBUTE_TESTER required_attrs[] = {
441		{KMF_KEYSTORE_TYPE_ATTR, FALSE, 1, sizeof (KMF_KEYSTORE_TYPE)},
442		{KMF_CREDENTIAL_ATTR, FALSE, sizeof (KMF_CREDENTIAL),
443			sizeof (KMF_CREDENTIAL)},
444		{KMF_NEWPIN_ATTR, FALSE, sizeof (KMF_CREDENTIAL),
445			sizeof (KMF_CREDENTIAL)},
446	};
447
448	int num_req_attrs = sizeof (required_attrs) /
449	    sizeof (KMF_ATTRIBUTE_TESTER);
450	uint32_t len;
451	KMF_KEYSTORE_TYPE kstype;
452
453	if (handle == NULL)
454		return (KMF_ERR_BAD_PARAMETER);
455
456	CLEAR_ERROR(handle, ret);
457	if (ret != KMF_OK)
458		return (ret);
459
460	ret = test_attributes(num_req_attrs, required_attrs,
461	    0, NULL, num_attr, attrlist);
462	if (ret != KMF_OK)
463		return (ret);
464
465	len = sizeof (kstype);
466	ret = kmf_get_attr(KMF_KEYSTORE_TYPE_ATTR, attrlist, num_attr,
467	    &kstype, &len);
468	if (ret != KMF_OK)
469		return (ret);
470
471	plugin = FindPlugin(handle, kstype);
472	if (plugin != NULL) {
473		if (plugin->funclist->SetTokenPin != NULL)
474			return (plugin->funclist->SetTokenPin(handle, num_attr,
475			    attrlist));
476		else
477			return (KMF_ERR_FUNCTION_NOT_FOUND);
478	}
479	return (KMF_ERR_PLUGIN_NOTFOUND);
480}
481
482/*
483 * Name: kmf_select_token
484 *
485 * Description:
486 *   This function enables the user of PKCS#11 plugin to select a
487 *   particular PKCS#11 token. Valid token label are required in order to
488 *   successfully complete this function.
489 *   All subsequent KMF APIs, which specify PKCS#11 keystore as
490 *   the backend, will be performed at the selected token.
491 *
492 * Parameters:
493 *   label(input) - pointer to the token label
494 *
495 * Returns:
496 *   A KMF_RETURN value indicating success or specifying a particular
497 *   error condition.
498 *   The value KMF_OK indicates success. All other values represent
499 *   an error condition.
500 */
501KMF_RETURN
502kmf_select_token(KMF_HANDLE_T handle, char *label, int readonly)
503{
504	KMF_RETURN kmf_rv = KMF_OK;
505	CK_RV ck_rv = CKR_OK;
506	CK_SLOT_ID slot_id;
507	CK_SESSION_HANDLE hSession;
508	CK_FLAGS 	openflags;
509
510	CLEAR_ERROR(handle, kmf_rv);
511	if (kmf_rv != KMF_OK)
512		return (kmf_rv);
513
514	if (label == NULL) {
515		return (KMF_ERR_BAD_PARAMETER);
516	}
517
518	kmf_rv = init_pk11();
519	if (kmf_rv != KMF_OK) {
520		return (kmf_rv);
521	}
522
523	/* Only one token can be active per thread */
524	if (handle->pk11handle != 0) {
525		return (KMF_ERR_TOKEN_SELECTED);
526	}
527
528	/* Find the token with matching label */
529	kmf_rv = kmf_pk11_token_lookup(handle, label, &slot_id);
530	if (kmf_rv != KMF_OK) {
531		return (kmf_rv);
532	}
533
534	openflags = CKF_SERIAL_SESSION;
535	if (!readonly)
536		openflags |= CKF_RW_SESSION;
537
538	/* Open a session then log the user into the token */
539	ck_rv = C_OpenSession(slot_id, openflags, NULL, NULL, &hSession);
540	if (ck_rv != CKR_OK) {
541		handle->lasterr.kstype = KMF_KEYSTORE_PK11TOKEN;
542		handle->lasterr.errcode = ck_rv;
543		return (KMF_ERR_INTERNAL);
544	}
545
546	handle->pk11handle = hSession;
547
548	return (kmf_rv);
549}
550
551CK_SESSION_HANDLE
552kmf_get_pk11_handle(KMF_HANDLE_T kmfh)
553{
554	return (kmfh->pk11handle);
555}
556
557KMF_RETURN
558kmf_pk11_init_token(KMF_HANDLE_T handle,
559	char *currlabel, char *newlabel,
560	CK_UTF8CHAR_PTR sopin, CK_ULONG sopinlen)
561{
562	KMF_RETURN ret = KMF_OK;
563	CK_RV ckrv;
564	CK_SLOT_ID slot_id = 0;
565
566	CLEAR_ERROR(handle, ret);
567	if (ret != KMF_OK)
568		return (ret);
569
570	/*
571	 * It is best to try and lookup tokens by label.
572	 */
573	if (currlabel != NULL) {
574		ret = kmf_pk11_token_lookup(handle, currlabel, &slot_id);
575		if (ret != KMF_OK)
576			return (ret);
577	} else {
578		/* We can't determine which slot to initialize */
579		return (KMF_ERR_TOKEN_NOT_PRESENT);
580	}
581
582	/* Initialize and set the new label (if given) */
583	ckrv = C_InitToken(slot_id, sopin, sopinlen,
584	    (CK_UTF8CHAR_PTR)(newlabel ? newlabel : currlabel));
585
586	if (ckrv != CKR_OK) {
587		if (ckrv == CKR_PIN_INCORRECT)
588			return (KMF_ERR_AUTH_FAILED);
589		else
590			return (KMF_ERR_INTERNAL);
591	}
592
593	return (ret);
594}
595