/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #define IDENT_N2RNG "SUNW_N2_Random_Number_Generator" #define N2RNG_PROVIDER2N2RNG(x) (((n2rng_provider_private_t *)x)->mp_n2rng) static void n2rng_provider_status(crypto_provider_handle_t, uint_t *); static int n2rng_random_number(crypto_provider_handle_t, crypto_session_id_t, uchar_t *, size_t, crypto_req_handle_t); static int ext_info(crypto_provider_handle_t, crypto_provider_ext_info_t *, crypto_req_handle_t); void n2rng_ksinit(n2rng_t *n2rng); void n2rng_ksdeinit(n2rng_t *n2rng); static int fips_init(n2rng_t *n2rng); static void fips_fini(n2rng_t *n2rng); int fips_random(n2rng_t *n2rng, uint8_t *out, size_t nbytes); static crypto_control_ops_t n2rng_control_ops = { n2rng_provider_status }; static crypto_random_number_ops_t n2rng_rng_ops = { NULL, /* seed_random */ n2rng_random_number }; static crypto_provider_management_ops_t n2rng_extinfo_op = { ext_info, /* ext_info */ NULL, /* init_token */ NULL, /* init_pin */ NULL, /* set_pin */ }; static crypto_ops_t n2rng_ops = { &n2rng_control_ops, NULL, /* digest_ops */ NULL, /* cipher_ops */ NULL, /* mac_ops */ NULL, /* sign_ops */ NULL, /* verify_ops */ NULL, /* dual_ops */ NULL, /* cipher_mac_ops */ &n2rng_rng_ops, /* rng_ops */ NULL, /* session_ops */ NULL, /* object_ops */ NULL, /* key_ops */ &n2rng_extinfo_op, /* management_ops */ NULL, /* ctx_ops */ NULL /* mech_ops */ }; static crypto_provider_info_t n2rng_prov_info = { CRYPTO_SPI_VERSION_2, NULL, /* pi_provider_description */ CRYPTO_HW_PROVIDER, NULL, /* pi_provider_dev */ NULL, /* pi_provider_handle */ &n2rng_ops, 0, /* number of mechanisms */ NULL, /* mechanism table */ 0, /* pi_logical_provider_count */ NULL /* pi_logical_providers */ }; static void strncpy_spacepad(uchar_t *s1, char *s2, int n) { int s2len = strlen(s2); (void) strncpy((char *)s1, s2, n); if (s2len < n) (void) memset(s1 + s2len, ' ', n - s2len); } /*ARGSUSED*/ static int ext_info(crypto_provider_handle_t prov, crypto_provider_ext_info_t *ext_info, crypto_req_handle_t cfreq) { #define BUFSZ 64 n2rng_t *n2rng = (n2rng_t *)prov; char buf[BUFSZ]; /* handle info common to logical and hardware provider */ /* Manufacturer ID */ strncpy_spacepad(ext_info->ei_manufacturerID, N2RNG_MANUFACTURER_ID, CRYPTO_EXT_SIZE_MANUF); /* Model */ strncpy_spacepad(ext_info->ei_model, "0", CRYPTO_EXT_SIZE_MODEL); /* Token flags */ ext_info->ei_flags = CRYPTO_EXTF_RNG | CRYPTO_EXTF_SO_PIN_LOCKED | CRYPTO_EXTF_WRITE_PROTECTED; ext_info->ei_max_session_count = CRYPTO_EFFECTIVELY_INFINITE; ext_info->ei_max_pin_len = 0; ext_info->ei_min_pin_len = 0; ext_info->ei_total_public_memory = CRYPTO_UNAVAILABLE_INFO; ext_info->ei_free_public_memory = CRYPTO_UNAVAILABLE_INFO; ext_info->ei_total_private_memory = CRYPTO_UNAVAILABLE_INFO; ext_info->ei_free_private_memory = CRYPTO_UNAVAILABLE_INFO; /* Time. No need to be supplied for token without a clock */ ext_info->ei_time[0] = '\000'; /* handle hardware provider specific fields */ /* Token label */ (void) snprintf(buf, BUFSZ, "%s/%d SUNW_N2_RNG", ddi_driver_name(n2rng->n_dip), ddi_get_instance(n2rng->n_dip)); /* Serial number */ strncpy_spacepad(ext_info->ei_serial_number, "0", CRYPTO_EXT_SIZE_SERIAL); /* Version info */ ext_info->ei_hardware_version.cv_major = 0; ext_info->ei_hardware_version.cv_minor = 0; ext_info->ei_firmware_version.cv_major = 0; ext_info->ei_firmware_version.cv_minor = 0; buf[BUFSZ - 1] = '\000'; /* set the token label */ strncpy_spacepad(ext_info->ei_label, buf, CRYPTO_EXT_SIZE_LABEL); #undef BUFSZ return (CRYPTO_SUCCESS); } static void unregister_task(void *targ) { n2rng_t *n2rng = (n2rng_t *)targ; /* Unregister provider without checking result */ (void) n2rng_unregister_provider(n2rng); } /* * Register with KCF if not already registered */ int n2rng_register_provider(n2rng_t *n2rng) { int ret; if (n2rng_isregistered(n2rng)) { DBG0(n2rng, DKCF, "n2rng_kcf: Crypto provider already " "registered"); return (DDI_SUCCESS); } else { ret = crypto_register_provider(&n2rng_prov_info, &n2rng->n_prov); if (ret == CRYPTO_SUCCESS) { DBG0(n2rng, DKCF, "n2rng_kcf: Crypto provider " "registered"); } else { cmn_err(CE_WARN, "crypto_register_provider() failed (%d)", ret); n2rng->n_prov = 0; return (DDI_FAILURE); } } n2rng_setregistered(n2rng); crypto_provider_notification(n2rng->n_prov, CRYPTO_PROVIDER_READY); return (DDI_SUCCESS); } /* * Unregister with KCF if not already registered */ int n2rng_unregister_provider(n2rng_t *n2rng) { if (!n2rng_isregistered(n2rng)) { DBG0(n2rng, DKCF, "n2rng_kcf: Crypto provider already " "unregistered"); } else { if (crypto_unregister_provider(n2rng->n_prov) == CRYPTO_SUCCESS) { DBG0(n2rng, DKCF, "n2rng_kcf: Crypto provider " "unregistered"); } else { n2rng_error(n2rng, "unable to unregister from kcf"); return (DDI_FAILURE); } } n2rng->n_prov = 0; n2rng_clrregistered(n2rng); return (DDI_SUCCESS); } /* * Set state to failed for all rngs if in control domain and dispatch a task * to unregister from kcf */ void n2rng_failure(n2rng_t *n2rng) { int rngid; rng_entry_t *rng; mutex_enter(&n2rng->n_lock); /* Check if error has already been detected */ if (n2rng_isfailed(n2rng)) { mutex_exit(&n2rng->n_lock); return; } cmn_err(CE_WARN, "n2rng: hardware failure detected"); n2rng_setfailed(n2rng); /* Set each rng to failed if running in control domain */ if (n2rng_iscontrol(n2rng)) { for (rngid = 0; rngid < n2rng->n_ctl_data->n_num_rngs; rngid++) { rng = &n2rng->n_ctl_data->n_rngs[rngid]; rng->n_rng_state = CTL_STATE_ERROR; } } mutex_exit(&n2rng->n_lock); /* Dispatch task to unregister from kcf */ if (ddi_taskq_dispatch(n2rng->n_taskq, unregister_task, (void *)n2rng, DDI_SLEEP) != DDI_SUCCESS) { cmn_err(CE_WARN, "n2rng: ddi_taskq_dispatch() failed"); } } /* * Set state to unconfigured for all rngs if in control domain and dispatch a * task to unregister from kcf. */ void n2rng_unconfigured(n2rng_t *n2rng) { int rngid; rng_entry_t *rng; mutex_enter(&n2rng->n_lock); /* Check if unconfigured state has already been detected */ if (!n2rng_isconfigured(n2rng)) { mutex_exit(&n2rng->n_lock); return; } cmn_err(CE_WARN, "n2rng: no longer generating entropy"); n2rng_clrconfigured(n2rng); /* Set each rng to unconfigured if running in control domain */ if (n2rng_iscontrol(n2rng)) { for (rngid = 0; rngid < n2rng->n_ctl_data->n_num_rngs; rngid++) { rng = &n2rng->n_ctl_data->n_rngs[rngid]; rng->n_rng_state = CTL_STATE_UNCONFIGURED; } } mutex_exit(&n2rng->n_lock); /* Dispatch task to unregister from kcf */ if (ddi_taskq_dispatch(n2rng->n_taskq, unregister_task, (void *)n2rng, DDI_SLEEP) != DDI_SUCCESS) { cmn_err(CE_WARN, "n2rng: ddi_taskq_dispatch() failed"); } else { /* Schedule a configuration retry */ n2rng_config_retry(n2rng, RNG_CFG_RETRY_SECS); } } /* * Setup and also register to kCF */ int n2rng_init(n2rng_t *n2rng) { int ret; char ID[64]; dev_info_t *dip; dip = n2rng->n_dip; /* Initialize data structures if not already done */ if (!n2rng_isinitialized(n2rng)) { /* initialize kstats */ n2rng_ksinit(n2rng); /* initialize the FIPS data and mutexes */ ret = fips_init(n2rng); if (ret) { n2rng_ksdeinit(n2rng); return (DDI_FAILURE); } } /* * Register with crypto framework if not already registered. * Be careful not to exceed 32 characters. */ (void) sprintf(ID, "%s/%d %s", ddi_driver_name(dip), ddi_get_instance(dip), IDENT_N2RNG); n2rng_prov_info.pi_provider_description = ID; n2rng_prov_info.pi_provider_dev.pd_hw = dip; n2rng_prov_info.pi_provider_handle = n2rng; n2rng_setinitialized(n2rng); ret = n2rng_register_provider(n2rng); if (ret != DDI_SUCCESS) { fips_fini(n2rng); n2rng_ksdeinit(n2rng); n2rng_clrinitialized(n2rng); return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * Unregister from kCF and cleanup */ int n2rng_uninit(n2rng_t *n2rng) { /* Un-initialize data structures if they exist */ if (n2rng_isinitialized(n2rng)) { /* * Unregister from kCF. * This needs to be done at the beginning of detach. */ if (n2rng_unregister_provider(n2rng) != DDI_SUCCESS) { return (DDI_FAILURE); } fips_fini(n2rng); /* deinitialize kstats */ n2rng_ksdeinit(n2rng); n2rng_clrinitialized(n2rng); } return (DDI_SUCCESS); } /* * At this time there are no periodic health checks. If the health * check done at attrach time fails, the driver does not even attach. * So there are no failure conditions to report, and this provider is * never busy. */ /* ARGSUSED */ static void n2rng_provider_status(crypto_provider_handle_t provider, uint_t *status) { *status = CRYPTO_PROVIDER_READY; } /*ARGSUSED*/ static int n2rng_random_number(crypto_provider_handle_t provider, crypto_session_id_t sess, unsigned char *buf, size_t buflen, crypto_req_handle_t cfreq) { n2rng_t *n2rng = (n2rng_t *)provider; int rv; rv = fips_random(n2rng, buf, buflen); atomic_add_64(&n2rng->n_stats[DS_RNGBYTES], buflen); atomic_inc_64(&n2rng->n_stats[DS_RNGJOBS]); return (rv); } static int fips_init(n2rng_t *n2rng) { int i; int rv; n2rng->n_frs.fips_round_robin_j = 0; for (i = 0; i < N2RNG_FIPS_INSTANCES; i++) { rv = n2rng_fips_random_init(n2rng, &n2rng->n_frs.fipsarray[i]); if (rv) { /* finalize all the FIPS structures allocated so far */ for (--i; i >= 0; --i) { n2rng_fips_random_fini( &n2rng->n_frs.fipsarray[i]); } return (rv); } } return (0); } static void fips_fini(n2rng_t *n2rng) { int i; for (i = 0; i < N2RNG_FIPS_INSTANCES; i++) { n2rng_fips_random_fini(&n2rng->n_frs.fipsarray[i]); } }