usr/src/uts/common/inet/kssl/ksslrec.c
author kais
Sat, 12 Nov 2005 18:58:05 -0800
changeset 898 64b2a371a6bd
child 1724 49de84004ca4
permissions -rw-r--r--
PSARC/2005/625 Greyhound - Solaris Kernel SSL proxy 4931229 Kernel-level SSL proxy

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#define	_SUN_TPI_VERSION 2
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/vtrace.h>
#include <sys/kmem.h>
#include <sys/cpuvar.h>
#include <sys/atomic.h>
#include <sys/sysmacros.h>

#include <sys/errno.h>
#include <sys/isa_defs.h>
#include <sys/md5.h>
#include <sys/sha1.h>
#include <sys/random.h>
#include <inet/common.h>
#include <netinet/in.h>

#include <sys/systm.h>
#include <sys/param.h>

#include "ksslimpl.h"
#include "ksslapi.h"
#include "ksslproto.h"
#include "kssldebug.h"

static ssl3CipherSuiteDef cipher_suite_defs[] = {
	{SSL_RSA_WITH_RC4_128_SHA,	cipher_rc4,	mac_sha,	72},
	{SSL_RSA_WITH_RC4_128_MD5,	cipher_rc4,	mac_md5,	64},
	{SSL_RSA_WITH_DES_CBC_SHA,	cipher_des,	mac_sha,	72},
	{SSL_RSA_WITH_3DES_EDE_CBC_SHA,	cipher_3des,	mac_sha,	104},
	{SSL_RSA_WITH_NULL_SHA,		cipher_null,	mac_sha,	40}
};

static int cipher_suite_defs_nentries =
    sizeof (cipher_suite_defs) / sizeof (cipher_suite_defs[0]);

static KSSLMACDef mac_defs[] = { /* indexed by SSL3MACAlgorithm */
	/* macsz padsz HashInit HashUpdate HashFinal */

	{MD5_HASH_LEN, SSL3_MD5_PAD_LEN,
	    (hashinit_func_t)MD5Init, (hashupdate_func_t)MD5Update,
	    (hashfinal_func_t)MD5Final},

	{SHA1_HASH_LEN, SSL3_SHA1_PAD_LEN,
	    (hashinit_func_t)SHA1Init, (hashupdate_func_t)SHA1Update,
	    (hashfinal_func_t)SHA1Final},
};

static uchar_t kssl_pad_1[60] = {
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36
};
static uchar_t kssl_pad_2[60] = {
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c
};

int kssl_debug;
int kssl_cache_count;
static boolean_t kssl_synchronous = B_FALSE;

static void kssl_update_handshake_hashes(ssl_t *, uchar_t *, uint_t);
static int kssl_compute_handshake_hashes(ssl_t *, SSL3Hashes *, uint32_t);
static int kssl_handle_client_hello(ssl_t *, mblk_t *, int);
static int kssl_handle_client_key_exchange(ssl_t *, mblk_t *, int,
    kssl_callback_t, void *);
static int kssl_send_server_hello(ssl_t *);
static int kssl_send_certificate_and_server_hello_done(ssl_t *);
static int kssl_send_change_cipher_specs(ssl_t *);
static int kssl_send_finished(ssl_t *, int);
static int kssl_handle_finished(ssl_t *, mblk_t *, int);
static void kssl_get_hello_random(uchar_t *);
static uchar_t *kssl_rsa_unwrap(uchar_t *, size_t *);
static void kssl_cache_sid(sslSessionID *, kssl_entry_t *);
static void kssl_lookup_sid(sslSessionID *, uchar_t *, ipaddr_t,
    kssl_entry_t *);
static int kssl_generate_tls_ms(ssl_t *, uchar_t *, size_t);
static void kssl_generate_ssl_ms(ssl_t *, uchar_t *, size_t);
static int kssl_generate_tls_keyblock(ssl_t *);
static void kssl_generate_keyblock(ssl_t *);
static void kssl_ssl3_key_material_derive_step(ssl_t *, uchar_t *, size_t,
    int, uchar_t *, int);
static int kssl_tls_PRF(ssl_t *, uchar_t *, size_t,
    uchar_t *, size_t, uchar_t *, size_t, uchar_t *, size_t);
static int kssl_tls_P_hash(crypto_mechanism_t *, crypto_key_t *,
    size_t, uchar_t *, size_t, uchar_t *, size_t, uchar_t *, size_t);
static void kssl_cke_done(void *, int);

#define	MAX_TLS_KEYBLOCK_SIZE 160 /* more than enough for largest TLS key */

#define	HMAC_INIT(m, k, c) \
	rv = crypto_mac_init(m, k, NULL, c, NULL); if (CRYPTO_ERR(rv)) goto end;

#define	HMAC_UPDATE(c, d, l) \
	dd.cd_raw.iov_base = (char *)d; \
	dd.cd_length = dd.cd_raw.iov_len = l; \
	rv = crypto_mac_update(c, &dd, NULL); if (CRYPTO_ERR(rv)) goto end;

#define	HMAC_FINAL(c, d, l) \
	mac.cd_raw.iov_base = (char *)d; \
	mac.cd_length = mac.cd_raw.iov_len = l; \
	rv = crypto_mac_final(c, &mac, NULL); if (CRYPTO_ERR(rv)) goto end;

int
kssl_compute_record_mac(
	ssl_t *ssl,
	int direction,
	uint64_t seq_num,
	SSL3ContentType ct,
	uchar_t *versionp,
	uchar_t *buf,
	int len,
	uchar_t *digest)
{
	KSSL_HASHCTX mac_ctx;
	KSSL_HASHCTX *ctx = &mac_ctx;
	uchar_t temp[16], *p;
	KSSLCipherSpec *spec;
	int rv = 0;

	spec = &ssl->spec[direction];

	if (spec->mac_hashsz == 0) {
		return (1);
	}

	/* mac_secret = ssl->mac_secret[direction]; */

	p = temp;

	*p++ = (seq_num >> 56) & 0xff;
	*p++ = (seq_num >> 48) & 0xff;
	*p++ = (seq_num >> 40) & 0xff;
	*p++ = (seq_num >> 32) & 0xff;
	*p++ = (seq_num >> 24) & 0xff;
	*p++ = (seq_num >> 16) & 0xff;
	*p++ = (seq_num >> 8) & 0xff;
	*p++ = (seq_num) & 0xff;
	*p++ = (uchar_t)ct;
	if (IS_TLS(ssl)) {
		*p++ = versionp[0];
		*p++ = versionp[1];
	}
	*p++ = (len >> 8) & 0xff;
	*p++ = (len) & 0xff;

	if (IS_TLS(ssl)) {
		crypto_data_t dd, mac;
		crypto_context_t ctx;

		dd.cd_format = CRYPTO_DATA_RAW;
		dd.cd_offset = 0;
		mac.cd_format = CRYPTO_DATA_RAW;
		mac.cd_offset = 0;

		HMAC_INIT(&spec->hmac_mech, &spec->hmac_key, &ctx);
		HMAC_UPDATE(ctx, temp, p - temp);
		HMAC_UPDATE(ctx, buf, len);
		HMAC_FINAL(ctx, digest, spec->mac_hashsz);
end:
		if (CRYPTO_ERR(rv)) {
#ifdef	DEBUG
			cmn_err(CE_WARN,
				"kssl_compute_record_mac - crypto_mac error "
				"0x%0x", rv);
#endif	/* DEBUG */
			KSSL_COUNTER(compute_mac_failure, 1);
		}
	} else {
		bcopy(&(ssl->mac_ctx[direction][0]), ctx,
			sizeof (KSSL_HASHCTX));
		spec->MAC_HashUpdate((void *)ctx, temp, p - temp);
		spec->MAC_HashUpdate((void *)ctx, buf, len);
		spec->MAC_HashFinal(digest, (void *)ctx);

		bcopy(&(ssl->mac_ctx[direction][1]), ctx,
			sizeof (KSSL_HASHCTX));
		spec->MAC_HashUpdate((void *)ctx, digest, spec->mac_hashsz);
		spec->MAC_HashFinal(digest, (void *)ctx);
	}

	return (rv);
}

/*
 * Handles handshake messages.
 * Messages to be replied are returned in handshake_sendbuf.
 */
