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