/* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file is part of the core Kernel Cryptographic Framework. * It implements the SPI functions exported to cryptographic * providers. */ #include #include #include #include #include /* * minalloc and maxalloc values to be used for taskq_create(). */ const int crypto_taskq_threads = CRYPTO_TASKQ_THREADS; const int crypto_taskq_minalloc = CRYPTO_TASKQ_MIN; const int crypto_taskq_maxalloc = CRYPTO_TASKQ_MAX; static void remove_provider(kcf_provider_desc_t *); static void process_logical_providers(const crypto_provider_info_t *, kcf_provider_desc_t *); static int init_prov_mechs(const crypto_provider_info_t *, kcf_provider_desc_t *); static int kcf_prov_kstat_update(kstat_t *, int); static void delete_kstat(kcf_provider_desc_t *); static const kcf_prov_stats_t kcf_stats_ks_data_template = { { "kcf_ops_total", KSTAT_DATA_UINT64 }, { "kcf_ops_passed", KSTAT_DATA_UINT64 }, { "kcf_ops_failed", KSTAT_DATA_UINT64 }, { "kcf_ops_returned_busy", KSTAT_DATA_UINT64 } }; /* * This routine is used to add cryptographic providers to the KEF framework. * Providers pass a crypto_provider_info structure to crypto_register_provider() * and get back a handle. The crypto_provider_info structure contains a * list of mechanisms supported by the provider and an ops vector containing * provider entry points. Hardware providers call this routine in their attach * routines. Software providers call this routine in their _init() routine. */ int crypto_register_provider(const crypto_provider_info_t *info, crypto_kcf_provider_handle_t *handle) { char *ks_name; kcf_provider_desc_t *prov_desc = NULL; int ret = CRYPTO_ARGUMENTS_BAD; /* * Check provider type, must be software, hardware, or logical. */ if (info->pi_provider_type != CRYPTO_HW_PROVIDER && info->pi_provider_type != CRYPTO_SW_PROVIDER && info->pi_provider_type != CRYPTO_LOGICAL_PROVIDER) return (CRYPTO_ARGUMENTS_BAD); /* * Allocate and initialize a new provider descriptor. We also * hold it and release it when done. */ prov_desc = kcf_alloc_provider_desc(info); KCF_PROV_REFHOLD(prov_desc); prov_desc->pd_prov_type = info->pi_provider_type; /* provider-private handle, opaque to KCF */ prov_desc->pd_prov_handle = info->pi_provider_handle; /* copy provider description string */ prov_desc->pd_description = info->pi_provider_description; /* Change from Illumos: the ops vector is persistent. */ if (info->pi_provider_type != CRYPTO_LOGICAL_PROVIDER) { prov_desc->pd_ops_vector = info->pi_ops_vector; prov_desc->pd_flags = info->pi_flags; } /* process the mechanisms supported by the provider */ if ((ret = init_prov_mechs(info, prov_desc)) != CRYPTO_SUCCESS) goto bail; /* * Add provider to providers tables, also sets the descriptor * pd_prov_id field. */ if ((ret = kcf_prov_tab_add_provider(prov_desc)) != CRYPTO_SUCCESS) { undo_register_provider(prov_desc, B_FALSE); goto bail; } /* * We create a taskq only for a hardware provider. The global * software queue is used for software providers. We handle ordering * of multi-part requests in the taskq routine. So, it is safe to * have multiple threads for the taskq. We pass TASKQ_PREPOPULATE flag * to keep some entries cached to improve performance. */ if (prov_desc->pd_prov_type == CRYPTO_HW_PROVIDER) prov_desc->pd_sched_info.ks_taskq = taskq_create("kcf_taskq", CRYPTO_TASKQ_THREADS, minclsyspri, CRYPTO_TASKQ_MIN, CRYPTO_TASKQ_MAX, TASKQ_PREPOPULATE); else prov_desc->pd_sched_info.ks_taskq = NULL; if (prov_desc->pd_prov_type != CRYPTO_LOGICAL_PROVIDER) { /* * Create the kstat for this provider. There is a kstat * installed for each successfully registered provider. * This kstat is deleted, when the provider unregisters. */ if (prov_desc->pd_prov_type == CRYPTO_SW_PROVIDER) { ks_name = kmem_asprintf("%s_%s", "NONAME", "provider_stats"); } else { ks_name = kmem_asprintf("%s_%d_%u_%s", "NONAME", 0, prov_desc->pd_prov_id, "provider_stats"); } prov_desc->pd_kstat = kstat_create("kcf", 0, ks_name, "crypto", KSTAT_TYPE_NAMED, sizeof (kcf_prov_stats_t) / sizeof (kstat_named_t), KSTAT_FLAG_VIRTUAL); if (prov_desc->pd_kstat != NULL) { bcopy(&kcf_stats_ks_data_template, &prov_desc->pd_ks_data, sizeof (kcf_stats_ks_data_template)); prov_desc->pd_kstat->ks_data = &prov_desc->pd_ks_data; KCF_PROV_REFHOLD(prov_desc); KCF_PROV_IREFHOLD(prov_desc); prov_desc->pd_kstat->ks_private = prov_desc; prov_desc->pd_kstat->ks_update = kcf_prov_kstat_update; kstat_install(prov_desc->pd_kstat); } kmem_strfree(ks_name); } if (prov_desc->pd_prov_type == CRYPTO_HW_PROVIDER) process_logical_providers(info, prov_desc); mutex_enter(&prov_desc->pd_lock); prov_desc->pd_state = KCF_PROV_READY; mutex_exit(&prov_desc->pd_lock); kcf_do_notify(prov_desc, B_TRUE); *handle = prov_desc->pd_kcf_prov_handle; ret = CRYPTO_SUCCESS; bail: KCF_PROV_REFRELE(prov_desc); return (ret); } /* * This routine is used to notify the framework when a provider is being * removed. Hardware providers call this routine in their detach routines. * Software providers call this routine in their _fini() routine. */ int crypto_unregister_provider(crypto_kcf_provider_handle_t handle) { uint_t mech_idx; kcf_provider_desc_t *desc; kcf_prov_state_t saved_state; /* lookup provider descriptor */ if ((desc = kcf_prov_tab_lookup((crypto_provider_id_t)handle)) == NULL) return (CRYPTO_UNKNOWN_PROVIDER); mutex_enter(&desc->pd_lock); /* * Check if any other thread is disabling or removing * this provider. We return if this is the case. */ if (desc->pd_state >= KCF_PROV_DISABLED) { mutex_exit(&desc->pd_lock); /* Release reference held by kcf_prov_tab_lookup(). */ KCF_PROV_REFRELE(desc); return (CRYPTO_BUSY); } saved_state = desc->pd_state; desc->pd_state = KCF_PROV_REMOVED; if (saved_state == KCF_PROV_BUSY) { /* * The per-provider taskq threads may be waiting. We * signal them so that they can start failing requests. */ cv_broadcast(&desc->pd_resume_cv); } if (desc->pd_prov_type == CRYPTO_SW_PROVIDER) { /* * Check if this provider is currently being used. * pd_irefcnt is the number of holds from the internal * structures. We add one to account for the above lookup. */ if (desc->pd_refcnt > desc->pd_irefcnt + 1) { desc->pd_state = saved_state; mutex_exit(&desc->pd_lock); /* Release reference held by kcf_prov_tab_lookup(). */ KCF_PROV_REFRELE(desc); /* * The administrator presumably will stop the clients * thus removing the holds, when they get the busy * return value. Any retry will succeed then. */ return (CRYPTO_BUSY); } } mutex_exit(&desc->pd_lock); if (desc->pd_prov_type != CRYPTO_SW_PROVIDER) { remove_provider(desc); } if (desc->pd_prov_type != CRYPTO_LOGICAL_PROVIDER) { /* remove the provider from the mechanisms tables */ for (mech_idx = 0; mech_idx < desc->pd_mech_list_count; mech_idx++) { kcf_remove_mech_provider( desc->pd_mechanisms[mech_idx].cm_mech_name, desc); } } /* remove provider from providers table */ if (kcf_prov_tab_rem_provider((crypto_provider_id_t)handle) != CRYPTO_SUCCESS) { /* Release reference held by kcf_prov_tab_lookup(). */ KCF_PROV_REFRELE(desc); return (CRYPTO_UNKNOWN_PROVIDER); } delete_kstat(desc); if (desc->pd_prov_type == CRYPTO_SW_PROVIDER) { /* Release reference held by kcf_prov_tab_lookup(). */ KCF_PROV_REFRELE(desc); /* * Wait till the existing requests complete. */ mutex_enter(&desc->pd_lock); while (desc->pd_state != KCF_PROV_FREED) cv_wait(&desc->pd_remove_cv, &desc->pd_lock); mutex_exit(&desc->pd_lock); } else { /* * Wait until requests that have been sent to the provider * complete. */ mutex_enter(&desc->pd_lock); while (desc->pd_irefcnt > 0) cv_wait(&desc->pd_remove_cv, &desc->pd_lock); mutex_exit(&desc->pd_lock); } kcf_do_notify(desc, B_FALSE); if (desc->pd_prov_type == CRYPTO_SW_PROVIDER) { /* * This is the only place where kcf_free_provider_desc() * is called directly. KCF_PROV_REFRELE() should free the * structure in all other places. */ ASSERT(desc->pd_state == KCF_PROV_FREED && desc->pd_refcnt == 0); kcf_free_provider_desc(desc); } else { KCF_PROV_REFRELE(desc); } return (CRYPTO_SUCCESS); } /* * This routine is used by software providers to determine * whether to use KM_SLEEP or KM_NOSLEEP during memory allocation. * Note that hardware providers can always use KM_SLEEP. So, * they do not need to call this routine. * * This routine can be called from user or interrupt context. */ int crypto_kmflag(crypto_req_handle_t handle) { return (REQHNDL2_KMFLAG(handle)); } /* * Process the mechanism info structures specified by the provider * during registration. A NULL crypto_provider_info_t indicates * an already initialized provider descriptor. * * Mechanisms are not added to the kernel's mechanism table if the * provider is a logical provider. * * Returns CRYPTO_SUCCESS on success, CRYPTO_ARGUMENTS if one * of the specified mechanisms was malformed, or CRYPTO_HOST_MEMORY * if the table of mechanisms is full. */ static int init_prov_mechs(const crypto_provider_info_t *info, kcf_provider_desc_t *desc) { uint_t mech_idx; uint_t cleanup_idx; int err = CRYPTO_SUCCESS; kcf_prov_mech_desc_t *pmd; int desc_use_count = 0; int mcount = desc->pd_mech_list_count; if (desc->pd_prov_type == CRYPTO_LOGICAL_PROVIDER) { if (info != NULL) { ASSERT(info->pi_mechanisms != NULL); bcopy(info->pi_mechanisms, desc->pd_mechanisms, sizeof (crypto_mech_info_t) * mcount); } return (CRYPTO_SUCCESS); } /* * Copy the mechanism list from the provider info to the provider * descriptor. desc->pd_mechanisms has an extra crypto_mech_info_t * element if the provider has random_ops since we keep an internal * mechanism, SUN_RANDOM, in this case. */ if (info != NULL) { ASSERT(info->pi_mechanisms != NULL); bcopy(info->pi_mechanisms, desc->pd_mechanisms, sizeof (crypto_mech_info_t) * mcount); } /* * For each mechanism support by the provider, add the provider * to the corresponding KCF mechanism mech_entry chain. */ for (mech_idx = 0; mech_idx < desc->pd_mech_list_count; mech_idx++) { crypto_mech_info_t *mi = &desc->pd_mechanisms[mech_idx]; if ((mi->cm_mech_flags & CRYPTO_KEYSIZE_UNIT_IN_BITS) && (mi->cm_mech_flags & CRYPTO_KEYSIZE_UNIT_IN_BYTES)) { err = CRYPTO_ARGUMENTS_BAD; break; } if (desc->pd_flags & CRYPTO_HASH_NO_UPDATE && mi->cm_func_group_mask & CRYPTO_FG_DIGEST) { /* * We ask the provider to specify the limit * per hash mechanism. But, in practice, a * hardware limitation means all hash mechanisms * will have the same maximum size allowed for * input data. So, we make it a per provider * limit to keep it simple. */ if (mi->cm_max_input_length == 0) { err = CRYPTO_ARGUMENTS_BAD; break; } else { desc->pd_hash_limit = mi->cm_max_input_length; } } if ((err = kcf_add_mech_provider(mech_idx, desc, &pmd)) != KCF_SUCCESS) break; if (pmd == NULL) continue; /* The provider will be used for this mechanism */ desc_use_count++; } /* * Don't allow multiple software providers with disabled mechanisms * to register. Subsequent enabling of mechanisms will result in * an unsupported configuration, i.e. multiple software providers * per mechanism. */ if (desc_use_count == 0 && desc->pd_prov_type == CRYPTO_SW_PROVIDER) return (CRYPTO_ARGUMENTS_BAD); if (err == KCF_SUCCESS) return (CRYPTO_SUCCESS); /* * An error occurred while adding the mechanism, cleanup * and bail. */ for (cleanup_idx = 0; cleanup_idx < mech_idx; cleanup_idx++) { kcf_remove_mech_provider( desc->pd_mechanisms[cleanup_idx].cm_mech_name, desc); } if (err == KCF_MECH_TAB_FULL) return (CRYPTO_HOST_MEMORY); return (CRYPTO_ARGUMENTS_BAD); } /* * Update routine for kstat. Only privileged users are allowed to * access this information, since this information is sensitive. * There are some cryptographic attacks (e.g. traffic analysis) * which can use this information. */ static int kcf_prov_kstat_update(kstat_t *ksp, int rw) { kcf_prov_stats_t *ks_data; kcf_provider_desc_t *pd = (kcf_provider_desc_t *)ksp->ks_private; if (rw == KSTAT_WRITE) return (EACCES); ks_data = ksp->ks_data; ks_data->ps_ops_total.value.ui64 = pd->pd_sched_info.ks_ndispatches; ks_data->ps_ops_failed.value.ui64 = pd->pd_sched_info.ks_nfails; ks_data->ps_ops_busy_rval.value.ui64 = pd->pd_sched_info.ks_nbusy_rval; ks_data->ps_ops_passed.value.ui64 = pd->pd_sched_info.ks_ndispatches - pd->pd_sched_info.ks_nfails - pd->pd_sched_info.ks_nbusy_rval; return (0); } /* * Utility routine called from failure paths in crypto_register_provider() * and from crypto_load_soft_disabled(). */ void undo_register_provider(kcf_provider_desc_t *desc, boolean_t remove_prov) { uint_t mech_idx; /* remove the provider from the mechanisms tables */ for (mech_idx = 0; mech_idx < desc->pd_mech_list_count; mech_idx++) { kcf_remove_mech_provider( desc->pd_mechanisms[mech_idx].cm_mech_name, desc); } /* remove provider from providers table */ if (remove_prov) (void) kcf_prov_tab_rem_provider(desc->pd_prov_id); } /* * Add provider (p1) to another provider's array of providers (p2). * Hardware and logical providers use this array to cross-reference * each other. */ static void add_provider_to_array(kcf_provider_desc_t *p1, kcf_provider_desc_t *p2) { kcf_provider_list_t *new; new = kmem_alloc(sizeof (kcf_provider_list_t), KM_SLEEP); mutex_enter(&p2->pd_lock); new->pl_next = p2->pd_provider_list; p2->pd_provider_list = new; KCF_PROV_IREFHOLD(p1); new->pl_provider = p1; mutex_exit(&p2->pd_lock); } /* * Remove provider (p1) from another provider's array of providers (p2). * Hardware and logical providers use this array to cross-reference * each other. */ static void remove_provider_from_array(kcf_provider_desc_t *p1, kcf_provider_desc_t *p2) { kcf_provider_list_t *pl = NULL, **prev; mutex_enter(&p2->pd_lock); for (pl = p2->pd_provider_list, prev = &p2->pd_provider_list; pl != NULL; prev = &pl->pl_next, pl = pl->pl_next) { if (pl->pl_provider == p1) { break; } } if (p1 == NULL) { mutex_exit(&p2->pd_lock); return; } /* detach and free kcf_provider_list structure */ KCF_PROV_IREFRELE(p1); *prev = pl->pl_next; kmem_free(pl, sizeof (*pl)); mutex_exit(&p2->pd_lock); } /* * Convert an array of logical provider handles (crypto_provider_id) * stored in a crypto_provider_info structure into an array of provider * descriptors (kcf_provider_desc_t) attached to a logical provider. */ static void process_logical_providers(const crypto_provider_info_t *info, kcf_provider_desc_t *hp) { kcf_provider_desc_t *lp; crypto_provider_id_t handle; int count = info->pi_logical_provider_count; int i; /* add hardware provider to each logical provider */ for (i = 0; i < count; i++) { handle = info->pi_logical_providers[i]; lp = kcf_prov_tab_lookup((crypto_provider_id_t)handle); if (lp == NULL) { continue; } add_provider_to_array(hp, lp); hp->pd_flags |= KCF_LPROV_MEMBER; /* * A hardware provider has to have the provider descriptor of * every logical provider it belongs to, so it can be removed * from the logical provider if the hardware provider * unregisters from the framework. */ add_provider_to_array(lp, hp); KCF_PROV_REFRELE(lp); } } /* * This routine removes a provider from all of the logical or * hardware providers it belongs to, and frees the provider's * array of pointers to providers. */ static void remove_provider(kcf_provider_desc_t *pp) { kcf_provider_desc_t *p; kcf_provider_list_t *e, *next; mutex_enter(&pp->pd_lock); for (e = pp->pd_provider_list; e != NULL; e = next) { p = e->pl_provider; remove_provider_from_array(pp, p); if (p->pd_prov_type == CRYPTO_HW_PROVIDER && p->pd_provider_list == NULL) p->pd_flags &= ~KCF_LPROV_MEMBER; KCF_PROV_IREFRELE(p); next = e->pl_next; kmem_free(e, sizeof (*e)); } pp->pd_provider_list = NULL; mutex_exit(&pp->pd_lock); } /* * Dispatch events as needed for a provider. is_added flag tells * whether the provider is registering or unregistering. */ void kcf_do_notify(kcf_provider_desc_t *prov_desc, boolean_t is_added) { int i; crypto_notify_event_change_t ec; ASSERT(prov_desc->pd_state > KCF_PROV_VERIFICATION_FAILED); /* * Inform interested clients of the mechanisms becoming * available/unavailable. We skip this for logical providers * as they do not affect mechanisms. */ if (prov_desc->pd_prov_type != CRYPTO_LOGICAL_PROVIDER) { ec.ec_provider_type = prov_desc->pd_prov_type; ec.ec_change = is_added ? CRYPTO_MECH_ADDED : CRYPTO_MECH_REMOVED; for (i = 0; i < prov_desc->pd_mech_list_count; i++) { (void) strlcpy(ec.ec_mech_name, prov_desc->pd_mechanisms[i].cm_mech_name, CRYPTO_MAX_MECH_NAME); kcf_walk_ntfylist(CRYPTO_EVENT_MECHS_CHANGED, &ec); } } /* * Inform interested clients about the new or departing provider. * In case of a logical provider, we need to notify the event only * for the logical provider and not for the underlying * providers which are known by the KCF_LPROV_MEMBER bit. */ if (prov_desc->pd_prov_type == CRYPTO_LOGICAL_PROVIDER || (prov_desc->pd_flags & KCF_LPROV_MEMBER) == 0) { kcf_walk_ntfylist(is_added ? CRYPTO_EVENT_PROVIDER_REGISTERED : CRYPTO_EVENT_PROVIDER_UNREGISTERED, prov_desc); } } static void delete_kstat(kcf_provider_desc_t *desc) { /* destroy the kstat created for this provider */ if (desc->pd_kstat != NULL) { kcf_provider_desc_t *kspd = desc->pd_kstat->ks_private; /* release reference held by desc->pd_kstat->ks_private */ ASSERT(desc == kspd); kstat_delete(kspd->pd_kstat); desc->pd_kstat = NULL; KCF_PROV_REFRELE(kspd); KCF_PROV_IREFRELE(kspd); } }