int
kssl_handle_handshake_message(ssl_t *ssl, mblk_t *mp, int *err,
    kssl_callback_t cbfn, void *arg)
{
	uint32_t msglen;
	uchar_t msghdr[4];

	ASSERT(ssl->msg.state == MSG_BODY);
	ASSERT(ssl->msg.msglen_bytes == 3);
	ASSERT(mp->b_wptr >= mp->b_rptr + ssl->msg.msglen);

	ssl->sslcnt++;
	msglen = ssl->msg.msglen;

	if (ssl->msg.type == client_hello) {
		MD5Init(&ssl->hs_md5);
		SHA1Init(&ssl->hs_sha1);
	}

	if (ssl->msg.type == finished && ssl->resumed == B_FALSE) {
		if (kssl_compute_handshake_hashes(ssl, &ssl->hs_hashes,
		    sender_client) != 0) {
			*err = SSL_MISS;
			return (0);
		}
	}

	if (ssl->msg.type != finished || ssl->resumed == B_FALSE) {
		msghdr[0] = (uchar_t)ssl->msg.type;

		msghdr[1] = (uchar_t)(msglen >> 16);
		msghdr[2] = (uchar_t)(msglen >> 8);
		msghdr[3] = (uchar_t)(msglen);
		kssl_update_handshake_hashes(ssl, msghdr, 4);
		kssl_update_handshake_hashes(ssl, mp->b_rptr, msglen);
	}

	ssl->msg.state = MSG_INIT;
	ssl->msg.msglen = 0;
	ssl->msg.msglen_bytes = 0;

	switch (ssl->msg.type) {
	case client_hello:
		if (ssl->hs_waitstate != wait_client_hello) {
			kssl_send_alert(ssl, alert_fatal,
			    unexpected_message);
			*err = EBADMSG;
			ssl->activeinput = B_FALSE;
			return (1);
		}
		*err = kssl_handle_client_hello(ssl, mp, msglen);
		if (*err == SSL_MISS) {
			ssl->activeinput = B_FALSE;
			return (0);
		}
		return (1);
	case client_key_exchange:
		if (ssl->hs_waitstate != wait_client_key) {
			kssl_send_alert(ssl, alert_fatal,
			    unexpected_message);
			*err = EBADMSG;
			ssl->activeinput = B_FALSE;
			return (1);
		}
		*err = kssl_handle_client_key_exchange(ssl, mp,
		    msglen, cbfn, arg);
		return (1);
	case finished:
		if (ssl->hs_waitstate != wait_finished) {
			kssl_send_alert(ssl, alert_fatal,
				unexpected_message);
			*err = EBADMSG;
			ssl->activeinput = B_FALSE;
			return (1);
		}
		*err = kssl_handle_finished(ssl, mp, msglen);
		return (1);
	default:
		kssl_send_alert(ssl, alert_fatal, unexpected_message);
		ssl->activeinput = B_FALSE;
		*err = EBADMSG;
		return (1);
	}
}

static void
kssl_update_handshake_hashes(ssl_t *ssl, uchar_t *buf, uint_t len)
{
	MD5Update(&ssl->hs_md5, buf, len);
	SHA1Update(&ssl->hs_sha1, buf, len);
}

static int
kssl_compute_handshake_hashes(
	ssl_t *ssl,
	SSL3Hashes *hashes,
	uint32_t sender)
{
	MD5_CTX md5 = ssl->hs_md5;	/* clone md5 context */
	SHA1_CTX sha1 = ssl->hs_sha1;	/* clone sha1 context */
	MD5_CTX *md5ctx = &md5;
	SHA1_CTX *sha1ctx = &sha1;

	if (IS_TLS(ssl)) {
		uchar_t seed[MD5_HASH_LEN + SHA1_HASH_LEN];
		char *label;

		/*
		 * Do not take another hash step here.
		 * Just complete the operation.
		 */
		MD5Final(hashes->md5, md5ctx);
		SHA1Final(hashes->sha1, sha1ctx);

		bcopy(hashes->md5, seed, MD5_HASH_LEN);
		bcopy(hashes->sha1, seed + MD5_HASH_LEN, SHA1_HASH_LEN);

		if (sender == sender_client)
			label = TLS_CLIENT_FINISHED_LABEL;
		else
			label = TLS_SERVER_FINISHED_LABEL;

		return (kssl_tls_PRF(ssl,
			ssl->sid.master_secret, (size_t)SSL3_MASTER_SECRET_LEN,
			(uchar_t *)label, strlen(label),
			seed, (size_t)(MD5_HASH_LEN + SHA1_HASH_LEN),
			hashes->tlshash, (size_t)TLS_FINISHED_SIZE));
	} else {
		uchar_t s[4];
		s[0] = (sender >> 24) & 0xff;
		s[1] = (sender >> 16) & 0xff;
		s[2] = (sender >> 8) & 0xff;
		s[3] = (sender) & 0xff;

		MD5Update(md5ctx, s, 4);
		MD5Update(md5ctx, ssl->sid.master_secret,
		    SSL3_MASTER_SECRET_LEN);
		MD5Update(md5ctx, kssl_pad_1, SSL3_MD5_PAD_LEN);
		MD5Final(hashes->md5, md5ctx);

		MD5Init(md5ctx);
		MD5Update(md5ctx, ssl->sid.master_secret,
		    SSL3_MASTER_SECRET_LEN);
		MD5Update(md5ctx, kssl_pad_2, SSL3_MD5_PAD_LEN);
		MD5Update(md5ctx, hashes->md5, MD5_HASH_LEN);
		MD5Final(hashes->md5, md5ctx);

		SHA1Update(sha1ctx, s, 4);
		SHA1Update(sha1ctx, ssl->sid.master_secret,
		    SSL3_MASTER_SECRET_LEN);
		SHA1Update(sha1ctx, kssl_pad_1, SSL3_SHA1_PAD_LEN);
		SHA1Final(hashes->sha1, sha1ctx);

		SHA1Init(sha1ctx);
		SHA1Update(sha1ctx, ssl->sid.master_secret,
		    SSL3_MASTER_SECRET_LEN);
		SHA1Update(sha1ctx, kssl_pad_2, SSL3_SHA1_PAD_LEN);
		SHA1Update(sha1ctx, hashes->sha1, SHA1_HASH_LEN);
		SHA1Final(hashes->sha1, sha1ctx);
		return (0);
	}
}


#define	KSSL_SSL3_CH_MIN_MSGLEN	(39)

static int
kssl_handle_client_hello(ssl_t *ssl, mblk_t *mp, int msglen)
{
	uchar_t *msgend;
	int err;
	SSL3AlertDescription desc = illegal_parameter;
	uint_t sidlen;
	uint_t nsuites;
	uchar_t *suitesp;
	uint_t i, j;
	uint16_t suite;
	int ch_msglen = KSSL_SSL3_CH_MIN_MSGLEN;

	ASSERT(mp->b_wptr >= mp->b_rptr + msglen);
	ASSERT(ssl->msg.type == client_hello);
	ASSERT(ssl->hs_waitstate == wait_client_hello);
	ASSERT(ssl->resumed == B_FALSE);

	if (msglen < ch_msglen) {
		goto falert;
	}

	msgend = mp->b_rptr + msglen;

	/* Support SSLv3 (version == 3.0) or TLS (version == 3.1) */
	if (ssl->major_version != 3 || (ssl->major_version == 3 &&
		ssl->minor_version != 0 && ssl->minor_version != 1)) {
		KSSL_DEBUG3_IF(kssl_debug,
			"HandleClientHello: handshake failure - "
			"SSL version not supported (%d %d)",
			ssl->major_version, ssl->minor_version);
		desc = handshake_failure;
		goto falert;
	}
	mp->b_rptr += 2; /* skip the version bytes */

	bcopy(mp->b_rptr, ssl->client_random, SSL3_RANDOM_LENGTH);
	mp->b_rptr += SSL3_RANDOM_LENGTH;

	ASSERT(ssl->sid.cached == B_FALSE);
	sidlen = *mp->b_rptr++;
	ch_msglen += sidlen;
	if (msglen < ch_msglen) {
		goto falert;
	}
	if (sidlen != SSL3_SESSIONID_BYTES) {
		mp->b_rptr += sidlen;
	} else {
		kssl_lookup_sid(&ssl->sid, mp->b_rptr, ssl->faddr,
		    ssl->kssl_entry);
		mp->b_rptr += SSL3_SESSIONID_BYTES;
	}

	nsuites = ((uint_t)mp->b_rptr[0] << 8) + (uint_t)mp->b_rptr[1];
	mp->b_rptr += 2;
	ch_msglen += nsuites;
	if (msglen != ch_msglen) {
		goto falert;
	}
	if (nsuites & 0x1) {
		goto falert;
	}
	suitesp = mp->b_rptr;
	if (ssl->sid.cached == B_TRUE) {
		suite = ssl->sid.cipher_suite;
		for (j = 0; j < nsuites; j += 2) {
			if (suitesp[j] == ((suite >> 8) & 0xff) &&
			    suitesp[j + 1] == (suite & 0xff)) {
				break;
			}
		}
		if (j < nsuites) {
			goto suite_found;
		}
		kssl_uncache_sid(&ssl->sid, ssl->kssl_entry);
		ssl->sid.cached = B_FALSE;
	}

	/* Check if this server is capable of the cipher suite */
	for (i = 0; i < ssl->kssl_entry->kssl_cipherSuites_nentries; i++) {
		suite = ssl->kssl_entry->kssl_cipherSuites[i];
		for (j = 0; j < nsuites; j += 2) {
			if (suitesp[j] == ((suite >> 8) & 0xff) &&
			    suitesp[j + 1] == (suite & 0xff)) {
				break;
			}
		}
		if (j < nsuites) {
			break;
		}
	}
	if (i == ssl->kssl_entry->kssl_cipherSuites_nentries) {
		if (ssl->sslcnt == 1) {
			KSSL_COUNTER(no_suite_found, 1);
			return (SSL_MISS);
		}
		desc = handshake_failure;
		KSSL_DEBUG1_IF(kssl_debug,
			"kssl_handle_client_hello: no cipher suites found");
		goto falert;
	}

suite_found:

	mp->b_rptr += nsuites;
	if (*mp->b_rptr++ != 1 || *mp->b_rptr++ != 0) {
		desc = handshake_failure;
		KSSL_DEBUG1_IF(kssl_debug,
			"kssl_handle_client_hello: handshake failure");
		goto falert;
	}

	mp->b_rptr = msgend;

	for (i = 0; i < cipher_suite_defs_nentries; i++) {
		if (suite == cipher_suite_defs[i].suite) {
			break;
		}
	}

	ASSERT(i < cipher_suite_defs_nentries);

	ssl->pending_cipher_suite = suite;
	ssl->pending_malg = cipher_suite_defs[i].malg;
	ssl->pending_calg = cipher_suite_defs[i].calg;
	ssl->pending_keyblksz = cipher_suite_defs[i].keyblksz;

	if (ssl->sid.cached == B_TRUE) {
		err = kssl_send_server_hello(ssl);
		if (err != 0) {
			return (err);
		}
		if (IS_TLS(ssl))
			err = kssl_generate_tls_keyblock(ssl);
		else
			kssl_generate_keyblock(ssl);

		err = kssl_send_change_cipher_specs(ssl);
		if (err != 0) {
			return (err);
		}

		err = kssl_send_finished(ssl, 1);
		if (err != 0)
			return (err);

		err = kssl_compute_handshake_hashes(ssl, &ssl->hs_hashes,
			sender_client);
		if (err != 0)
			return (err);

		ssl->hs_waitstate = wait_change_cipher;
		ssl->resumed = B_TRUE;
		ssl->activeinput = B_FALSE;
		KSSL_COUNTER(resumed_sessions, 1);
		return (0);
	}

	(void) random_get_pseudo_bytes(ssl->sid.session_id,
	    SSL3_SESSIONID_BYTES);
	ssl->sid.client_addr = ssl->faddr;
	ssl->sid.cipher_suite = suite;

	err = kssl_send_server_hello(ssl);
	if (err != 0) {
		return (err);
	}
	err = kssl_send_certificate_and_server_hello_done(ssl);
	if (err != 0) {
		return (err);
	}
	KSSL_COUNTER(full_handshakes, 1);
	ssl->hs_waitstate = wait_client_key;
	ssl->activeinput = B_FALSE;
	return (0);

falert:
	kssl_send_alert(ssl, alert_fatal, desc);
	return (EBADMSG);
}

