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 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Mechanism Manager - centralized knowledge of mechanisms.
30  *
31  * The core of the mechmanager is the "mechlist" data structure. It contains
32  * information about all mechanisms available from providers that have been
33  * exposed to the application.
34  *
35  * Each element in the array represents a particular mechanism type. The
36  * array is sorted by type, so that searching by mechanism can be done
37  * quickly. Each element also contains the mechanism data for each slot.
38  *
39  * The mechlist is constructed on an as-needed basis, entries are not added
40  * until the application triggers an action that requires an entry to be
41  * added (or updated).
42  *
43  */
44 
45 #include <string.h>
46 #include <strings.h>
47 #include "pkcs11Conf.h"
48 #include "metaGlobal.h"
49 
50 
51 /* Global data... */
52 
53 #define	INITIAL_MECHLIST_SIZE	256
54 
55 typedef struct mechliststruct {
56 	CK_MECHANISM_TYPE type;
57 	mechinfo_t *slots;
58 } mechlist_t;
59 
60 static pthread_rwlock_t mechlist_lock = PTHREAD_RWLOCK_INITIALIZER;
61 static mechlist_t *mechlist;
62 static unsigned long num_mechs;
63 static unsigned long true_mechlist_size;
64 
65 
66 /* Prototypes... */
67 static CK_RV meta_mechManager_update_mech(CK_MECHANISM_TYPE, boolean_t);
68 static CK_RV meta_mechManager_update_slot(CK_ULONG);
69 static CK_RV update_slotmech(CK_MECHANISM_TYPE, CK_ULONG, unsigned long);
70 static CK_RV meta_mechManager_allocmechs(CK_MECHANISM_TYPE *, unsigned long,
71 	unsigned long *);
72 static boolean_t find_mech_index(CK_MECHANISM_TYPE, unsigned long *);
73 static int qsort_mechtypes(const void *, const void *);
74 
75 
76 /*
77  * meta_mechManager_initialize
78  *
79  * Called from C_Initialize. Allocates and initializes storage needed
80  * by the slot manager.
81  */
82 CK_RV
83 meta_mechManager_initialize()
84 {
85 	/* The mechlist can dynamically grow, but let's preallocate space. */
86 	mechlist = calloc(INITIAL_MECHLIST_SIZE, sizeof (mechlist_t));
87 	if (mechlist == NULL)
88 		return (CKR_HOST_MEMORY);
89 
90 	true_mechlist_size = INITIAL_MECHLIST_SIZE;
91 	num_mechs = 0;
92 
93 	return (CKR_OK);
94 }
95 
96 
97 /*
98  * meta_mechManager_finalize
99  *
100  * Called from C_Finalize. Deallocates any storage held by the slot manager.
101  */
102 void
103 meta_mechManager_finalize()
104 {
105 	int i;
106 
107 	/* No need to lock list, we assume all sessions are closed. */
108 	for (i = 0; i < num_mechs; i++) {
109 		free(mechlist[i].slots);
110 	}
111 
112 	free(mechlist);
113 	mechlist = NULL;
114 	num_mechs = 0;
115 	true_mechlist_size = 0;
116 }
117 
118 
119 /*
120  * meta_mechManager_get_mechs
121  *
122  * Get list of all available mechanisms.
123  *
124  * Follows PKCS#11 semantics, where list may be NULL to only request a
125  * count of available mechanisms.
126  */
127 CK_RV
128 meta_mechManager_get_mechs(CK_MECHANISM_TYPE *list, CK_ULONG *listsize)
129 {
130 	CK_RV rv = CKR_OK;
131 	CK_ULONG num_found = 0;
132 	CK_ULONG slotnum, num_slots;
133 	unsigned long i;
134 
135 	/* get number of slots */
136 	num_slots = meta_slotManager_get_slotcount();
137 
138 	/*
139 	 * Update slot info. Ignore any errors.
140 	 *
141 	 * NOTE: Due to the PKCS#11 convention of calling C_GetMechanismList
142 	 * twice (once to get the count, again to get the actual list), this
143 	 * is somewhat inefficient... However, I don't see an easy way to fix
144 	 * that without impacting other cases (eg, when the first call contains
145 	 * an "optimistic" pre-allocated buffer).
146 	 */
147 	for (slotnum = 0; slotnum < num_slots; slotnum++) {
148 		(void) meta_mechManager_update_slot(slotnum);
149 	}
150 
151 
152 	/*
153 	 * Count the number of mechanisms. We can't just use num_mechs,
154 	 * because some mechs may not currently be supported on any slot.
155 	 * Also, it may not be allowed based on the mechanism policy.
156 	 */
157 
158 	(void) pthread_rwlock_rdlock(&mechlist_lock);
159 	for (i = 0; i < num_mechs; i++) {
160 		CK_ULONG j;
161 		boolean_t supported;
162 
163 		if (pkcs11_is_dismech(METASLOT_FRAMEWORK_ID,
164 		    mechlist[i].type)) {
165 			/* skip mechs disabled by policy */
166 			continue;
167 		}
168 
169 		supported = FALSE;
170 		for (j = 0; j < num_slots; j++) {
171 			if (!mechlist[i].slots[j].initialized)
172 				continue;
173 
174 			if (mechlist[i].slots[j].supported) {
175 				supported = B_TRUE;
176 				break;
177 			}
178 		}
179 
180 		if (supported) {
181 			num_found++;
182 
183 			if (list && *listsize >= num_found) {
184 				list[num_found - 1] = mechlist[i].type;
185 			}
186 		}
187 	}
188 	(void) pthread_rwlock_unlock(&mechlist_lock);
189 
190 	if (num_found > *listsize)
191 		rv = CKR_BUFFER_TOO_SMALL;
192 
193 	*listsize = num_found;
194 
195 	return (rv);
196 }
197 
198 
199 /*
200  * meta_mechManager_get_slots
201  *
202  * Get list of all slots supporting the specified mechanism.
203  *
204  * The "mech_support_info" argument should have allocated enough
205  * space to accomodate the list of slots that supports the
206  * specified mechanism.  The "num_supporting_slots" field
207  * in the "mech_support_info" structure will indicate how
208  * many slots are found to support the mechanism.
209  *
210  * If any error occurred in getting the list, info in
211  * mech_support_info argument is not updated.
212  *
213  */
214 CK_RV
215 meta_mechManager_get_slots(mech_support_info_t  *mech_support_info,
216     boolean_t force_update, CK_MECHANISM_INFO *mech_info)
217 {
218 	CK_RV rv;
219 	boolean_t found;
220 	CK_ULONG i, num_slots;
221 	unsigned long index, num_found = 0;
222 	CK_MECHANISM_INFO info;
223 
224 	rv = meta_mechManager_update_mech(mech_support_info->mech,
225 	    force_update);
226 	if (rv != CKR_OK) {
227 		return (rv);
228 	}
229 
230 	(void) pthread_rwlock_rdlock(&mechlist_lock);
231 
232 	found = find_mech_index(mech_support_info->mech, &index);
233 	if (!found) {
234 		goto finish;
235 	}
236 
237 	num_slots = meta_slotManager_get_slotcount();
238 	for (i = 0; i < num_slots; i++) {
239 		if (!mechlist[index].slots[i].initialized ||
240 		    !mechlist[index].slots[i].supported)
241 			continue;
242 
243 		if (mech_info) {
244 			info = mechlist[index].slots[i].mechanism_info;
245 			if (!(info.flags & mech_info->flags)) {
246 				continue;
247 			}
248 		}
249 
250 		num_found++;
251 		(mech_support_info->supporting_slots)[num_found - 1]
252 		    = &mechlist[index].slots[i];
253 	}
254 
255 finish:
256 	(void) pthread_rwlock_unlock(&mechlist_lock);
257 
258 	if (num_found == 0) {
259 		rv = CKR_MECHANISM_INVALID;
260 	} else {
261 		mech_support_info->num_supporting_slots = num_found;
262 	}
263 
264 	return (rv);
265 }
266 
267 
268 /*
269  * meta_mechManager_update_mech
270  *
271  * Updates a mechanism in the mechlist. If the mechanism is not
272  * listed, all providers will be queried. If the mechanism
273  * is present, but not initialized for some providers, those providers
274  * will be queried. Existing entries will not be updated unless the
275  * force_refresh flag is set.
276  *
277  * The force_refresh flag is used by C_GetMechanismInfo, to force an
278  * update. Updates are not forced during the common usage by operations
279  * [eg C_EncryptInit] to avoid poor performance.
280  */
281 static CK_RV
282 meta_mechManager_update_mech(CK_MECHANISM_TYPE mech, boolean_t force_refresh)
283 {
284 	CK_RV rv;
285 	CK_ULONG slot, num_slots;
286 	unsigned long index = 0;
287 	boolean_t found;
288 
289 	/* Ensure list contains the mechanism. */
290 	rv = meta_mechManager_allocmechs(&mech, 1, &index);
291 	if (rv != CKR_OK)
292 		return (rv);
293 
294 	(void) pthread_rwlock_wrlock(&mechlist_lock);
295 	/*
296 	 * We didn't retain a lock after the first search, so it's possible
297 	 * that the mechlist was updated. Search again, but use the last
298 	 * index as a hint to quickly find the mechanism.
299 	 */
300 	found = find_mech_index(mech, &index);
301 	if (!found) {
302 		/* Shouldn't happen - entries are not removed from list. */
303 		rv = CKR_GENERAL_ERROR;
304 		goto finish;
305 	}
306 
307 	num_slots = meta_slotManager_get_slotcount();
308 	for (slot = 0; slot < num_slots; slot++) {
309 		if (force_refresh || !mechlist[index].slots[slot].initialized) {
310 			rv = update_slotmech(mech, slot, index);
311 			if (rv != CKR_OK) {
312 				/* Ignore error and continue with next slot. */
313 				rv = CKR_OK;
314 			}
315 		}
316 	}
317 
318 finish:
319 	(void) pthread_rwlock_unlock(&mechlist_lock);
320 
321 	return (rv);
322 }
323 
324 
325 /*
326  * meta_mechManager_update_slot
327  *
328  * Updates a slot in the mechlist. Called by C_GetMechanismList
329  * [by way of meta_mechManager_get_mechs()]. Unlike
330  * meta_mechManager_get_slots(), the context is always to force a refresh
331  * of the mechlist.
332  *
333  */
334 static CK_RV
335 meta_mechManager_update_slot(CK_ULONG slotnum)
336 {
337 	unsigned long index = 0;
338 	CK_MECHANISM_TYPE *slot_mechlist = NULL, *tmp_slot_mechlist = NULL;
339 	CK_ULONG slot_mechlistsize, mechnum, tmp_mechlistsize;
340 	CK_RV rv;
341 	boolean_t found;
342 	CK_SLOT_ID fw_st_id, true_id;
343 	int i;
344 
345 	fw_st_id = meta_slotManager_get_framework_table_id(slotnum);
346 	true_id = TRUEID(fw_st_id);
347 
348 	/* First, get the count. */
349 	rv = FUNCLIST(fw_st_id)->C_GetMechanismList(true_id, NULL,
350 	    &slot_mechlistsize);
351 	if (rv != CKR_OK) {
352 		goto finish;
353 	}
354 
355 	tmp_slot_mechlist = malloc(
356 	    slot_mechlistsize * sizeof (CK_MECHANISM_TYPE));
357 	if (tmp_slot_mechlist == NULL) {
358 		rv = CKR_HOST_MEMORY;
359 		goto finish;
360 	}
361 
362 	/* Next, get the actual list. */
363 	rv = FUNCLIST(fw_st_id)->C_GetMechanismList(true_id,
364 	    tmp_slot_mechlist, &slot_mechlistsize);
365 	if (rv != CKR_OK) {
366 		goto finish;
367 	}
368 
369 	/*
370 	 * filter the list of mechanisms returned by the underlying slot
371 	 * to remove any mechanisms that are explicitly disabled
372 	 * in the configuration file.
373 	 */
374 	slot_mechlist = malloc(slot_mechlistsize * sizeof (CK_MECHANISM_TYPE));
375 	if (slot_mechlist == NULL) {
376 		rv = CKR_HOST_MEMORY;
377 		goto finish;
378 	}
379 
380 	tmp_mechlistsize = 0;
381 	for (i = 0; i < slot_mechlistsize; i++) {
382 		/* filter out the disabled mechanisms */
383 		if (pkcs11_is_dismech(fw_st_id, tmp_slot_mechlist[i])) {
384 			continue;
385 		}
386 
387 		slot_mechlist[tmp_mechlistsize] = tmp_slot_mechlist[i];
388 		tmp_mechlistsize++;
389 	}
390 	slot_mechlistsize = tmp_mechlistsize;
391 
392 	/* Sort the mechanisms by value. */
393 	qsort(slot_mechlist, slot_mechlistsize, sizeof (CK_MECHANISM_TYPE),
394 	    qsort_mechtypes);
395 
396 	/* Ensure list contains the mechanisms. */
397 	rv = meta_mechManager_allocmechs(slot_mechlist, slot_mechlistsize,
398 	    &index);
399 	if (rv != CKR_OK)
400 		goto finish;
401 
402 	/* Update the mechanism info. */
403 	(void) pthread_rwlock_wrlock(&mechlist_lock);
404 	for (mechnum = 0; mechnum < slot_mechlistsize; mechnum++) {
405 		found = find_mech_index(slot_mechlist[mechnum], &index);
406 		if (!found) {
407 			/* This shouldn't happen. */
408 			rv = CKR_GENERAL_ERROR;
409 			goto finish;
410 		}
411 
412 		rv = update_slotmech(slot_mechlist[mechnum], slotnum, index);
413 		if (rv != CKR_OK) {
414 			/* Ignore error, make best effort to finish update. */
415 			rv = CKR_OK;
416 			continue;
417 		}
418 	}
419 	(void) pthread_rwlock_unlock(&mechlist_lock);
420 
421 finish:
422 	if (slot_mechlist) {
423 		free(slot_mechlist);
424 	}
425 
426 	if (tmp_slot_mechlist) {
427 		free(tmp_slot_mechlist);
428 	}
429 
430 	return (rv);
431 }
432 
433 
434 /*
435  * update_slotmech
436  *
437  * Updates the information for a particular mechanism for a particular slot.
438  * (ie, slotlist[foo].slots[bar])
439  *
440  * It is assumed that the caller to this function (all of which are
441  * in this file) holds the write-lock to "mechlist_lock".
442  *
443  */
444 static CK_RV
445 update_slotmech(CK_MECHANISM_TYPE mech, CK_ULONG slotnum,
446 	unsigned long index)
447 {
448 	CK_RV rv = CKR_OK;
449 	CK_MECHANISM_INFO info;
450 	CK_SLOT_ID fw_st_id, true_id;
451 
452 	mechlist[index].slots[slotnum].slotnum = slotnum;
453 	fw_st_id = meta_slotManager_get_framework_table_id(slotnum);
454 	true_id = TRUEID(fw_st_id);
455 
456 	/*
457 	 * Check if the specified mechanism is in the disabled list
458 	 * of the specified slot.  If so, we can immediately conclude
459 	 * that it is not supported by the specified slot.
460 	 */
461 	if (pkcs11_is_dismech(fw_st_id, mech)) {
462 		/*
463 		 * we mark this as initialized so that we won't try
464 		 * to do this check later
465 		 */
466 		mechlist[index].slots[slotnum].initialized = B_TRUE;
467 		mechlist[index].slots[slotnum].supported = B_FALSE;
468 		bzero(&mechlist[index].slots[slotnum].mechanism_info,
469 		    sizeof (CK_MECHANISM_INFO));
470 		goto finish;
471 	}
472 
473 	rv = FUNCLIST(fw_st_id)->C_GetMechanismInfo(true_id, mech, &info);
474 	if (rv == CKR_OK) {
475 		mechlist[index].slots[slotnum].initialized = B_TRUE;
476 		mechlist[index].slots[slotnum].supported = B_TRUE;
477 		mechlist[index].slots[slotnum].mechanism_info = info;
478 	} else {
479 		/* record that the mechanism isn't supported for the slot */
480 		mechlist[index].slots[slotnum].initialized = B_TRUE;
481 		mechlist[index].slots[slotnum].supported = B_FALSE;
482 		bzero(&mechlist[index].slots[slotnum].mechanism_info,
483 		    sizeof (CK_MECHANISM_INFO));
484 	}
485 
486 finish:
487 	return (rv);
488 }
489 
490 
491 /*
492  * meta_mechManager_allocmechs
493  *
494  * Ensures that all of the specified mechanisms are present in the
495  * mechlist. If a mechanism is not present, an uninitialized entry is
496  * added for it.
497  *
498  * The returned index can be used by the caller as a hint to where the
499  * first mechanism was located.
500  */
501 static CK_RV
502 meta_mechManager_allocmechs(CK_MECHANISM_TYPE *new_mechs,
503 	unsigned long num_new_mechs, unsigned long *index_hint)
504 {
505 	CK_RV rv = CKR_OK;
506 	unsigned long i, index = 0;
507 	boolean_t found;
508 
509 	/* The optimistic assumption is that the mech is already present. */
510 	(void) pthread_rwlock_rdlock(&mechlist_lock);
511 	for (i = 0; i < num_new_mechs; i++) {
512 		found = find_mech_index(new_mechs[i], &index);
513 
514 		if (i == 0)
515 			*index_hint = index;
516 
517 		if (!found)
518 			break;
519 	}
520 	(void) pthread_rwlock_unlock(&mechlist_lock);
521 
522 	if (found) {
523 		return (CKR_OK);
524 	}
525 
526 	/*
527 	 * We stopped searching when the first unknown mech was found. Now
528 	 * obtain a write-lock, and continue from where we left off, inserting
529 	 * unknown mechanisms.
530 	 */
531 
532 	(void) pthread_rwlock_wrlock(&mechlist_lock);
533 	for (; i < num_new_mechs; i++) {
534 		found = find_mech_index(new_mechs[i], &index);
535 
536 		if (!found) {
537 			mechinfo_t *new_mechinfos;
538 
539 			new_mechinfos = calloc(meta_slotManager_get_slotcount(),
540 			    sizeof (mechinfo_t));
541 			if (new_mechinfos == NULL) {
542 				rv = CKR_HOST_MEMORY;
543 				goto finish;
544 			}
545 
546 			/*
547 			 * If the current storage for the mechlist is too
548 			 * small, allocate a new list twice as large.
549 			 */
550 			if (num_mechs == true_mechlist_size) {
551 				mechlist_t *newmechlist;
552 
553 				newmechlist = realloc(mechlist,
554 				    2 * true_mechlist_size *
555 				    sizeof (mechlist_t));
556 
557 				if (newmechlist == NULL) {
558 					rv = CKR_HOST_MEMORY;
559 					free(new_mechinfos);
560 					goto finish;
561 				}
562 
563 				mechlist = newmechlist;
564 				true_mechlist_size *= 2;
565 			}
566 
567 			/* Shift existing entries to make space. */
568 			(void) memmove(&mechlist[index+1], &mechlist[index],
569 			    (num_mechs - index) * sizeof (mechlist_t));
570 			num_mechs++;
571 
572 			mechlist[index].type = new_mechs[i];
573 			mechlist[index].slots = new_mechinfos;
574 		}
575 	}
576 
577 finish:
578 	(void) pthread_rwlock_unlock(&mechlist_lock);
579 
580 	return (rv);
581 }
582 
583 
584 /*
585  * find_mech_index
586  *
587  * Performs a search of mechlist for the specified mechanism, and
588  * returns if the mechanism was found or not. The value of the "index"
589  * argument will be where the mech is (if found), or where it should
590  * be (if not found).
591  *
592  * The current value of "index" will be used as a starting point, if the
593  * caller already knows where the mechanism is likely to be.
594  *
595  * The caller is assumed to have a lock on the mechlist, preventing it
596  * from being changed while searching (also to ensure the returned index
597  * will remain valid until the list is unlocked).
598  *
599  * FUTURE: convert to binary search [from O(N) to a O(log(N))].
600  *
601  * NOTES:
602  * 1) This function assumes that mechMap is a sorted list.
603  */
604 static boolean_t
605 find_mech_index(CK_MECHANISM_TYPE mechanism, unsigned long *index)
606 {
607 	boolean_t found = B_FALSE;
608 	unsigned long i;
609 
610 	for (i = 0; i < num_mechs; i++) {
611 
612 		if (mechlist[i].type == mechanism) {
613 			found = B_TRUE;
614 			break;
615 		}
616 
617 		if (mechlist[i].type > mechanism)
618 			break;
619 	}
620 
621 	*index = i;
622 
623 	return (found);
624 }
625 
626 static int
627 qsort_mechtypes(const void *arg1, const void *arg2)
628 {
629 	CK_MECHANISM_TYPE mech1 = *((CK_MECHANISM_TYPE *)arg1);
630 	CK_MECHANISM_TYPE mech2 = *((CK_MECHANISM_TYPE *)arg2);
631 
632 	if (mech1 > mech2)
633 		return (1);
634 	if (mech1 < mech2)
635 		return (-1);
636 	return (0);
637 }
638 
639 /*
640  * Check if the specified mechanism is supported by the specified slot.
641  * The result is returned in the "supports" argument.  If the "slot_info"
642  * argument is not NULL, it will be filled with information about
643  * the slot.
644  */
645 CK_RV
646 meta_mechManager_slot_supports_mech(CK_MECHANISM_TYPE mechanism,
647     CK_ULONG slotnum, boolean_t *supports, mechinfo_t **slot_info,
648     boolean_t force_update, CK_MECHANISM_INFO *mech_info)
649 {
650 
651 	boolean_t found;
652 	CK_RV rv;
653 	unsigned long index;
654 	CK_MECHANISM_INFO info;
655 
656 	*supports = B_FALSE;
657 
658 	rv = meta_mechManager_update_mech(mechanism, force_update);
659 	if (rv != CKR_OK)
660 		return (rv);
661 
662 	(void) pthread_rwlock_rdlock(&mechlist_lock);
663 
664 	found = find_mech_index(mechanism, &index);
665 	if (!found) {
666 		goto finish;
667 	}
668 
669 	if ((mechlist[index].slots[slotnum].initialized) &&
670 	    (mechlist[index].slots[slotnum].supported)) {
671 		if (mech_info) {
672 			info = mechlist[index].slots[slotnum].mechanism_info;
673 			if (!(info.flags & mech_info->flags)) {
674 				goto finish;
675 			}
676 		}
677 		*supports = B_TRUE;
678 		if (slot_info) {
679 			*slot_info = &(mechlist[index].slots[slotnum]);
680 		}
681 	}
682 
683 finish:
684 	(void) pthread_rwlock_unlock(&mechlist_lock);
685 
686 	return (rv);
687 }
688