/* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The kernel SSL module ioctls. */ #include <sys/types.h> #include <sys/modctl.h> #include <sys/conf.h> #include <sys/ddi.h> #include <sys/sunddi.h> #include <sys/kmem.h> #include <sys/errno.h> #include <sys/file.h> #include <sys/cred.h> #include <sys/proc.h> #include <sys/task.h> #include <sys/model.h> #include <sys/sysmacros.h> #include <sys/policy.h> #include <sys/crypto/common.h> #include <sys/crypto/api.h> #include <inet/common.h> #include <inet/ip.h> #include "ksslimpl.h" #include "kssl.h" #include "ksslproto.h" kssl_entry_t **kssl_entry_tab; int kssl_entry_tab_size; int kssl_entry_tab_nentries; uint_t null_cipher_suite; /* setable in /etc/system */ kmutex_t kssl_tab_mutex; static void certificate_free(Certificate_t *cert) { kmem_free(cert->msg, cert->len); kmem_free(cert, sizeof (struct Certificate)); } static void privateKey_free(crypto_key_t *privkey) { int i; size_t attrs_size; crypto_object_attribute_t *attrs; attrs = privkey->ck_attrs; attrs_size = privkey->ck_count * sizeof (crypto_object_attribute_t); for (i = 0; i < privkey->ck_count; i++) { bzero(attrs[i].oa_value, attrs[i].oa_value_len); kmem_free(attrs[i].oa_value, attrs[i].oa_value_len); } kmem_free(attrs, attrs_size); kmem_free(privkey, sizeof (crypto_key_t)); } static void sess_free(kssl_session_info_t *s) { if (s->is_valid_handle) { (void) crypto_session_logout(s->prov, s->sid, NULL); (void) crypto_session_close(s->prov, s->sid, NULL); crypto_release_provider(s->prov); s->is_valid_handle = B_FALSE; } if (s->evnt_handle != NULL) { crypto_unnotify_events(s->evnt_handle); s->evnt_handle = NULL; } bzero(s->tokpin, s->pinlen); kmem_free(s, sizeof (kssl_session_info_t) + s->pinlen); } /* * Frees the space for the entry and the keys and certs * it carries. */ void kssl_free_entry(kssl_entry_t *kssl_entry) { int i; Certificate_t *cert; crypto_key_t *privkey; kssl_session_info_t *s; if (kssl_entry->ke_no_freeall) { kmem_free(kssl_entry, sizeof (kssl_entry_t)); return; } if ((cert = kssl_entry->ke_server_certificate) != NULL) { certificate_free(cert); } if ((privkey = kssl_entry->ke_private_key) != NULL) { privateKey_free(privkey); } for (i = 0; i < kssl_entry->sid_cache_nentries; i++) mutex_destroy(&(kssl_entry->sid_cache[i].se_lock)); kmem_free(kssl_entry->sid_cache, kssl_entry->sid_cache_nentries * sizeof (kssl_sid_ent_t)); ASSERT(kssl_entry->ke_proxy_head == NULL); ASSERT(kssl_entry->ke_fallback_head == NULL); if ((s = kssl_entry->ke_sessinfo) != NULL) { ASSERT(kssl_entry->ke_is_nxkey); sess_free(s); } kmem_free(kssl_entry, sizeof (kssl_entry_t)); } /* * Returns the index of the entry in kssl_entry_tab[] that matches * the address and port. Returns -1 if no match is found. */ static int kssl_find_entry(in6_addr_t laddr, in_port_t port, int type, boolean_t wild_card_match) { int i; kssl_entry_t *ep; ASSERT(MUTEX_HELD(&kssl_tab_mutex)); for (i = 0; i < kssl_entry_tab_size; i++) { ep = kssl_entry_tab[i]; if (ep == NULL) continue; if (!((type == IS_SSL_PORT && ep->ke_ssl_port == port) || (type == IS_PROXY_PORT && ep->ke_proxy_port == port))) continue; if (IN6_ARE_ADDR_EQUAL(&laddr, &ep->ke_laddr) || (wild_card_match && (IN6_IS_ADDR_UNSPECIFIED(&laddr) || IN6_IS_ADDR_UNSPECIFIED(&ep->ke_laddr)))) break; } if (i == kssl_entry_tab_size) return (-1); return (i); } static void copy_int_to_bytearray(int x, uchar_t *buf) { buf[0] = (x >> 16) & 0xff; buf[1] = (x >> 8) & 0xff; buf[2] = (x) & 0xff; } static int extract_certificate(kssl_params_t *kssl_params, Certificate_t **certpp) { int i, len; uint64_t in_size; uchar_t *end_pos; uint32_t ncert; uint32_t *cert_sizes; Certificate_t *cert; char *begin = (char *)kssl_params; uchar_t *cert_buf; int cert_buf_len; uchar_t *cert_from, *cert_to; ASSERT(kssl_params); in_size = kssl_params->kssl_params_size; end_pos = (uchar_t *)kssl_params + in_size; /* * Get the certs array. First the array of sizes, then the actual * certs. */ ncert = kssl_params->kssl_certs.sc_count; if (ncert == 0) { /* no certs in here! why did ya call? */ return (EINVAL); } if (in_size < (sizeof (kssl_params_t) + ncert * sizeof (uint32_t))) { return (EINVAL); } /* Trusting that the system call preserved the 4-byte alignment */ cert_sizes = (uint32_t *)(begin + kssl_params->kssl_certs.sc_sizes_offset); /* should this be an ASSERT()? */ if (!IS_P2ALIGNED(cert_sizes, sizeof (uint32_t))) { return (EINVAL); } len = 0; for (i = 0; i < ncert; i++) { if (cert_sizes[i] < 1) { return (EINVAL); } len += cert_sizes[i] + 3; } len += 3; /* length of certificate message without msg header */ cert_buf_len = len + 4 + 4; /* add space for msg headers */ cert_buf = kmem_alloc(cert_buf_len, KM_SLEEP); cert_buf[0] = (uchar_t)certificate; copy_int_to_bytearray(len, & cert_buf[1]); copy_int_to_bytearray(len - 3, & cert_buf[4]); cert_from = (uchar_t *)(begin + kssl_params->kssl_certs.sc_certs_offset); cert_to = &cert_buf[7]; for (i = 0; i < ncert; i++) { copy_int_to_bytearray(cert_sizes[i], cert_to); cert_to += 3; if (cert_from + cert_sizes[i] > end_pos) { kmem_free(cert_buf, cert_buf_len); return (EINVAL); } bcopy(cert_from, cert_to, cert_sizes[i]); cert_from += cert_sizes[i]; cert_to += cert_sizes[i]; } len += 4; cert_buf[len] = (uchar_t)server_hello_done; copy_int_to_bytearray(0, & cert_buf[len + 1]); cert = kmem_alloc(sizeof (Certificate_t), KM_SLEEP); cert->msg = cert_buf; cert->len = cert_buf_len; *certpp = cert; return (0); } static int extract_private_key(kssl_params_t *kssl_params, crypto_key_t **privkey) { char *begin = (char *)kssl_params; char *end_pos; int i, j, rv; size_t attrs_size; crypto_object_attribute_t *newattrs; char *mp_attrs; kssl_object_attribute_t att; char *attval; uint32_t attlen; crypto_key_t *kssl_privkey; end_pos = (char *)kssl_params + kssl_params->kssl_params_size; kssl_privkey = kmem_alloc(sizeof (crypto_key_t), KM_SLEEP); kssl_privkey->ck_format = kssl_params->kssl_privkey.ks_format; kssl_privkey->ck_count = kssl_params->kssl_privkey.ks_count; switch (kssl_privkey->ck_format) { case CRYPTO_KEY_ATTR_LIST: break; case CRYPTO_KEY_RAW: case CRYPTO_KEY_REFERENCE: default: rv = EINVAL; goto err1; } /* allocate the attributes */ attrs_size = kssl_privkey->ck_count * sizeof (crypto_object_attribute_t); mp_attrs = begin + kssl_params->kssl_privkey.ks_attrs_offset; if (mp_attrs + attrs_size > end_pos) { rv = EINVAL; goto err1; } newattrs = kmem_alloc(attrs_size, KM_SLEEP); /* Now the individual attributes */ for (i = 0; i < kssl_privkey->ck_count; i++) { bcopy(mp_attrs, &att, sizeof (kssl_object_attribute_t)); mp_attrs += sizeof (kssl_object_attribute_t); attval = begin + att.ka_value_offset; attlen = att.ka_value_len; if (attval + attlen > end_pos) { rv = EINVAL; goto err2; } newattrs[i].oa_type = att.ka_type; newattrs[i].oa_value_len = attlen; newattrs[i].oa_value = kmem_alloc(attlen, KM_SLEEP); bcopy(attval, newattrs[i].oa_value, attlen); } kssl_privkey->ck_attrs = newattrs; *privkey = kssl_privkey; return (0); err2: for (j = 0; j < i; j++) { bzero(newattrs[j].oa_value, newattrs[j].oa_value_len); kmem_free(newattrs[j].oa_value, newattrs[j].oa_value_len); } kmem_free(newattrs, attrs_size); err1: kmem_free(kssl_privkey, sizeof (crypto_key_t)); return (rv); } static int create_sessinfo(kssl_params_t *kssl_params, kssl_entry_t *kssl_entry) { int rv; char *p; kssl_session_info_t *s; kssl_tokinfo_t *t; t = &kssl_params->kssl_token; /* Do a sanity check */ if (t->pinlen > MAX_PIN_LENGTH) { return (EINVAL); } s = kmem_zalloc(sizeof (kssl_session_info_t) + t->pinlen, KM_SLEEP); s->pinlen = t->pinlen; bcopy(t->toklabel, s->toklabel, CRYPTO_EXT_SIZE_LABEL); p = (char *)kssl_params + t->tokpin_offset; bcopy(p, s->tokpin, s->pinlen); ASSERT(kssl_entry->ke_sessinfo == NULL); kssl_entry->ke_sessinfo = s; /* Get the handle to the non extractable key */ rv = kssl_get_obj_handle(kssl_entry); kssl_params->kssl_token.ck_rv = rv; if (rv != CRYPTO_SUCCESS) { sess_free(s); kssl_entry->ke_sessinfo = NULL; return (EINVAL); } kssl_entry->ke_sessinfo->is_valid_handle = B_TRUE; kssl_entry->ke_sessinfo->do_reauth = B_FALSE; kssl_entry->ke_sessinfo->evnt_handle = crypto_notify_events(kssl_prov_evnt, CRYPTO_EVENT_PROVIDER_REGISTERED | CRYPTO_EVENT_PROVIDER_UNREGISTERED); return (0); } static kssl_entry_t * create_kssl_entry(kssl_params_t *kssl_params, Certificate_t *cert, crypto_key_t *privkey) { int i; uint16_t s; kssl_entry_t *kssl_entry, *ep; uint_t cnt, mech_count; crypto_mech_name_t *mechs; boolean_t got_rsa, got_md5, got_sha1, got_rc4, got_des, got_3des; boolean_t got_aes; kssl_entry = kmem_zalloc(sizeof (kssl_entry_t), KM_SLEEP); kssl_entry->ke_laddr = kssl_params->kssl_addr.sin6_addr; kssl_entry->ke_ssl_port = kssl_params->kssl_addr.sin6_port; kssl_entry->ke_proxy_port = kssl_params->kssl_proxy_port; if (kssl_params->kssl_session_cache_timeout == 0) kssl_entry->sid_cache_timeout = DEFAULT_SID_TIMEOUT; else kssl_entry->sid_cache_timeout = kssl_params->kssl_session_cache_timeout; if (kssl_params->kssl_session_cache_size == 0) kssl_entry->sid_cache_nentries = DEFAULT_SID_CACHE_NENTRIES; else kssl_entry->sid_cache_nentries = kssl_params->kssl_session_cache_size; kssl_entry->ke_private_key = privkey; kssl_entry->ke_server_certificate = cert; kssl_entry->ke_is_nxkey = kssl_params->kssl_is_nxkey; if (kssl_entry->ke_is_nxkey) { if (create_sessinfo(kssl_params, kssl_entry) != 0) { kmem_free(kssl_entry, sizeof (kssl_entry_t)); return (NULL); } } mechs = crypto_get_mech_list(&mech_count, KM_SLEEP); if (mechs != NULL) { got_rsa = got_md5 = got_sha1 = got_rc4 = got_des = got_3des = got_aes = B_FALSE; for (i = 0; i < mech_count; i++) { if (strncmp(SUN_CKM_RSA_X_509, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_rsa = B_TRUE; else if (strncmp(SUN_CKM_MD5_HMAC, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_md5 = B_TRUE; else if (strncmp(SUN_CKM_SHA1_HMAC, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_sha1 = B_TRUE; else if (strncmp(SUN_CKM_RC4, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_rc4 = B_TRUE; else if (strncmp(SUN_CKM_DES_CBC, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_des = B_TRUE; else if (strncmp(SUN_CKM_DES3_CBC, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_3des = B_TRUE; else if (strncmp(SUN_CKM_AES_CBC, mechs[i], CRYPTO_MAX_MECH_NAME) == 0) got_aes = B_TRUE; } cnt = 0; ep = kssl_entry; for (i = 0; i < CIPHER_SUITE_COUNT - 1; i++) { switch (s = kssl_params->kssl_suites[i]) { case SSL_RSA_WITH_RC4_128_MD5: if (got_rsa && got_rc4 && got_md5) ep->kssl_cipherSuites[cnt++] = s; break; case SSL_RSA_WITH_RC4_128_SHA: if (got_rsa && got_rc4 && got_sha1) ep->kssl_cipherSuites[cnt++] = s; break; case SSL_RSA_WITH_DES_CBC_SHA: if (got_rsa && got_des && got_sha1) ep->kssl_cipherSuites[cnt++] = s; break; case SSL_RSA_WITH_3DES_EDE_CBC_SHA: if (got_rsa && got_3des && got_sha1) ep->kssl_cipherSuites[cnt++] = s; break; case TLS_RSA_WITH_AES_128_CBC_SHA: if (got_rsa && got_aes && got_sha1) ep->kssl_cipherSuites[cnt++] = s; break; case TLS_RSA_WITH_AES_256_CBC_SHA: if (got_rsa && got_aes && got_sha1) ep->kssl_cipherSuites[cnt++] = s; break; case CIPHER_NOTSET: default: break; } } crypto_free_mech_list(mechs, mech_count); } /* * Add the no encryption suite to the end if requested by the * kssl:null_cipher_suite /etc/system tunable since we do not want * to be running with it by default. */ if (null_cipher_suite && got_rsa && got_sha1) kssl_entry->kssl_cipherSuites[cnt++] = SSL_RSA_WITH_NULL_SHA; kssl_entry->kssl_cipherSuites_nentries = cnt; for (i = 0; i < cnt; i++) kssl_entry->kssl_saved_Suites[i] = kssl_entry->kssl_cipherSuites[i]; kssl_entry->sid_cache = kmem_alloc( kssl_entry->sid_cache_nentries * sizeof (kssl_sid_ent_t), KM_SLEEP); for (i = 0; i < kssl_entry->sid_cache_nentries; i++) { mutex_init(&(kssl_entry->sid_cache[i].se_lock), NULL, MUTEX_DEFAULT, NULL); kssl_entry->sid_cache[i].se_used = 0; kssl_entry->sid_cache[i].se_sid.cached = B_FALSE; } KSSL_ENTRY_REFHOLD(kssl_entry); return (kssl_entry); } int kssl_add_entry(kssl_params_t *kssl_params) { int rv, index, i; Certificate_t *cert; crypto_key_t *privkey; kssl_entry_t *kssl_entry; in6_addr_t laddr; if ((rv = extract_certificate(kssl_params, &cert)) != 0) { return (rv); } if ((rv = extract_private_key(kssl_params, &privkey)) != 0) { certificate_free(cert); return (rv); } kssl_entry = create_kssl_entry(kssl_params, cert, privkey); if (kssl_entry == NULL) { certificate_free(cert); privateKey_free(privkey); return (EINVAL); } laddr = kssl_params->kssl_addr.sin6_addr; retry: mutex_enter(&kssl_tab_mutex); /* Allocate the array first time here */ if (kssl_entry_tab == NULL) { size_t allocsize; kssl_entry_t **tmp_tab; int tmp_size; tmp_size = KSSL_TAB_INITSIZE; allocsize = tmp_size * sizeof (kssl_entry_t *); mutex_exit(&kssl_tab_mutex); tmp_tab = kmem_zalloc(allocsize, KM_SLEEP); mutex_enter(&kssl_tab_mutex); if (kssl_entry_tab != NULL) { mutex_exit(&kssl_tab_mutex); kmem_free(tmp_tab, allocsize); goto retry; } kssl_entry_tab_size = tmp_size; kssl_entry_tab = tmp_tab; index = 0; } else { /* Check if a matching entry exists already */ index = kssl_find_entry(laddr, kssl_params->kssl_addr.sin6_port, IS_SSL_PORT, B_TRUE); if (index == -1) { /* Check if an entry with the same proxy port exists */ if (kssl_find_entry(laddr, kssl_params->kssl_proxy_port, IS_PROXY_PORT, B_TRUE) != -1) { mutex_exit(&kssl_tab_mutex); kssl_free_entry(kssl_entry); return (EADDRINUSE); } /* No matching entry, find an empty spot */ for (i = 0; i < kssl_entry_tab_size; i++) { if (kssl_entry_tab[i] == NULL) break; } /* Table full. Gotta grow it */ if (i == kssl_entry_tab_size) { kssl_entry_t **new_tab, **old_tab; size_t allocsize; size_t oldtabsize = kssl_entry_tab_size * sizeof (kssl_entry_t *); int tmp_size, old_size; tmp_size = old_size = kssl_entry_tab_size; tmp_size += KSSL_TAB_INITSIZE; allocsize = tmp_size * sizeof (kssl_entry_t *); mutex_exit(&kssl_tab_mutex); new_tab = kmem_zalloc(allocsize, KM_SLEEP); mutex_enter(&kssl_tab_mutex); if (kssl_entry_tab_size > old_size) { mutex_exit(&kssl_tab_mutex); kmem_free(new_tab, allocsize); goto retry; } kssl_entry_tab_size = tmp_size; bcopy(kssl_entry_tab, new_tab, oldtabsize); old_tab = kssl_entry_tab; kssl_entry_tab = new_tab; kmem_free(old_tab, oldtabsize); } index = i; } else { kssl_entry_t *ep; /* * We do not want an entry with a specific address and * an entry with IN_ADDR_ANY to coexist. We could * replace the existing entry. But, most likely this * is misconfiguration. Better bail out with an error. */ ep = kssl_entry_tab[index]; if ((IN6_IS_ADDR_UNSPECIFIED(&laddr) && !IN6_IS_ADDR_UNSPECIFIED(&ep->ke_laddr)) || (!IN6_IS_ADDR_UNSPECIFIED(&laddr) && IN6_IS_ADDR_UNSPECIFIED(&ep->ke_laddr))) { mutex_exit(&kssl_tab_mutex); kssl_free_entry(kssl_entry); return (EEXIST); } /* Replace the existing entry */ KSSL_ENTRY_REFRELE(kssl_entry_tab[index]); kssl_entry_tab[index] = NULL; kssl_entry_tab_nentries--; } } kssl_entry_tab[index] = kssl_entry; kssl_entry_tab_nentries++; mutex_exit(&kssl_tab_mutex); return (0); } int kssl_delete_entry(struct sockaddr_in6 *kssl_addr) { in6_addr_t laddr; int index; laddr = kssl_addr->sin6_addr; mutex_enter(&kssl_tab_mutex); index = kssl_find_entry(laddr, kssl_addr->sin6_port, IS_SSL_PORT, B_FALSE); if (index == -1) { mutex_exit(&kssl_tab_mutex); return (ENOENT); } KSSL_ENTRY_REFRELE(kssl_entry_tab[index]); kssl_entry_tab[index] = NULL; kssl_entry_tab_nentries--; mutex_exit(&kssl_tab_mutex); return (0); } /* * We care about only one private key object. * So, set the max count to only 1. */ #define MAX_OBJECT_COUNT 1 /* * Open a session to the provider specified by the label and * authenticate to it. Find the private key object with the * specified attributes and save the handle. The userland component * must set all the attributes in the template so as to uniquely * identify the object. * * Note that the handle will be invalid if we logout or close * the session to the provider. */ int kssl_get_obj_handle(kssl_entry_t *kp) { int rv; unsigned int count; void *cookie = NULL; crypto_provider_t prov; kssl_session_info_t *s; crypto_session_id_t sid; crypto_object_attribute_t *attrs; crypto_object_id_t ohndl[MAX_OBJECT_COUNT]; char label[CRYPTO_EXT_SIZE_LABEL + 1]; ASSERT(kp->ke_is_nxkey); s = kp->ke_sessinfo; bcopy(s->toklabel, label, CRYPTO_EXT_SIZE_LABEL); label[CRYPTO_EXT_SIZE_LABEL] = '\0'; prov = crypto_get_provider(label, NULL, NULL); if (prov == NULL) return (CRYPTO_UNKNOWN_PROVIDER); rv = crypto_session_open(prov, &sid, NULL); if (rv != CRYPTO_SUCCESS) { goto err1; } rv = crypto_session_login(prov, sid, CRYPTO_USER, s->tokpin, s->pinlen, NULL); if (rv != CRYPTO_SUCCESS) { goto err2; } count = kp->ke_private_key->ck_count; attrs = kp->ke_private_key->ck_attrs; rv = crypto_object_find_init(prov, sid, attrs, count, &cookie, NULL); if (rv != CRYPTO_SUCCESS) { goto err3; } rv = crypto_object_find(prov, cookie, ohndl, &count, MAX_OBJECT_COUNT, NULL); if (rv != CRYPTO_SUCCESS || count == 0) { if (count == 0) rv = CRYPTO_FAILED; goto err3; } (void) crypto_object_find_final(prov, cookie, NULL); s->sid = sid; s->prov = prov; s->key.ck_format = CRYPTO_KEY_REFERENCE; /* Keep the handle around for later use */ s->key.ck_obj_id = ohndl[0]; return (CRYPTO_SUCCESS); err3: (void) crypto_session_logout(prov, sid, NULL); err2: (void) crypto_session_close(prov, sid, NULL); err1: crypto_release_provider(prov); return (rv); }