static void
kssl_cache_sid(sslSessionID *sid, kssl_entry_t *kssl_entry)
{
	uint_t index;
	uchar_t *s = sid->session_id;
	int l = SSL3_SESSIONID_BYTES - 1;
	kmutex_t *lock;

	ASSERT(sid->cached == B_TRUE);

	index = (int)sid->client_addr ^ (((int)s[0] << 24) | ((int)s[1] << 16) |
	    ((int)s[2] << 8) | (int)s[l]);

	index %= kssl_entry->sid_cache_nentries;

	sid->time = lbolt;

	lock = &(kssl_entry->sid_cache[index].se_lock);
	mutex_enter(lock);
	kssl_entry->sid_cache[index].se_used++;
	bcopy(sid, &(kssl_entry->sid_cache[index].se_sid), sizeof (*sid));
	mutex_exit(lock);
}

static void
kssl_lookup_sid(sslSessionID *sid, uchar_t *s, ipaddr_t faddr,
    kssl_entry_t *kssl_entry)
{
	uint_t index;
	int l = SSL3_SESSIONID_BYTES - 1;
	kmutex_t *lock;
	sslSessionID *csid;

	ASSERT(sid->cached == B_FALSE);

	KSSL_COUNTER(sid_cache_lookups, 1);

	index = (int)faddr ^ (((int)s[0] << 24) | ((int)s[1] << 16) |
	    ((int)s[2] << 8) | (int)s[l]);

	index %= kssl_entry->sid_cache_nentries;

	lock = &(kssl_entry->sid_cache[index].se_lock);
	mutex_enter(lock);
	csid = &(kssl_entry->sid_cache[index].se_sid);
	if (csid->cached == B_FALSE || csid->client_addr != faddr ||
	    bcmp(csid->session_id, s, SSL3_SESSIONID_BYTES)) {
		mutex_exit(lock);
		return;
	}

	if (TICK_TO_SEC(lbolt - csid->time) > kssl_entry->sid_cache_timeout) {
		csid->cached = B_FALSE;
		mutex_exit(lock);
		return;
	}

	bcopy(csid, sid, sizeof (*sid));
	mutex_exit(lock);
	ASSERT(sid->cached == B_TRUE);

	KSSL_COUNTER(sid_cache_hits, 1);
}

static uchar_t *
kssl_rsa_unwrap(uchar_t *buf, size_t *lenp)
{
	size_t len = *lenp;
	int i = 2;

	if (buf[0] != 0 || buf[1] != 2) {
		return (NULL);
	}

	while (i < len) {
		if (buf[i++] == 0) {
			*lenp = len - i;
			break;
		}
	}

	if (i == len) {
		return (NULL);
	}

	return (buf + i);
}

void
kssl_uncache_sid(sslSessionID *sid, kssl_entry_t *kssl_entry)
{
	uint_t index;
	uchar_t *s = sid->session_id;
	int l = SSL3_SESSIONID_BYTES - 1;
	sslSessionID *csid;
	kmutex_t *lock;

	ASSERT(sid->cached == B_TRUE);

	KSSL_COUNTER(sid_uncached, 1);

	index = (int)sid->client_addr ^ (((int)s[0] << 24) | ((int)s[1] << 16) |
	    ((int)s[2] << 8) | (int)s[l]);

	index %= kssl_entry->sid_cache_nentries;

	lock = &(kssl_entry->sid_cache[index].se_lock);
	mutex_enter(lock);
	csid = &(kssl_entry->sid_cache[index].se_sid);
	if (csid->client_addr != sid->client_addr ||
	    bcmp(csid->session_id, s, SSL3_SESSIONID_BYTES)) {
		mutex_exit(lock);
		return;
	}
	csid->cached = B_FALSE;
	mutex_exit(lock);
}


#define	KSSL_SSL3_SH_RECLEN	(74)
#define	KSSL_SSL3_FIN_MSGLEN	(36)

#define	KSSL_SSL3_MAX_CCP_FIN_MSGLEN	(128)	/* comfortable upper bound */

static int
kssl_send_server_hello(ssl_t *ssl)
{
	mblk_t *mp;
	uchar_t *buf;
	uchar_t *msgstart;

	mp = allocb(ssl->tcp_mss, BPRI_HI);
	if (mp == NULL) {
		KSSL_COUNTER(alloc_fails, 1);
		return (ENOMEM);
	}
	ssl->handshake_sendbuf = mp;
	buf = mp->b_wptr;

	/* 5 byte record header */
	buf[0] = content_handshake;
	buf[1] = ssl->major_version;
	buf[2] = ssl->minor_version;
	buf[3] = KSSL_SSL3_SH_RECLEN >> 8;
	buf[4] = KSSL_SSL3_SH_RECLEN & 0xff;
	buf += SSL3_HDR_LEN;

	msgstart = buf;

	/* 6 byte message header */
	buf[0] = (uchar_t)server_hello;			/* message type */
	buf[1] = 0;					/* message len byte 0 */
	buf[2] = ((KSSL_SSL3_SH_RECLEN - 4) >> 8) &
	    0xff;					/* message len byte 1 */
	buf[3] = (KSSL_SSL3_SH_RECLEN - 4) & 0xff;	/* message len byte 2 */

	buf[4] = ssl->major_version;	/* version byte 0 */
	buf[5] = ssl->minor_version;	/* version byte 1 */

	buf += 6;

	kssl_get_hello_random(ssl->server_random);
	bcopy(ssl->server_random, buf, SSL3_RANDOM_LENGTH);
	buf += SSL3_RANDOM_LENGTH;

	buf[0] = SSL3_SESSIONID_BYTES;
	bcopy(ssl->sid.session_id, buf + 1, SSL3_SESSIONID_BYTES);
	buf += SSL3_SESSIONID_BYTES + 1;

	buf[0] = (ssl->pending_cipher_suite >> 8) & 0xff;
	buf[1] = ssl->pending_cipher_suite & 0xff;

	buf[2] = 0;	/* No compression */

	mp->b_wptr = buf + 3;
	ASSERT(mp->b_wptr < mp->b_datap->db_lim);

	kssl_update_handshake_hashes(ssl, msgstart, KSSL_SSL3_SH_RECLEN);
	return (0);
}

static void
kssl_get_hello_random(uchar_t *buf)
{
	timestruc_t ts;
	time_t sec;

	gethrestime(&ts);
	sec = ts.tv_sec;

	buf[0] = (sec >> 24) & 0xff;
	buf[1] = (sec >> 16) & 0xff;
	buf[2] = (sec >> 8) & 0xff;
	buf[3] = (sec) & 0xff;

	(void) random_get_pseudo_bytes(&buf[4], SSL3_RANDOM_LENGTH - 4);

	/* Should this be caching? */
}

