925 lines
27 KiB
C
925 lines
27 KiB
C
/*
|
|
* 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 <sys/zfs_context.h>
|
|
#include <sys/crypto/common.h>
|
|
#include <sys/crypto/impl.h>
|
|
#include <sys/crypto/sched_impl.h>
|
|
#include <sys/crypto/spi.h>
|
|
|
|
/*
|
|
* minalloc and maxalloc values to be used for taskq_create().
|
|
*/
|
|
int crypto_taskq_threads = CRYPTO_TASKQ_THREADS;
|
|
int crypto_taskq_minalloc = CRYPTO_TASKQ_MIN;
|
|
int crypto_taskq_maxalloc = CRYPTO_TASKQ_MAX;
|
|
|
|
static void remove_provider(kcf_provider_desc_t *);
|
|
static void process_logical_providers(crypto_provider_info_t *,
|
|
kcf_provider_desc_t *);
|
|
static int init_prov_mechs(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 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 }
|
|
};
|
|
|
|
#define KCF_SPI_COPY_OPS(src, dst, ops) if ((src)->ops != NULL) \
|
|
*((dst)->ops) = *((src)->ops);
|
|
|
|
/*
|
|
* Copy an ops vector from src to dst. Used during provider registration
|
|
* to copy the ops vector from the provider info structure to the
|
|
* provider descriptor maintained by KCF.
|
|
* Copying the ops vector specified by the provider is needed since the
|
|
* framework does not require the provider info structure to be
|
|
* persistent.
|
|
*/
|
|
static void
|
|
copy_ops_vector_v1(crypto_ops_t *src_ops, crypto_ops_t *dst_ops)
|
|
{
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_control_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_digest_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_cipher_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_mac_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_sign_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_verify_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_dual_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_dual_cipher_mac_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_random_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_session_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_object_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_key_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_provider_ops);
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_ctx_ops);
|
|
}
|
|
|
|
static void
|
|
copy_ops_vector_v2(crypto_ops_t *src_ops, crypto_ops_t *dst_ops)
|
|
{
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_mech_ops);
|
|
}
|
|
|
|
static void
|
|
copy_ops_vector_v3(crypto_ops_t *src_ops, crypto_ops_t *dst_ops)
|
|
{
|
|
KCF_SPI_COPY_OPS(src_ops, dst_ops, co_nostore_key_ops);
|
|
}
|
|
|
|
/*
|
|
* 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(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;
|
|
|
|
if (info->pi_interface_version > CRYPTO_SPI_VERSION_3)
|
|
return (CRYPTO_VERSION_MISMATCH);
|
|
|
|
/*
|
|
* 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 */
|
|
if (info->pi_provider_description != NULL) {
|
|
/*
|
|
* pi_provider_descriptor is a string that can contain
|
|
* up to CRYPTO_PROVIDER_DESCR_MAX_LEN + 1 characters
|
|
* INCLUDING the terminating null character. A bcopy()
|
|
* is necessary here as pd_description should not have
|
|
* a null character. See comments in kcf_alloc_provider_desc()
|
|
* for details on pd_description field.
|
|
*/
|
|
bcopy(info->pi_provider_description, prov_desc->pd_description,
|
|
MIN(strlen(info->pi_provider_description),
|
|
(size_t)CRYPTO_PROVIDER_DESCR_MAX_LEN));
|
|
}
|
|
|
|
if (info->pi_provider_type != CRYPTO_LOGICAL_PROVIDER) {
|
|
if (info->pi_ops_vector == NULL) {
|
|
goto bail;
|
|
}
|
|
copy_ops_vector_v1(info->pi_ops_vector,
|
|
prov_desc->pd_ops_vector);
|
|
if (info->pi_interface_version >= CRYPTO_SPI_VERSION_2) {
|
|
copy_ops_vector_v2(info->pi_ops_vector,
|
|
prov_desc->pd_ops_vector);
|
|
prov_desc->pd_flags = info->pi_flags;
|
|
}
|
|
if (info->pi_interface_version == CRYPTO_SPI_VERSION_3) {
|
|
copy_ops_vector_v3(info->pi_ops_vector,
|
|
prov_desc->pd_ops_vector);
|
|
}
|
|
}
|
|
|
|
/* object_ops and nostore_key_ops are mutually exclusive */
|
|
if (prov_desc->pd_ops_vector->co_object_ops &&
|
|
prov_desc->pd_ops_vector->co_nostore_key_ops) {
|
|
goto bail;
|
|
}
|
|
|
|
/* 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_minalloc, crypto_taskq_maxalloc,
|
|
TASKQ_PREPOPULATE);
|
|
else
|
|
prov_desc->pd_sched_info.ks_taskq = NULL;
|
|
|
|
/* no kernel session to logical providers */
|
|
if (prov_desc->pd_prov_type != CRYPTO_LOGICAL_PROVIDER) {
|
|
/*
|
|
* Open a session for session-oriented providers. This session
|
|
* is used for all kernel consumers. This is fine as a provider
|
|
* is required to support multiple thread access to a session.
|
|
* We can do this only after the taskq has been created as we
|
|
* do a kcf_submit_request() to open the session.
|
|
*/
|
|
if (KCF_PROV_SESSION_OPS(prov_desc) != NULL) {
|
|
kcf_req_params_t params;
|
|
|
|
KCF_WRAP_SESSION_OPS_PARAMS(¶ms,
|
|
KCF_OP_SESSION_OPEN, &prov_desc->pd_sid, 0,
|
|
CRYPTO_USER, NULL, 0, prov_desc);
|
|
ret = kcf_submit_request(prov_desc, NULL, NULL, ¶ms,
|
|
B_FALSE);
|
|
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
undo_register_provider(prov_desc, B_TRUE);
|
|
ret = CRYPTO_FAILED;
|
|
goto bail;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
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 to notify the framework that the state of
|
|
* a cryptographic provider has changed. Valid state codes are:
|
|
*
|
|
* CRYPTO_PROVIDER_READY
|
|
* The provider indicates that it can process more requests. A provider
|
|
* will notify with this event if it previously has notified us with a
|
|
* CRYPTO_PROVIDER_BUSY.
|
|
*
|
|
* CRYPTO_PROVIDER_BUSY
|
|
* The provider can not take more requests.
|
|
*
|
|
* CRYPTO_PROVIDER_FAILED
|
|
* The provider encountered an internal error. The framework will not
|
|
* be sending any more requests to the provider. The provider may notify
|
|
* with a CRYPTO_PROVIDER_READY, if it is able to recover from the error.
|
|
*
|
|
* This routine can be called from user or interrupt context.
|
|
*/
|
|
void
|
|
crypto_provider_notification(crypto_kcf_provider_handle_t handle, uint_t state)
|
|
{
|
|
kcf_provider_desc_t *pd;
|
|
|
|
/* lookup the provider from the given handle */
|
|
if ((pd = kcf_prov_tab_lookup((crypto_provider_id_t)handle)) == NULL)
|
|
return;
|
|
|
|
mutex_enter(&pd->pd_lock);
|
|
|
|
if (pd->pd_state <= KCF_PROV_VERIFICATION_FAILED)
|
|
goto out;
|
|
|
|
if (pd->pd_prov_type == CRYPTO_LOGICAL_PROVIDER) {
|
|
cmn_err(CE_WARN, "crypto_provider_notification: "
|
|
"logical provider (%x) ignored\n", handle);
|
|
goto out;
|
|
}
|
|
switch (state) {
|
|
case CRYPTO_PROVIDER_READY:
|
|
switch (pd->pd_state) {
|
|
case KCF_PROV_BUSY:
|
|
pd->pd_state = KCF_PROV_READY;
|
|
/*
|
|
* Signal the per-provider taskq threads that they
|
|
* can start submitting requests.
|
|
*/
|
|
cv_broadcast(&pd->pd_resume_cv);
|
|
break;
|
|
|
|
case KCF_PROV_FAILED:
|
|
/*
|
|
* The provider recovered from the error. Let us
|
|
* use it now.
|
|
*/
|
|
pd->pd_state = KCF_PROV_READY;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CRYPTO_PROVIDER_BUSY:
|
|
switch (pd->pd_state) {
|
|
case KCF_PROV_READY:
|
|
pd->pd_state = KCF_PROV_BUSY;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CRYPTO_PROVIDER_FAILED:
|
|
/*
|
|
* We note the failure and return. The per-provider taskq
|
|
* threads check this flag and start failing the
|
|
* requests, if it is set. See process_req_hwp() for details.
|
|
*/
|
|
switch (pd->pd_state) {
|
|
case KCF_PROV_READY:
|
|
pd->pd_state = KCF_PROV_FAILED;
|
|
break;
|
|
|
|
case KCF_PROV_BUSY:
|
|
pd->pd_state = KCF_PROV_FAILED;
|
|
/*
|
|
* The per-provider taskq threads may be waiting. We
|
|
* signal them so that they can start failing requests.
|
|
*/
|
|
cv_broadcast(&pd->pd_resume_cv);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
out:
|
|
mutex_exit(&pd->pd_lock);
|
|
KCF_PROV_REFRELE(pd);
|
|
}
|
|
|
|
/*
|
|
* This routine is used to notify the framework the result of
|
|
* an asynchronous request handled by a provider. Valid error
|
|
* codes are the same as the CRYPTO_* errors defined in common.h.
|
|
*
|
|
* This routine can be called from user or interrupt context.
|
|
*/
|
|
void
|
|
crypto_op_notification(crypto_req_handle_t handle, int error)
|
|
{
|
|
kcf_call_type_t ctype;
|
|
|
|
if (handle == NULL)
|
|
return;
|
|
|
|
if ((ctype = GET_REQ_TYPE(handle)) == CRYPTO_SYNCH) {
|
|
kcf_sreq_node_t *sreq = (kcf_sreq_node_t *)handle;
|
|
|
|
if (error != CRYPTO_SUCCESS)
|
|
sreq->sn_provider->pd_sched_info.ks_nfails++;
|
|
KCF_PROV_IREFRELE(sreq->sn_provider);
|
|
kcf_sop_done(sreq, error);
|
|
} else {
|
|
kcf_areq_node_t *areq = (kcf_areq_node_t *)handle;
|
|
|
|
ASSERT(ctype == CRYPTO_ASYNCH);
|
|
if (error != CRYPTO_SUCCESS)
|
|
areq->an_provider->pd_sched_info.ks_nfails++;
|
|
KCF_PROV_IREFRELE(areq->an_provider);
|
|
kcf_aop_done(areq, error);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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(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) {
|
|
if (info->pi_ops_vector->co_random_ops != NULL) {
|
|
crypto_mech_info_t *rand_mi;
|
|
|
|
/*
|
|
* Need the following check as it is possible to have
|
|
* a provider that implements just random_ops and has
|
|
* pi_mechanisms == NULL.
|
|
*/
|
|
if (info->pi_mechanisms != NULL) {
|
|
bcopy(info->pi_mechanisms, desc->pd_mechanisms,
|
|
sizeof (crypto_mech_info_t) * (mcount - 1));
|
|
}
|
|
rand_mi = &desc->pd_mechanisms[mcount - 1];
|
|
|
|
bzero(rand_mi, sizeof (crypto_mech_info_t));
|
|
(void) strncpy(rand_mi->cm_mech_name, SUN_RANDOM,
|
|
CRYPTO_MAX_MECH_NAME);
|
|
rand_mi->cm_func_group_mask = CRYPTO_FG_RANDOM;
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Utility routine called from crypto_load_soft_disabled(). Callers
|
|
* should have done a prior undo_register_provider().
|
|
*/
|
|
void
|
|
redo_register_provider(kcf_provider_desc_t *pd)
|
|
{
|
|
/* process the mechanisms supported by the provider */
|
|
(void) init_prov_mechs(NULL, pd);
|
|
|
|
/*
|
|
* Hold provider in providers table. We should not call
|
|
* kcf_prov_tab_add_provider() here as the provider descriptor
|
|
* is still valid which means it has an entry in the provider
|
|
* table.
|
|
*/
|
|
KCF_PROV_REFHOLD(pd);
|
|
KCF_PROV_IREFHOLD(pd);
|
|
}
|
|
|
|
/*
|
|
* 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(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);
|
|
}
|
|
}
|