static int
kssl_tls_P_hash(crypto_mechanism_t *mech, crypto_key_t *key,
	size_t hashlen,
	uchar_t *label, size_t label_len,
	uchar_t *seed, size_t seedlen,
	uchar_t *data, size_t datalen)
{
	int rv = 0;
	uchar_t A1[MAX_HASH_LEN], result[MAX_HASH_LEN];
	int bytes_left = datalen;
	crypto_data_t dd, mac;
	crypto_context_t ctx;

	dd.cd_format = CRYPTO_DATA_RAW;
	dd.cd_offset = 0;
	mac.cd_format = CRYPTO_DATA_RAW;
	mac.cd_offset = 0;

	/*
	 * A(i) = HMAC_hash(secred, seed + A(i-1));
	 * A(0) = seed;
	 *
	 * Compute A(1):
	 * A(1) = HMAC_hash(secret, label + seed)
	 *
	 */
	HMAC_INIT(mech, key, &ctx);
	HMAC_UPDATE(ctx, label, label_len);
	HMAC_UPDATE(ctx, seed, seedlen);
	HMAC_FINAL(ctx, A1, hashlen);

	/* Compute A(2) ... A(n) */
	while (bytes_left > 0) {
		HMAC_INIT(mech, key, &ctx);
		HMAC_UPDATE(ctx, A1, hashlen);
		HMAC_UPDATE(ctx, label, label_len);
		HMAC_UPDATE(ctx, seed, seedlen);
		HMAC_FINAL(ctx, result, hashlen);

		/*
		 * The A(i) value is stored in "result".
		 * Save the results of the MAC so it can be input to next
		 * iteration.
		 */
		if (bytes_left > hashlen) {
			/* Store the chunk result */
			bcopy(result, data, hashlen);
			data += hashlen;

			bytes_left -= hashlen;

			/* Update A1 for next iteration */
			HMAC_INIT(mech, key, &ctx);
			HMAC_UPDATE(ctx, A1, hashlen);
			HMAC_FINAL(ctx, A1, hashlen);

		} else {
			bcopy(result, data, bytes_left);
			data += bytes_left;
			bytes_left = 0;
		}
	}
end:
	if (CRYPTO_ERR(rv)) {
#ifdef	DEBUG
		cmn_err(CE_WARN, "kssl_P_hash: crypto_mac error 0x%02X", rv);
#endif	/* DEBUG */
		KSSL_COUNTER(compute_mac_failure, 1);
	}
	return (rv);
}

/* ARGSUSED */
static int
kssl_tls_PRF(ssl_t *ssl,
	uchar_t *secret, size_t secret_len,
	uchar_t *label, size_t label_len,
	uchar_t *seed, size_t seed_len,
	uchar_t *prfresult, size_t prfresult_len)
{
	/*
	 * RFC 2246:
	 *  PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR
	 *				P_SHA1(S2, label + seed);
	 * S1 = 1st half of secret.
	 * S1 = 2nd half of secret.
	 *
	 */

	int rv = 0, i;
	uchar_t psha1[MAX_TLS_KEYBLOCK_SIZE];
	crypto_key_t S1, S2;

	/* length of secret keys is ceil(length/2) */
	size_t slen = roundup(secret_len, 2) / 2;

	if (prfresult_len > MAX_TLS_KEYBLOCK_SIZE) {
		KSSL_DEBUG2_IF(kssl_debug, "kssl_tls_PRF: unexpected keyblock "
			"size (%lu)", prfresult_len);
		return (CRYPTO_ARGUMENTS_BAD);
	}

	ASSERT(prfresult != NULL);
	ASSERT(label != NULL);
	ASSERT(seed != NULL);

	S1.ck_data   = secret;
	S1.ck_length = slen * 8; /* bits */
	S1.ck_format = CRYPTO_KEY_RAW;

	S2.ck_data   = secret + slen;
	S2.ck_length = slen * 8; /* bits */
	S2.ck_format = CRYPTO_KEY_RAW;

	rv = kssl_tls_P_hash(&hmac_md5_mech, &S1, MD5_HASH_LEN,
			label, label_len,
			seed, seed_len,
			prfresult, prfresult_len);
	if (CRYPTO_ERR(rv))
		goto end;

	rv = kssl_tls_P_hash(&hmac_sha1_mech, &S2, SHA1_HASH_LEN,
			label, label_len,
			seed, seed_len,
			psha1, prfresult_len);
	if (CRYPTO_ERR(rv))
		goto end;

	for (i = 0; i < prfresult_len; i++)
		prfresult[i] ^= psha1[i];

end:
	if (CRYPTO_ERR(rv))
		bzero(prfresult, prfresult_len);

	return (rv);
}

static int
kssl_generate_tls_ms(ssl_t *ssl, uchar_t *pms, size_t pmslen)
{
	uchar_t seed[SSL3_RANDOM_LENGTH * 2];

	/*
	 * Computing the master secret:
	 * ----------------------------
	 * master_secret = PRF (pms, "master secret",
	 *		ClientHello.random + ServerHello.random);
	 */
	bcopy(ssl->client_random, seed, SSL3_RANDOM_LENGTH);
	bcopy(ssl->server_random, seed + SSL3_RANDOM_LENGTH,
		SSL3_RANDOM_LENGTH);

	return (kssl_tls_PRF(ssl,
		pms, pmslen,
		(uchar_t *)TLS_MASTER_SECRET_LABEL,
		(size_t)strlen(TLS_MASTER_SECRET_LABEL),
		seed, sizeof (seed),
		ssl->sid.master_secret,
		(size_t)sizeof (ssl->sid.master_secret)));
}

static void
kssl_generate_ssl_ms(ssl_t *ssl, uchar_t *pms, size_t pmslen)
{
	uchar_t buf[SSL3_PRE_MASTER_SECRET_LEN];
	uchar_t *ms;
	int hlen = MD5_HASH_LEN;

	ms = ssl->sid.master_secret;

	/* if pms is bad fake it to thwart Bleichenbacher attack */
	if (pms == NULL || pmslen != SSL3_PRE_MASTER_SECRET_LEN ||
	    pms[0] != ssl->major_version || pms[1] != ssl->minor_version) {
#ifdef	DEBUG
		cmn_err(CE_WARN, "Under Bleichenbacher attack");
#endif	/* DEBUG */
		KSSL_COUNTER(bad_pre_master_secret, 1);
		pms = buf;
		pmslen = SSL3_PRE_MASTER_SECRET_LEN;
		pms[0] = ssl->major_version;
		pms[1] = ssl->minor_version;
		(void) random_get_pseudo_bytes(&buf[2], pmslen - 2);
	}
	kssl_ssl3_key_material_derive_step(ssl, pms, pmslen, 1, ms, 0);
	kssl_ssl3_key_material_derive_step(ssl, pms, pmslen, 2, ms + hlen, 0);
	kssl_ssl3_key_material_derive_step(ssl, pms, pmslen, 3, ms + 2 * hlen,
	    0);
}

static int
kssl_generate_tls_keyblock(ssl_t *ssl)
{
	uchar_t seed[2 * SSL3_RANDOM_LENGTH];

	bcopy(ssl->server_random, seed, SSL3_RANDOM_LENGTH);
	bcopy(ssl->client_random, seed + SSL3_RANDOM_LENGTH,
		SSL3_RANDOM_LENGTH);

	return (kssl_tls_PRF(ssl, ssl->sid.master_secret,
		(size_t)SSL3_MASTER_SECRET_LEN,
		(uchar_t *)TLS_KEY_EXPANSION_LABEL,
		(size_t)strlen(TLS_KEY_EXPANSION_LABEL),
		seed, (size_t)sizeof (seed),
		ssl->pending_keyblock,
		(size_t)ssl->pending_keyblksz));

}

static void
kssl_generate_keyblock(ssl_t *ssl)
{
	uchar_t *ms;
	size_t mslen = SSL3_MASTER_SECRET_LEN;
	int hlen = MD5_HASH_LEN;
	uchar_t *keys = ssl->pending_keyblock;
	int steps = howmany(ssl->pending_keyblksz, hlen);
	int i;

	ms = ssl->sid.master_secret;

	ASSERT(hlen * steps <= MAX_KEYBLOCK_LENGTH);

	for (i = 1; i <= steps; i++) {
		kssl_ssl3_key_material_derive_step(ssl, ms, mslen, i, keys, 1);
		keys += hlen;
	}
}

static char *ssl3_key_derive_seeds[8] = {"A", "BB", "CCC", "DDDD", "EEEEE",
					    "FFFFFF", "GGGGGGG", "HHHHHHHH"};

static void
kssl_ssl3_key_material_derive_step(
	ssl_t *ssl,
	uchar_t *secret,
	size_t secretlen,
	int step,
	uchar_t *dst,
	int sr_first)
{
	SHA1_CTX sha1, *sha1ctx;
	MD5_CTX md5, *md5ctx;
	uchar_t sha1_hash[SHA1_HASH_LEN];

	sha1ctx = &sha1;
	md5ctx = &md5;

	ASSERT(step <=
	    sizeof (ssl3_key_derive_seeds) /
	    sizeof (ssl3_key_derive_seeds[0]));
	step--;

	SHA1Init(sha1ctx);
	SHA1Update(sha1ctx, (uchar_t *)ssl3_key_derive_seeds[step],
	    step + 1);
	SHA1Update(sha1ctx, secret, secretlen);
	if (sr_first) {
		SHA1Update(sha1ctx, ssl->server_random, SSL3_RANDOM_LENGTH);
		SHA1Update(sha1ctx, ssl->client_random, SSL3_RANDOM_LENGTH);
	} else {
		SHA1Update(sha1ctx, ssl->client_random, SSL3_RANDOM_LENGTH);
		SHA1Update(sha1ctx, ssl->server_random, SSL3_RANDOM_LENGTH);
	}
	SHA1Final(sha1_hash, sha1ctx);

	MD5Init(md5ctx);
	MD5Update(md5ctx, secret, secretlen);
	MD5Update(md5ctx, sha1_hash, SHA1_HASH_LEN);
	MD5Final(dst, md5ctx);
}

static int
kssl_send_certificate_and_server_hello_done(ssl_t *ssl)
{
	int cur_reclen;
	int mss;
	int len, copylen;
	mblk_t *mp;
	uchar_t *cert_buf;
	int cert_len;
	uchar_t *msgbuf;
	Certificate_t *cert;

	cert = ssl->kssl_entry->ke_server_certificate;
	if (cert == NULL) {
	    return (ENOENT);
	}
	cert_buf = cert->msg;
	cert_len = cert->len;

	mp = ssl->handshake_sendbuf;
	mss = ssl->tcp_mss;
	ASSERT(mp != NULL);
	cur_reclen = mp->b_wptr - mp->b_rptr - SSL3_HDR_LEN;
	ASSERT(cur_reclen == KSSL_SSL3_SH_RECLEN);
	/* Assume MSS is at least 80 bytes */
	ASSERT(mss > cur_reclen + SSL3_HDR_LEN);
	ASSERT(cur_reclen < SSL3_MAX_RECORD_LENGTH); /* XXX */

	copylen = mss - (cur_reclen + SSL3_HDR_LEN);
	len = cert_len;
	copylen = MIN(copylen, len);
	copylen = MIN(copylen, SSL3_MAX_RECORD_LENGTH - cur_reclen);

	/* new record always starts in a new mblk for simplicity */
	msgbuf = cert_buf;
	for (;;) {
		ASSERT(mp->b_wptr + copylen <= mp->b_datap->db_lim);
		bcopy(msgbuf, mp->b_wptr, copylen);
		msgbuf += copylen;
		mp->b_wptr += copylen;
		cur_reclen += copylen;
		len -= copylen;
		if (len == 0) {
			break;
		}
		if (cur_reclen == SSL3_MAX_RECORD_LENGTH) {
			cur_reclen = 0;
		}
		copylen = MIN(len, mss);
		copylen = MIN(copylen, SSL3_MAX_RECORD_LENGTH - cur_reclen);
		mp->b_cont = allocb(copylen, BPRI_HI);
		if (mp->b_cont == NULL) {
			KSSL_COUNTER(alloc_fails, 1);
			freemsg(ssl->handshake_sendbuf);
			ssl->handshake_sendbuf = NULL;
			return (ENOMEM);
		}
		mp = mp->b_cont;
		if (cur_reclen == 0) {
			mp->b_wptr[0] = content_handshake;
			mp->b_wptr[1] = ssl->major_version;
			mp->b_wptr[2] = ssl->minor_version;
			cur_reclen = MIN(len, SSL3_MAX_RECORD_LENGTH);
			mp->b_wptr[3] = (cur_reclen >> 8) & 0xff;
			mp->b_wptr[4] = (cur_reclen) & 0xff;
			mp->b_wptr += SSL3_HDR_LEN;
			cur_reclen = 0;
			copylen = MIN(copylen, mss - SSL3_HDR_LEN);
		}
	}

	/* adjust the record length field for the first record */
	mp = ssl->handshake_sendbuf;
	cur_reclen = MIN(KSSL_SSL3_SH_RECLEN + cert_len,
	    SSL3_MAX_RECORD_LENGTH);
	mp->b_rptr[3] = (cur_reclen >> 8) & 0xff;
	mp->b_rptr[4] = (cur_reclen) & 0xff;

	kssl_update_handshake_hashes(ssl, cert_buf, cert_len);

	return (0);
}

static int
kssl_send_change_cipher_specs(ssl_t *ssl)
{
	mblk_t *mp, *newmp;
	uchar_t *buf;

	mp = ssl->handshake_sendbuf;

	/* We're most likely to hit the fast path for resumed sessions */
	if ((mp != NULL) &&
	    (mp->b_datap->db_lim - mp->b_wptr > KSSL_SSL3_MAX_CCP_FIN_MSGLEN)) {
		buf = mp->b_wptr;
	} else {
		newmp = allocb(KSSL_SSL3_MAX_CCP_FIN_MSGLEN, BPRI_HI);

		if (newmp == NULL)
			return (ENOMEM);	/* need to do better job! */

		if (mp == NULL) {
			ssl->handshake_sendbuf = newmp;
		} else {
			linkb(ssl->handshake_sendbuf, newmp);
		}
		mp = newmp;
		buf = mp->b_rptr;
	}

	/* 5 byte record header */
	buf[0] = content_change_cipher_spec;
	buf[1] = ssl->major_version;
	buf[2] = ssl->minor_version;
	buf[3] = 0;
	buf[4] = 1;
	buf += SSL3_HDR_LEN;

	buf[0] = 1;

	mp->b_wptr = buf + 1;
	ASSERT(mp->b_wptr < mp->b_datap->db_lim);

	ssl->seq_num[KSSL_WRITE] = 0;
	return (kssl_spec_init(ssl, KSSL_WRITE));
}

int
kssl_spec_init(ssl_t *ssl, int dir)
{
	KSSL_HASHCTX *ctx;
	KSSLCipherSpec *spec = &ssl->spec[dir];
	int ret = 0;

	spec->mac_hashsz = mac_defs[ssl->pending_malg].hashsz;
	spec->mac_padsz = mac_defs[ssl->pending_malg].padsz;

	spec->MAC_HashInit = mac_defs[ssl->pending_malg].HashInit;
	spec->MAC_HashUpdate = mac_defs[ssl->pending_malg].HashUpdate;
	spec->MAC_HashFinal = mac_defs[ssl->pending_malg].HashFinal;

	if (dir == KSSL_READ) {
		bcopy(ssl->pending_keyblock, ssl->mac_secret[dir],
		    spec->mac_hashsz);
	} else {
		bcopy(&(ssl->pending_keyblock[spec->mac_hashsz]),
		    ssl->mac_secret[dir], spec->mac_hashsz);
	}

	/* Pre-compute these here. will save cycles on each record later */
	if (!IS_TLS(ssl)) {
		ctx = &ssl->mac_ctx[dir][0];
		spec->MAC_HashInit((void *)ctx);
		spec->MAC_HashUpdate((void *)ctx, ssl->mac_secret[dir],
		    spec->mac_hashsz);
		spec->MAC_HashUpdate((void *)ctx, kssl_pad_1,
			spec->mac_padsz);

		ctx = &ssl->mac_ctx[dir][1];
		spec->MAC_HashInit((void *)ctx);
		spec->MAC_HashUpdate((void *)ctx, ssl->mac_secret[dir],
		    spec->mac_hashsz);
		spec->MAC_HashUpdate((void *)ctx, kssl_pad_2,
			spec->mac_padsz);
	}

	spec->cipher_type = cipher_defs[ssl->pending_calg].type;
	spec->cipher_mech.cm_type = cipher_defs[ssl->pending_calg].mech_type;
	spec->cipher_bsize = cipher_defs[ssl->pending_calg].bsize;
	spec->cipher_keysz = cipher_defs[ssl->pending_calg].keysz;

	if (spec->cipher_ctx != NULL) {
		crypto_cancel_ctx(spec->cipher_ctx);
		spec->cipher_ctx = 0;
	}

	/*
	 * Initialize HMAC keys for TLS.
	 */
	if (IS_TLS(ssl)) {
		if (ssl->pending_malg == mac_md5) {
			spec->hmac_mech = hmac_md5_mech;
		} else if (ssl->pending_malg == mac_sha) {
			spec->hmac_mech = hmac_sha1_mech;
		}

		spec->hmac_key.ck_format = CRYPTO_KEY_RAW;
		spec->hmac_key.ck_data = ssl->mac_secret[dir];
		spec->hmac_key.ck_length = spec->mac_hashsz * 8;
	}

	/* We're done if this is the nil cipher */
	if (spec->cipher_keysz == 0) {
		return (0);
	}

	/* Initialize the key and the active context */
	spec->cipher_key.ck_format = CRYPTO_KEY_RAW;
	spec->cipher_key.ck_length = 8 * spec->cipher_keysz; /* in bits */

	if (cipher_defs[ssl->pending_calg].bsize > 0) {
		/* client_write_IV */
		spec->cipher_mech.cm_param =
		    (caddr_t)&(ssl->pending_keyblock[2 * spec->mac_hashsz +
		    2 * spec->cipher_keysz]);
		spec->cipher_mech.cm_param_len = spec->cipher_bsize;
	}
	spec->cipher_data.cd_format = CRYPTO_DATA_RAW;
	if (dir == KSSL_READ) {
		spec->cipher_mech.cm_param_len =
		    cipher_defs[ssl->pending_calg].bsize;

		/* cilent_write_key */
		spec->cipher_key.ck_data =
		    &(ssl->pending_keyblock[2 * spec->mac_hashsz]);

		ret = crypto_decrypt_init(&(spec->cipher_mech),
			&(spec->cipher_key), NULL, &spec->cipher_ctx, NULL);
#ifdef	DEBUG
		if (CRYPTO_ERR(ret)) {
			cmn_err(CE_WARN, "kssl_spec_init: "
				"crypto_decrypt_init error 0x%02X", ret);
		}
#endif	/* DEBUG */
	} else {
		if (cipher_defs[ssl->pending_calg].bsize > 0) {
			spec->cipher_mech.cm_param += spec->cipher_bsize;
		}
		/* server_write_key */
		spec->cipher_key.ck_data =
		    &(ssl->pending_keyblock[2 * spec->mac_hashsz +
		    spec->cipher_keysz]);

		ret = crypto_encrypt_init(&(spec->cipher_mech),
			&(spec->cipher_key), NULL, &spec->cipher_ctx, NULL);
#ifdef	DEBUG
		if (CRYPTO_ERR(ret))
			cmn_err(CE_WARN, "kssl_spec_init: "
				"crypto_encrypt_init error 0x%02X", ret);
#endif	/* DEBUG */
	}
	return (ret);
}

static int
kssl_send_finished(ssl_t *ssl, int update_hsh)
{
	mblk_t *mp;
	uchar_t *buf;
	uchar_t *rstart;
	uchar_t *versionp;
	SSL3Hashes ssl3hashes;
	size_t finish_len;
	int ret = 0;

	mp = ssl->handshake_sendbuf;
	ASSERT(mp != NULL);
	buf = mp->b_wptr;
	ASSERT(buf - mp->b_rptr == SSL3_HDR_LEN + KSSL_SSL3_SH_RECLEN +
	    SSL3_HDR_LEN + 1 || buf - mp->b_rptr == SSL3_HDR_LEN + 1);

	rstart = buf;

	if (IS_TLS(ssl))
		finish_len = TLS_FINISHED_SIZE;
	else
		finish_len = KSSL_SSL3_FIN_MSGLEN;

	/* 5 byte record header */
	buf[0] = content_handshake;
	buf[1] = ssl->major_version;
	buf[2] = ssl->minor_version;
	buf[3] = 0;
	buf[4] = 4 + finish_len;

	versionp = &buf[1];

	buf += SSL3_HDR_LEN;

	/* 4 byte message header */
	buf[0] = (uchar_t)finished;	/* message type */
	buf[1] = 0;			/* message len byte 0 */
	buf[2] = 0;			/* message len byte 1 */
	buf[3] = finish_len;	/* message len byte 2 */
	buf += 4;

	if (IS_TLS(ssl)) {
		bcopy(ssl->hs_hashes.md5, ssl3hashes.md5,
			sizeof (ssl3hashes.md5));
		bcopy(ssl->hs_hashes.sha1, ssl3hashes.sha1,
			sizeof (ssl3hashes.sha1));
	}

	/* Compute hashes for the SENDER side */
	ret = kssl_compute_handshake_hashes(ssl, &ssl3hashes, sender_server);
	if (ret != 0)
		return (ret);

	if (IS_TLS(ssl)) {
		bcopy(ssl3hashes.tlshash, buf, sizeof (ssl3hashes.tlshash));
	} else {
		bcopy(ssl3hashes.md5, buf, MD5_HASH_LEN);
		bcopy(ssl3hashes.sha1, buf + MD5_HASH_LEN, SHA1_HASH_LEN);
	}

	if (update_hsh) {
		kssl_update_handshake_hashes(ssl, buf - 4, finish_len + 4);
	}

	mp->b_wptr = buf + finish_len;

	ret = kssl_mac_encrypt_record(ssl, content_handshake, versionp,
	    rstart, mp);
	ASSERT(mp->b_wptr <= mp->b_datap->db_lim);

	return (ret);
}

int
kssl_mac_encrypt_record(ssl_t *ssl,
	SSL3ContentType ct,
	uchar_t *versionp,
	uchar_t *rstart,
	mblk_t *mp)
{
	KSSLCipherSpec *spec;
	int mac_sz;
	int ret = 0;
	uint16_t rec_sz;
	int pad_sz;
	int i;

	ASSERT(ssl != NULL);
	ASSERT(rstart >= mp->b_rptr);
	ASSERT(rstart < mp->b_wptr);

	spec = &ssl->spec[KSSL_WRITE];
	mac_sz = spec->mac_hashsz;

	rec_sz = (mp->b_wptr - rstart) - SSL3_HDR_LEN;
	ASSERT(rec_sz > 0);

	if (mac_sz != 0) {
		ASSERT(mp->b_wptr + mac_sz <= mp->b_datap->db_lim);
		ret = kssl_compute_record_mac(ssl, KSSL_WRITE,
		    ssl->seq_num[KSSL_WRITE], ct, versionp,
		    rstart + SSL3_HDR_LEN, rec_sz, mp->b_wptr);
		if (ret == CRYPTO_SUCCESS) {
			ssl->seq_num[KSSL_WRITE]++;
			mp->b_wptr += mac_sz;
			rec_sz += mac_sz;
		} else {
			return (ret);
		}
	}

	if (spec->cipher_type == type_block) {
		pad_sz = spec->cipher_bsize -
		    (rec_sz & (spec->cipher_bsize - 1));
		ASSERT(mp->b_wptr + pad_sz <= mp->b_datap->db_lim);
		for (i = 0; i < pad_sz; i++) {
			mp->b_wptr[i] = pad_sz - 1;
		}
		mp->b_wptr += pad_sz;
		rec_sz += pad_sz;
	}

	ASSERT(rec_sz <= SSL3_MAX_RECORD_LENGTH);

	U16_TO_BE16(rec_sz, rstart + 3);

	if (spec->cipher_ctx == 0)
		return (ret);

	spec->cipher_data.cd_length = rec_sz;
	spec->cipher_data.cd_raw.iov_base = (char *)(rstart + SSL3_HDR_LEN);
	spec->cipher_data.cd_raw.iov_len = rec_sz;
	/* One record at a time. Otherwise, gotta allocate the crypt_data_t */
	ret = crypto_encrypt_update(spec->cipher_ctx, &spec->cipher_data,
	    NULL, NULL);
#ifdef	DEBUG
	if (CRYPTO_ERR(ret)) {
		cmn_err(CE_WARN,
			"kssl_mac_encrypt_record: crypto_encrypt_update "
			"error 0x%02X", ret);
	}
#endif	/* DEBUG */
	return (ret);
}

void
kssl_send_alert(ssl_t *ssl, SSL3AlertLevel level, SSL3AlertDescription desc)
{
	mblk_t *mp;
	uchar_t *buf;
	KSSLCipherSpec *spec;

	ASSERT(ssl != NULL);

	ssl->sendalert_level = level;
	ssl->sendalert_desc = desc;

	if (level == alert_fatal) {
#ifdef	DEBUG
		cmn_err(CE_WARN, "sending an alert %d %d from %p\n", level,
		    desc, (void *)caller());
#endif	/* DEBUG */
		if (ssl->sid.cached == B_TRUE) {
			kssl_uncache_sid(&ssl->sid, ssl->kssl_entry);
			ssl->sid.cached = B_FALSE;
		}
		ssl->fatal_alert = B_TRUE;
		KSSL_COUNTER(fatal_alerts, 1);
	} else
		KSSL_COUNTER(warning_alerts, 1);

	spec = &ssl->spec[KSSL_WRITE];

	ASSERT(ssl->alert_sendbuf == NULL);
	ssl->alert_sendbuf = mp = allocb(7 + spec->mac_hashsz +
	    spec->cipher_bsize, BPRI_HI);
	if (mp == NULL) {
		KSSL_COUNTER(alloc_fails, 1);
		return;
	}
	buf = mp->b_wptr;

	/* 5 byte record header */
	buf[0] = content_alert;
	buf[1] = ssl->major_version;
	buf[2] = ssl->minor_version;
	buf[3] = 0;
	buf[4] = 2;
	buf += SSL3_HDR_LEN;

	/* alert contents */
	buf[0] = (uchar_t)level;
	buf[1] = (uchar_t)desc;

	mp->b_wptr = buf + 2;
}

/* Assumes RSA encryption */
static int
kssl_handle_client_key_exchange(ssl_t *ssl, mblk_t *mp, int msglen,
    kssl_callback_t cbfn, void *arg)
{
	char *buf;
	uchar_t *pms;
	size_t pmslen;
	int allocated;
	int err;
	crypto_key_t *privkey;
	crypto_data_t *wrapped_pms_data, *pms_data;
	crypto_call_req_t creq, *creqp;

	privkey = ssl->kssl_entry->ke_private_key;
	if (privkey == NULL) {
	    return (ENOENT);
	}

	ASSERT(ssl->msg.type == client_key_exchange);
	ASSERT(ssl->hs_waitstate == wait_client_key);

	/*
	 * TLS adds an extra 2 byte length field before the data.
	 */
	if (IS_TLS(ssl)) {
		msglen = (mp->b_rptr[0] << 8) | mp->b_rptr[1];
		mp->b_rptr += 2;
	}

	/*
	 * Allocate all we need in one shot. about 300 bytes total, for
	 * 1024 bit RSA modulus.
	 * The buffer layout will be: pms_data, wrapped_pms_data, the
	 * value of the wrapped pms from the client, then room for the
	 * resulting decrypted premaster secret.
	 */
	allocated = 2 * (sizeof (crypto_data_t) + msglen);
	buf = kmem_alloc(allocated, KM_NOSLEEP);
	if (buf == NULL) {
		return (ENOMEM);
	}

	pms_data = (crypto_data_t *)buf;
	wrapped_pms_data = &(((crypto_data_t *)buf)[1]);

	wrapped_pms_data->cd_format = pms_data->cd_format = CRYPTO_DATA_RAW;
	wrapped_pms_data->cd_offset = pms_data->cd_offset = 0;
	wrapped_pms_data->cd_length = pms_data->cd_length = msglen;
	wrapped_pms_data->cd_miscdata = pms_data->cd_miscdata = NULL;
	wrapped_pms_data->cd_raw.iov_len = pms_data->cd_raw.iov_len = msglen;
	wrapped_pms_data->cd_raw.iov_base = buf + 2 * sizeof (crypto_data_t);
	pms_data->cd_raw.iov_base = wrapped_pms_data->cd_raw.iov_base + msglen;

	bcopy(mp->b_rptr, wrapped_pms_data->cd_raw.iov_base, msglen);
	mp->b_rptr += msglen;

	/* Proceed synchronously if out of interrupt and configured to do so */
	if ((kssl_synchronous) && (!servicing_interrupt())) {
		creqp = NULL;
	} else {
		ssl->cke_callback_func = cbfn;
		ssl->cke_callback_arg = arg;
		creq.cr_flag = kssl_call_flag;
		creq.cr_callback_func = kssl_cke_done;
		creq.cr_callback_arg = ssl;

		/* The callback routine will release this one */
		KSSL_SSL_REFHOLD(ssl);

		creqp = &creq;
	}

	err = crypto_decrypt(&rsa_x509_mech, wrapped_pms_data,
		privkey, NULL, pms_data, creqp);

	switch (err) {
	case CRYPTO_SUCCESS:
		break;

	case CRYPTO_QUEUED:
		/*
		 * Finish the master secret then the rest of key material
		 * derivation later.
		 */
		ssl->job.kjob = creq.cr_reqid;
		ssl->job.buf = buf;
		ssl->job.buflen = allocated;
		ssl->hs_waitstate = wait_client_key_done;
		return (0);
	default:
#ifdef	DEBUG
		cmn_err(CE_WARN, "kssl_handle_client_key_exchange: "
			"crypto_decrypt error 0x%02X", err);
#endif	/* DEBUG */
		kmem_free(buf, allocated);
		return (ENOMEM);
	}

	pmslen = pms_data->cd_length;
	pms = kssl_rsa_unwrap((uchar_t *)pms_data->cd_raw.iov_base, &pmslen);

	/* generate master key and save it in the ssl sid structure */
	if (IS_TLS(ssl)) {
		err = kssl_generate_tls_ms(ssl, pms, pmslen);
		if (!CRYPTO_ERR(err))
			err = kssl_generate_tls_keyblock(ssl);
	} else {
		kssl_generate_ssl_ms(ssl, pms, pmslen);
		kssl_generate_keyblock(ssl);
	}

	if (err == CRYPTO_SUCCESS)
		ssl->hs_waitstate = wait_change_cipher;

	ssl->activeinput = B_FALSE;

	kmem_free(buf, allocated);

	return (0);
}

static int
kssl_handle_finished(ssl_t *ssl, mblk_t *mp, int msglen)
{
	int err;
	size_t finish_len;
	int hashcompare;

	ASSERT(ssl->msg.type == finished);
	ASSERT(ssl->hs_waitstate == wait_finished);

	if (IS_TLS(ssl))
		finish_len = TLS_FINISHED_SIZE;
	else
		finish_len = KSSL_SSL3_FIN_MSGLEN;

	if (msglen != finish_len) {
		kssl_send_alert(ssl, alert_fatal, illegal_parameter);
		return (EBADMSG);
	}

	if (IS_TLS(ssl)) {
		hashcompare = bcmp(mp->b_rptr, ssl->hs_hashes.tlshash,
			finish_len);
	} else {
		hashcompare = bcmp(mp->b_rptr, &ssl->hs_hashes, finish_len);
	}

	/* The handshake hashes should be computed by now */
	if (hashcompare != 0) {
		kssl_send_alert(ssl, alert_fatal, handshake_failure);
		return (EBADMSG);
	}

	mp->b_rptr += msglen;

	ssl->hs_waitstate = idle_handshake;

	if (ssl->resumed == B_TRUE) {
		ssl->activeinput = B_FALSE;
		return (0);
	}

	err = kssl_send_change_cipher_specs(ssl);
	if (err != 0) {
		return (err);
	}
	err = kssl_send_finished(ssl, 0);
	if (err != 0) {
		return (err);
	}

	ASSERT(ssl->sid.cached == B_FALSE);
	ssl->sid.cached = B_TRUE;
	kssl_cache_sid(&ssl->sid, ssl->kssl_entry);
	ssl->activeinput = B_FALSE;

	return (0);
}

#define	KSSL2_CH_MIN_RECSZ	(9)

/*
 * This method is needed to handle clients which send the
 * SSLv2/SSLv3 handshake for backwards compat with SSLv2 servers.
 * We are not really doing SSLv2 here, just handling the header
 * and then switching to SSLv3.
 */
int
kssl_handle_v2client_hello(ssl_t *ssl, mblk_t *mp, int recsz)
{
	uchar_t *recend;
	int err;
	SSL3AlertDescription desc = illegal_parameter;
	uint_t randlen;
	uint_t sidlen;
	uint_t nsuites;
	uchar_t *suitesp;
	uchar_t *rand;
	uint_t i, j;
	uint16_t suite;
	int ch_recsz = KSSL2_CH_MIN_RECSZ;

	ASSERT(mp->b_wptr >= mp->b_rptr + recsz);
	ASSERT(ssl->hs_waitstate == wait_client_hello);
	ASSERT(ssl->resumed == B_FALSE);

	if (recsz < ch_recsz) {
		goto falert;
	}

	MD5Init(&ssl->hs_md5);
	SHA1Init(&ssl->hs_sha1);

	kssl_update_handshake_hashes(ssl, mp->b_rptr, recsz);

	recend = mp->b_rptr + recsz;

	if (*mp->b_rptr != 1) {
		goto falert;
	}
	mp->b_rptr += 3;

	nsuites = ((uint_t)mp->b_rptr[0] << 8) + (uint_t)mp->b_rptr[1];
	sidlen = ((uint_t)mp->b_rptr[2] << 8) + (uint_t)mp->b_rptr[3];
	randlen = ((uint_t)mp->b_rptr[4] << 8) + (uint_t)mp->b_rptr[5];
	if (nsuites % 3 != 0) {
		KSSL_DEBUG2_IF(kssl_debug,
			"kssl_handle_v2client_hello nsuites = %d, error.",
			nsuites);
		goto falert;
	}
	if (randlen < SSL_MIN_CHALLENGE_BYTES ||
	    randlen > SSL_MAX_CHALLENGE_BYTES) {
		KSSL_DEBUG2_IF(kssl_debug,
			"kssl_handle_v2client_hello randlen out of range: %d",
			randlen);
		goto falert;
	}
	mp->b_rptr += 6;
	ch_recsz += nsuites + sidlen + randlen;
	if (recsz != ch_recsz) {
		goto falert;
	}
	suitesp = mp->b_rptr;
	rand = suitesp + nsuites + sidlen;
	if (randlen < SSL3_RANDOM_LENGTH) {
		bzero(ssl->client_random, SSL3_RANDOM_LENGTH);
	}
	bcopy(rand, &ssl->client_random[SSL3_RANDOM_LENGTH - randlen],
	    randlen);

	for (i = 0; i < ssl->kssl_entry->kssl_cipherSuites_nentries; i++) {
		suite = ssl->kssl_entry->kssl_cipherSuites[i];
		for (j = 0; j < nsuites; j += 3) {
			if (suitesp[j] != 0) {
				continue;
			}

			if (suitesp[j + 1] == ((suite >> 8) & 0xff) &&
			    suitesp[j + 2] == (suite & 0xff)) {
				break;
			}
		}
		if (j < nsuites) {
			break;
		}
	}
	if (i == ssl->kssl_entry->kssl_cipherSuites_nentries) {
		KSSL_DEBUG1_IF(kssl_debug, "kssl_handle_v2client_hello - "
			"cannot find SSLv2 cipher suite");
		ssl->activeinput = B_FALSE;
		return (SSL_MISS);
	}

	mp->b_rptr = recend;

	for (i = 0; i < cipher_suite_defs_nentries; i++) {
		if (suite == cipher_suite_defs[i].suite) {
			break;
		}
	}

	ASSERT(i < cipher_suite_defs_nentries);

	ssl->pending_cipher_suite = suite;
	ssl->pending_malg = cipher_suite_defs[i].malg;
	ssl->pending_calg = cipher_suite_defs[i].calg;
	ssl->pending_keyblksz = cipher_suite_defs[i].keyblksz;

	ASSERT(ssl->sid.cached == B_FALSE);

	(void) random_get_pseudo_bytes(ssl->sid.session_id,
	    SSL3_SESSIONID_BYTES);
	ssl->sid.client_addr = ssl->faddr;
	ssl->sid.cipher_suite = suite;

	err = kssl_send_server_hello(ssl);
	if (err != 0) {
		return (err);
	}
	err = kssl_send_certificate_and_server_hello_done(ssl);
	if (err != 0) {
		return (err);
	}
	KSSL_COUNTER(full_handshakes, 1);
	ssl->hs_waitstate = wait_client_key;
	ssl->activeinput = B_FALSE;
	return (0);

falert:
	kssl_send_alert(ssl, alert_fatal, desc);
	ssl->activeinput = B_FALSE;
	return (EBADMSG);
}

/*
 * Call back routine for asynchronously submitted RSA decryption jobs.
 * The routine retreived the pre-master secret, and proceeds to generate
 * the remaining key materials.
 */
static void
kssl_cke_done(void *arg, int status)
{
	int ret = 0;
	uchar_t *pms;
	size_t pmslen;
	crypto_data_t *pms_data;
	kssl_cmd_t kssl_cmd = KSSL_CMD_NONE;
	ssl_t *ssl = (ssl_t *)arg;
	mblk_t *alertmp;
	kssl_callback_t cbfn;
	void *cbarg;

	mutex_enter(&ssl->kssl_lock);

	ASSERT(ssl->msg.type == client_key_exchange);
	ASSERT(ssl->hs_waitstate == wait_client_key_done);

	if (status != CRYPTO_SUCCESS) {
		kssl_send_alert(ssl, alert_fatal, decrypt_error);
		kssl_cmd = KSSL_CMD_SEND;
		goto out;
	}

	pms_data = (crypto_data_t *)(ssl->job.buf);

	ASSERT(pms_data != NULL);

	pmslen = pms_data->cd_length;
	pms = kssl_rsa_unwrap((uchar_t *)pms_data->cd_raw.iov_base, &pmslen);

	/* generate master key and save it in the ssl sid structure */
	if (IS_TLS(ssl)) {
		ret = kssl_generate_tls_ms(ssl, pms, pmslen);
		if (!CRYPTO_ERR(ret))
			ret = kssl_generate_tls_keyblock(ssl);
	} else {
		kssl_generate_ssl_ms(ssl, pms, pmslen);
		kssl_generate_keyblock(ssl);
	}

	if (ret == CRYPTO_SUCCESS)
		ssl->hs_waitstate = wait_change_cipher;

out:
	kmem_free(ssl->job.buf, ssl->job.buflen);

	ssl->job.kjob = 0;
	ssl->job.buf = NULL;
	ssl->job.buflen = 0;

	ssl->activeinput = B_FALSE;

	/* If we're the only ones left, then we won't callback */
	if (ssl->kssl_refcnt == 1) {
		mutex_exit(&ssl->kssl_lock);
		KSSL_SSL_REFRELE(ssl);
		return;
	}

	cbfn = ssl->cke_callback_func;
	cbarg = ssl->cke_callback_arg;
	alertmp = ssl->alert_sendbuf;
	ssl->alert_sendbuf = NULL;

	mutex_exit(&ssl->kssl_lock);

	KSSL_SSL_REFRELE(ssl);

	/* Now call the callback routine */
	(*(cbfn))(cbarg, alertmp, kssl_cmd);
}

/*
 * Returns the first complete contiguous record out of rec_ass_head
 * The record is returned in a separate contiguous mblk, rec_ass_head is
 * left pointing to the next record in the queue.
 *
 * The output looks as follows:
 *
 * |--------|---------- .... -----|<---------->|<----------->|--- ... ---|
 * ^        ^                     ^  mac_size     pad_size               ^
 * |        |___ b_rptr  b_wptr __|                                      |
 * |                                                                     |
 * |___ db_base                                                db_lim ___|
 */
mblk_t *
kssl_get_next_record(ssl_t *ssl)
{
	mblk_t *mp, *retmp;
	int rhsz = SSL3_HDR_LEN;
	uint16_t rec_sz;
	int mpsz, total_size;
	SSL3ContentType content_type;

	ASSERT(MUTEX_HELD(&ssl->kssl_lock));

	mp = ssl->rec_ass_head;
	if (mp == NULL)
		return (NULL);

	/* Fast path: when mp has at least a complete record */
	if (MBLKL(mp) < rhsz) {
		/* Not even a complete header in there yet */
		if (msgdsize(mp) < rhsz) {
			return (NULL);
		}

		if (!pullupmsg(mp, rhsz)) {
			kssl_send_alert(ssl, alert_fatal, internal_error);
			freemsg(mp);
			ssl->rec_ass_head = ssl->rec_ass_tail = NULL;
			return (NULL);
		}
	}
	content_type = (SSL3ContentType)mp->b_rptr[0];
	if (content_type == content_handshake_v2) {
		rec_sz = (uint16_t)mp->b_rptr[1];
		rhsz = 2;
	} else {
		uint8_t *rec_sz_p = (uint8_t *)mp->b_rptr + 3;
		rec_sz = BE16_TO_U16(rec_sz_p);
	}

	/*
	 * same tests as above. Only rare very fragmented cases will
	 * incur the cost of msgdsize() and msgpullup(). Well formed
	 * packets will fall in the most frequent fast path.
	 */
	total_size = rhsz + rec_sz;

	/*
	 * Missing: defensive against record fabricated with longer than
	 * MAX record length.
	 */
	if (MBLKL(mp) < total_size) {
		/* Not a complete record yet. Keep accumulating */
		if (msgdsize(mp) < total_size) {
			return (NULL);
		}

		if (!pullupmsg(mp, total_size)) {
			kssl_send_alert(ssl, alert_fatal, internal_error);
			freemsg(mp);
			ssl->rec_ass_head = ssl->rec_ass_tail = NULL;
			return (NULL);
		}
	}
	mpsz = MBLKL(mp);	/* could've changed after the pullup */

	if (mpsz > total_size) {
		/* gotta allocate a new block */
		if ((retmp = dupb(mp)) == NULL) {
			kssl_send_alert(ssl, alert_fatal, internal_error);
			freemsg(mp);
			ssl->rec_ass_head = ssl->rec_ass_tail = NULL;
			return (NULL);
		}

		retmp->b_wptr = retmp->b_rptr + total_size;
		mp->b_rptr += total_size;
		ssl->rec_ass_head = mp;
	} else {
		ASSERT(mpsz == total_size);
		ssl->rec_ass_head = mp->b_cont;
		mp->b_cont = NULL;
		retmp = mp;
	}
	/* Adjust the tail */
	if ((mp = ssl->rec_ass_tail = ssl->rec_ass_head) != NULL) {
		for (; mp->b_cont != NULL; mp = mp->b_cont) {
			ssl->rec_ass_tail = mp->b_cont;
		}
	}

	return (retmp);
}


static void
kssl_mblksfree(ssl_t *ssl)
{

	ASSERT(ssl != NULL);

	if (ssl->rec_ass_head != NULL) {
		freemsg(ssl->rec_ass_head);
	}
	ssl->rec_ass_head = NULL;
	ssl->rec_ass_tail = NULL;

	if (ssl->msg.head != NULL) {
		freemsg(ssl->msg.head);
	}
	ssl->msg.head = NULL;
	ssl->msg.tail = NULL;

	if (ssl->handshake_sendbuf != NULL) {
		freemsg(ssl->handshake_sendbuf);
		ssl->handshake_sendbuf = NULL;
	}
	if (ssl->alert_sendbuf != NULL) {
		freemsg(ssl->alert_sendbuf);
		ssl->alert_sendbuf = NULL;
	}
}

static void
kssl_specsfree(ssl_t *ssl)
{
	KSSLCipherSpec *spec = &ssl->spec[KSSL_READ];

	if (spec->cipher_ctx != NULL) {
		crypto_cancel_ctx(spec->cipher_ctx);
		spec->cipher_ctx = 0;
	}

	spec = &ssl->spec[KSSL_WRITE];

	if (spec->cipher_ctx != NULL) {
		crypto_cancel_ctx(spec->cipher_ctx);
		spec->cipher_ctx = 0;
	}
}

/*
 * Frees the ssl structure (aka the context of an SSL session).
 * Any pending crypto jobs are cancelled.
 * Any initiated crypto contexts are freed as well.
 */
void
kssl_free_context(ssl_t *ssl)
{
	ASSERT(ssl != NULL);
	if (!(MUTEX_HELD(&ssl->kssl_lock))) {
		/* we're coming from an external API entry point */
		mutex_enter(&ssl->kssl_lock);
	}

	if (ssl->job.kjob != NULL) {
		crypto_cancel_req(ssl->job.kjob);
		kmem_free(ssl->job.buf, ssl->job.buflen);

		ssl->job.kjob = 0;
		ssl->job.buf = NULL;
		ssl->job.buflen = 0;
	}

	kssl_mblksfree(ssl);
	kssl_specsfree(ssl);

	KSSL_ENTRY_REFRELE(ssl->kssl_entry);
	ssl->kssl_entry = NULL;

	mutex_exit(&ssl->kssl_lock);

	kmem_cache_free(kssl_cache, ssl);
	kssl_cache_count--;
}