/*
* 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/kmem.h>
#include <sys/cpuvar.h>
#include <sys/atomic.h>
#include <sys/sysmacros.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/tihdr.h>
#include "ksslimpl.h"
#include "ksslproto.h"
#include "ksslapi.h"
static kssl_cmd_t kssl_handle_any_record(kssl_ctx_t ctx, mblk_t *mp,
mblk_t **decrmp, kssl_callback_t cbfn, void *arg);
static boolean_t kssl_enqueue(kssl_chain_t **head, void *item);
static void kssl_dequeue(kssl_chain_t **head, void *item);
static kssl_status_t kssl_build_single_record(ssl_t *ssl, mblk_t *mp);
/*
* The socket bind request is intercepted and re-routed here
* to see is there is SSL relevant job to do, based on the kssl config
* in the kssl_entry_tab.
* Looks up the kernel SSL proxy table, to find an entry that matches the
* same serveraddr, and has one of the following two criteria:
* 1. in_port is an ssl_port. This endpoint can be used later as a fallback
* to complete connections that cannot be handled by the SSL kernel proxy
* (typically non supported ciphersuite). The cookie for the calling client
* is saved with the kssl_entry to be retrieved for the fallback.
* The function returns KSSL_HAS_PROXY.
*
* 2. in_port is a proxy port for another ssl port. The ssl port is then
* substituted to the in_port in the bind_req TPI structure, so that
* the bind falls through to the SSL port. At the end of this operation,
* all the packets arriving to the SSL port will be delivered to an
* accepted endpoint child of this bound socket.
* The kssl_entry_t is returned in *ksslent, for later use by the
* lower modules' SSL hooks that handle the Handshake messages.
* The function returns KSSL_IS_PROXY.
*
* The function returns KSSL_NO_PROXY otherwise.
*/
kssl_endpt_type_t
kssl_check_proxy(struct sockaddr *addr, socklen_t len, void *cookie,
kssl_ent_t *ksslent)
{
int i;
kssl_endpt_type_t ret;
kssl_entry_t *ep;
sin_t *sin;
sin6_t *sin6;
in6_addr_t mapped_v4addr;
in6_addr_t *v6addr;
in_port_t in_port;
if (kssl_entry_tab_nentries == 0) {
return (KSSL_NO_PROXY);
}
ret = KSSL_NO_PROXY;
sin = (struct sockaddr_in *)addr;
switch (len) {
case sizeof (sin_t):
in_port = ntohs(sin->sin_port);
IN6_IPADDR_TO_V4MAPPED(sin->sin_addr.s_addr, &mapped_v4addr);
v6addr = &mapped_v4addr;
break;
case sizeof (sin6_t):
sin6 = (sin6_t *)sin;
in_port = ntohs(sin6->sin6_port);
v6addr = &sin6->sin6_addr;
break;
default:
return (ret);
}
mutex_enter(&kssl_tab_mutex);
for (i = 0; i < kssl_entry_tab_size; i++) {
if ((ep = kssl_entry_tab[i]) == NULL)
continue;
if (IN6_ARE_ADDR_EQUAL(&ep->ke_laddr, v6addr) ||
IN6_IS_ADDR_UNSPECIFIED(&ep->ke_laddr)) {
/* This is an SSL port to fallback to */
if (ep->ke_ssl_port == in_port) {
/*
* Let's see first if there's at least
* an endpoint for a proxy server.
* If there's none, then we return as we have
* no proxy, so that the bind() to the
* transport layer goes through.
* The calling module will ask for this
* cookie if it wants to fall back to it,
* so add this one to the list of fallback
* clients.
*/
if (!kssl_enqueue((kssl_chain_t **)
&(ep->ke_fallback_head), cookie)) {
break;
}
KSSL_ENTRY_REFHOLD(ep);
*ksslent = (kssl_ent_t)ep;
ret = KSSL_HAS_PROXY;
break;
}
/* This is a proxy port. */
if (ep->ke_proxy_port == in_port) {
/* Add the caller's cookie to proxies list */
if (!kssl_enqueue((kssl_chain_t **)
&(ep->ke_proxy_head), cookie)) {
break;
}
/*
* Make this look like the SSL port to the
* transport below
*/
sin->sin_port = htons(ep->ke_ssl_port);
KSSL_ENTRY_REFHOLD(ep);
*ksslent = (kssl_ent_t)ep;
ret = KSSL_IS_PROXY;
break;
}
}
}
mutex_exit(&kssl_tab_mutex);
return (ret);
}
/*
* Retrieved an endpoint "bound" to the SSL entry.
* Such endpoint has previously called kssl_check_proxy(), got itself
* linked to the kssl_entry's ke_fallback_head list.
* This routine returns the cookie from that SSL entry ke_fallback_head list.
*/
void *
kssl_find_fallback(kssl_ent_t ksslent)
{
kssl_entry_t *kssl_entry = (kssl_entry_t *)ksslent;
if (kssl_entry->ke_fallback_head != NULL)
return (kssl_entry->ke_fallback_head->fallback_bound);
KSSL_COUNTER(proxy_fallback_failed, 1);
return (NULL);
}
/*
* Re-usable code for adding and removing an element to/from a chain that
* matches "item"
* The chain is simple-linked and NULL ended.
*/
/*
* This routine returns TRUE if the item was either successfully added to
* the chain, or is already there. It returns FALSE otherwise.
*/
static boolean_t
kssl_enqueue(kssl_chain_t **head, void *item)
{
kssl_chain_t *newchain, *cur;
/* Lookup the existing entries to avoid duplicates */
cur = *head;
while (cur != NULL) {
if (cur->item == item) {
return (B_TRUE);
}
cur = cur->next;
}
newchain = kmem_alloc(sizeof (kssl_chain_t), KM_NOSLEEP);
if (newchain == NULL) {
return (B_FALSE);
}
newchain->item = item;
newchain->next = *head;
*head = newchain;
return (B_TRUE);
}
static void
kssl_dequeue(kssl_chain_t **head, void *item)
{
kssl_chain_t *prev, *cur;
prev = cur = *head;
while (cur != NULL) {
if (cur->item == item) {
if (cur == *head)
*head = (*head)->next;
else
prev->next = cur->next;
kmem_free(cur, sizeof (kssl_chain_t));
return;
}
prev = cur;
cur = cur->next;
}
}
/*
* Holds the kssl_entry
*/
void
kssl_hold_ent(kssl_ent_t ksslent)
{
KSSL_ENTRY_REFHOLD((kssl_entry_t *)ksslent);
}
/*
* Releases the kssl_entry
* If the caller passes a cookie, then it should be removed from both
* proxies and fallbacks chains.
*/
void
kssl_release_ent(kssl_ent_t ksslent, void *cookie, kssl_endpt_type_t endpt_type)
{
kssl_entry_t *kssl_entry = (kssl_entry_t *)ksslent;
if (cookie != NULL) {
if (endpt_type == KSSL_IS_PROXY) {
ASSERT(kssl_entry->ke_proxy_head != NULL);
kssl_dequeue(
(kssl_chain_t **)&kssl_entry->ke_proxy_head,
cookie);
}
if (endpt_type == KSSL_HAS_PROXY) {
ASSERT(kssl_entry->ke_fallback_head != NULL);
kssl_dequeue(
(kssl_chain_t **)&kssl_entry->ke_fallback_head,
cookie);
}
}
KSSL_ENTRY_REFRELE(kssl_entry);
}
/*
* Releases the kssl_context
*/
void
kssl_release_ctx(kssl_ctx_t ksslctx)
{
kssl_free_context((ssl_t *)ksslctx);
}
/*
* Done with asynchronous processing
*/
void
kssl_async_done(kssl_ctx_t ksslctx)
{
ssl_t *ssl = (ssl_t *)ksslctx;
mutex_enter(&ssl->kssl_lock);
if (--ssl->async_ops_pending == 0)
cv_signal(&ssl->async_cv);
mutex_exit(&ssl->kssl_lock);
}
/*
* Packets are accumulated here, if there are packets already queued,
* or if the context is active.
* The context is active when an incoming record processing function
* is already executing on a different thread.
* Queued packets are handled either when an mblk arrived and completes
* a record, or, when the active context processor finishes the task at
* hand.
* The caller has to keep calling this routine in a loop until it returns
* B_FALSE in *more. The reason for this is SSL3: The protocol
* allows the client to send its first application_data message right
* after it had sent its Finished message, and not wait for the server
* ChangeCipherSpec and Finished. This overlap means we can't batch up
* a returned Handshake message to be sent on the wire
* with a decrypted application_data to be delivered to the application.
*/
kssl_cmd_t
kssl_input(kssl_ctx_t ctx, mblk_t *mp, mblk_t **decrmp, boolean_t *more,
kssl_callback_t cbfn, void *arg)
{
mblk_t *recmp, *outmp = NULL;
kssl_cmd_t kssl_cmd;
ssl_t *ssl;
uint8_t *rec_sz_p;
int mplen;
SSL3ContentType content_type;
uint16_t rec_sz;
ASSERT(ctx != NULL);
if (mp != NULL) {
ASSERT(mp->b_prev == NULL && mp->b_next == NULL);
}
ssl = (ssl_t *)(ctx);
*decrmp = NULL;
*more = B_FALSE;
mutex_enter(&ssl->kssl_lock);
if (ssl->close_notify_clnt == B_TRUE) {
DTRACE_PROBE(kssl_err__close_notify);
goto sendnewalert;
}
/* Whomever is currently processing this connection will get to this */
if (ssl->activeinput) {
if (mp != NULL) {
KSSL_ENQUEUE_MP(ssl, mp);
}
mutex_exit(&ssl->kssl_lock);
return (KSSL_CMD_NONE);
}
/*
* Fast path for complete incoming application_data records on an empty
* queue.
* This is by far the most frequently encountered case
*/
if ((!ssl->activeinput) && (ssl->rec_ass_head == NULL) &&
((mp != NULL) && (mplen = MBLKL(mp)) > SSL3_HDR_LEN)) {
DTRACE_PROBE1(kssl_mblk__fast_path, mblk_t *, mp);
content_type = (SSL3ContentType)mp->b_rptr[0];
if ((content_type == content_application_data) &&
(ssl->hs_waitstate == idle_handshake)) {
rec_sz_p = SSL3_REC_SIZE(mp);
rec_sz = BE16_TO_U16(rec_sz_p);
if ((mp->b_cont == NULL) && (mplen == rec_sz)) {
*decrmp = mp;
mutex_exit(&ssl->kssl_lock);
return (KSSL_CMD_DELIVER_PROXY);
}
}
}
ssl->activeinput = B_TRUE;
/* Accumulate at least one record */
if (mp != NULL) {
KSSL_ENQUEUE_MP(ssl, mp);
mp = NULL;
}
recmp = kssl_get_next_record(ssl);
if (recmp == NULL) {
ssl->activeinput = B_FALSE;
if (ssl->alert_sendbuf != NULL) {
DTRACE_PROBE(kssl_err__alert_to_send);
goto sendalert;
}
/* Not even a complete header yet. wait for the rest */
mutex_exit(&ssl->kssl_lock);
return (KSSL_CMD_NONE);
}
do {
DTRACE_PROBE1(kssl_mblk__kssl_input_cycle, mblk_t *, recmp);
content_type = (SSL3ContentType)recmp->b_rptr[0];
switch (content_type) {
case content_application_data:
/*
* application_data records are decrypted and
* MAC-verified by the stream head, and in the context
* a read()'ing thread. This avoids unfairly charging
* the cost of handling this record on the whole system,
* and prevents doing it while in the shared IP
* perimeter.
*/
ssl->activeinput = B_FALSE;
if (ssl->hs_waitstate != idle_handshake) {
DTRACE_PROBE(kssl_err__waitstate_not_idle);
goto sendnewalert;
}
outmp = recmp;
kssl_cmd = KSSL_CMD_DELIVER_PROXY;
break;
case content_change_cipher_spec:
case content_alert:
case content_handshake:
case content_handshake_v2:
/*
* If we're past the initial handshake, start letting
* the stream head process all records, in particular
* the close_notify.
* This is needed to avoid processing them out of
* sequence when previous application data packets are
* waiting to be decrypted/MAC'ed and delivered.
*/
if (ssl->hs_waitstate == idle_handshake) {
ssl->activeinput = B_FALSE;
outmp = recmp;
kssl_cmd = KSSL_CMD_DELIVER_PROXY;
} else {
kssl_cmd = kssl_handle_any_record(ssl, recmp,
&outmp, cbfn, arg);
}
break;
default:
ssl->activeinput = B_FALSE;
DTRACE_PROBE(kssl_err__invalid_content_type);
goto sendnewalert;
}
/* Priority to Alert messages */
if (ssl->alert_sendbuf != NULL) {
DTRACE_PROBE(kssl_err__alert_to_send_cycle);
goto sendalert;
}
/* Then handshake messages */
if (ssl->handshake_sendbuf) {
if (*decrmp != NULL) {
linkb(*decrmp, ssl->handshake_sendbuf);
} else {
*decrmp = ssl->handshake_sendbuf;
}
ssl->handshake_sendbuf = NULL;
*more = ((ssl->rec_ass_head != NULL) &&
(!ssl->activeinput));
mutex_exit(&ssl->kssl_lock);
return (kssl_cmd);
}
if (ssl->hs_waitstate == idle_handshake) {
*more = ((ssl->rec_ass_head != NULL) &&
(!ssl->activeinput));
}
if (outmp != NULL) {
*decrmp = outmp;
/*
* Don't process any packet after an application_data.
* We could well receive the close_notify which should
* be handled separately.
*/
mutex_exit(&ssl->kssl_lock);
return (kssl_cmd);
}
/*
* The current record isn't done yet. Don't start the next one
*/
if (ssl->activeinput) {
mutex_exit(&ssl->kssl_lock);
return (kssl_cmd);
}
} while ((recmp = kssl_get_next_record(ssl)) != NULL);
mutex_exit(&ssl->kssl_lock);
return (kssl_cmd);
sendnewalert:
kssl_send_alert(ssl, alert_fatal, unexpected_message);
if (mp != NULL) {
freeb(mp);
}
sendalert:
*decrmp = ssl->alert_sendbuf;
ssl->alert_sendbuf = NULL;
mutex_exit(&ssl->kssl_lock);
return (KSSL_CMD_SEND);
}
/*
* Decrypt and verify the MAC of an incoming chain of application_data record.
* Each block has exactly one SSL record.
*/
kssl_cmd_t
kssl_handle_mblk(kssl_ctx_t ctx, mblk_t **mpp, mblk_t **outmp)
{
uchar_t *recend, *rec_sz_p;
uchar_t *real_recend;
mblk_t *prevmp = NULL, *nextmp, *firstmp, *mp, *copybp;
int mac_sz;
uchar_t version[2];
uint16_t rec_sz;
SSL3AlertDescription desc;
SSL3ContentType content_type;
ssl_t *ssl;
KSSLCipherSpec *spec;
int error, ret;
kssl_cmd_t kssl_cmd = KSSL_CMD_DELIVER_PROXY;
boolean_t deliverit = B_FALSE;
crypto_data_t cipher_data;
ASSERT(ctx != NULL);
ssl = (ssl_t *)(ctx);
mp = firstmp = *mpp;
*outmp = NULL;
more:
while (mp != NULL) {
ASSERT(DB_TYPE(mp) == M_DATA);
if (DB_REF(mp) > 1) {
/*
* Fortunately copyb() preserves the offset,
* tail space and alignment so the copy is
* ready to be made an SSL record.
*/
if ((copybp = copyb(mp)) == NULL)
return (NULL);
copybp->b_cont = mp->b_cont;
if (mp == firstmp) {
*mpp = copybp;
} else if (prevmp != NULL) {
prevmp->b_cont = copybp;
}
freeb(mp);
mp = copybp;
}
DTRACE_PROBE1(kssl_mblk__handle_record_cycle, mblk_t *, mp);
content_type = (SSL3ContentType)mp->b_rptr[0];
switch (content_type) {
case content_application_data:
break;
case content_change_cipher_spec:
case content_alert:
case content_handshake:
case content_handshake_v2:
nextmp = mp->b_cont;
/* Remove this message */
if (prevmp != NULL) {
prevmp->b_cont = nextmp;
/*
* If we had processed blocks that need to
* be delivered, then remember that error code
*/
if (kssl_cmd == KSSL_CMD_DELIVER_PROXY)
deliverit = B_TRUE;
}
mutex_enter(&ssl->kssl_lock);
/* NOTE: This routine could free mp. */
kssl_cmd = kssl_handle_any_record(ssl, mp, outmp,
NULL, NULL);
if (ssl->alert_sendbuf != NULL) {
mp = nextmp;
DTRACE_PROBE(kssl_err__alert_after_handle_any);
goto sendalert;
}
mutex_exit(&ssl->kssl_lock);
if (deliverit) {
kssl_cmd = KSSL_CMD_DELIVER_PROXY;
}
mp = nextmp;
continue; /* to the while loop */
default:
desc = decode_error;
KSSL_COUNTER(internal_errors, 1);
DTRACE_PROBE(kssl_err__decode_error);
goto makealert;
}
version[0] = mp->b_rptr[1];
version[1] = mp->b_rptr[2];
rec_sz_p = SSL3_REC_SIZE(mp);
rec_sz = BE16_TO_U16(rec_sz_p);
mp->b_rptr += SSL3_HDR_LEN;
recend = mp->b_rptr + rec_sz;
real_recend = recend;
/*
* Check the assumption that each mblk contains exactly
* one complete SSL record. We bail out if the check fails.
*/
ASSERT(recend == mp->b_wptr);
if (recend != mp->b_wptr) {
desc = decode_error;
KSSL_COUNTER(internal_errors, 1);
DTRACE_PROBE(kssl_err__not_complete_record);
goto makealert;
}
spec = &ssl->spec[KSSL_READ];
mac_sz = spec->mac_hashsz;
if (spec->cipher_ctx != 0) {
/*
* The record length must be a multiple of the
* block size for block ciphers.
* The cipher_bsize is always a power of 2.
*/
if ((spec->cipher_type == type_block) &&
((rec_sz & (spec->cipher_bsize - 1)) != 0)) {
DTRACE_PROBE2(kssl_err__bad_record_size,
uint16_t, rec_sz,
int, spec->cipher_bsize);
KSSL_COUNTER(record_decrypt_failure, 1);
mp->b_rptr = recend;
desc = decrypt_error;
goto makealert;
}
cipher_data.cd_format = CRYPTO_DATA_RAW;
cipher_data.cd_offset = 0;
cipher_data.cd_length = rec_sz;
cipher_data.cd_miscdata = NULL;
cipher_data.cd_raw.iov_base = (char *)mp->b_rptr;
cipher_data.cd_raw.iov_len = rec_sz;
error = crypto_decrypt_update(spec->cipher_ctx,
&cipher_data, NULL, NULL);
if (CRYPTO_ERR(error)) {
DTRACE_PROBE1(
kssl_err__crypto_decrypt_update_failed,
int, error);
KSSL_COUNTER(record_decrypt_failure, 1);
mp->b_rptr = recend;
desc = decrypt_error;
goto makealert;
}
}
if (spec->cipher_type == type_block) {
uint_t pad_sz = recend[-1];
pad_sz++;
if (pad_sz + mac_sz > rec_sz) {
DTRACE_PROBE(kssl_err__pad_mac_bigger);
mp->b_rptr = recend;
desc = bad_record_mac;
goto makealert;
}
rec_sz -= pad_sz;
recend -= pad_sz;
}
if (mac_sz != 0) {
uchar_t hash[MAX_HASH_LEN];
if (rec_sz < mac_sz) {
DTRACE_PROBE(kssl_err__pad_smaller_mac);
mp->b_rptr = real_recend;
desc = bad_record_mac;
goto makealert;
}
rec_sz -= mac_sz;
recend -= mac_sz;
ret = kssl_compute_record_mac(ssl, KSSL_READ,
ssl->seq_num[KSSL_READ], content_type,
version, mp->b_rptr, rec_sz, hash);
if (ret != CRYPTO_SUCCESS ||
bcmp(hash, recend, mac_sz)) {
DTRACE_PROBE1(kssl_mblk__MACmismatch_handlerec,
mblk_t *, mp);
mp->b_rptr = real_recend;
desc = bad_record_mac;
DTRACE_PROBE(kssl_err__msg_MAC_mismatch);
KSSL_COUNTER(verify_mac_failure, 1);
goto makealert;
}
ssl->seq_num[KSSL_READ]++;
}
if (ssl->hs_waitstate != idle_handshake) {
DTRACE_PROBE1(kssl_err__unexpected_msg,
SSL3WaitState, ssl->hs_waitstate);
mp->b_rptr = real_recend;
desc = unexpected_message;
goto makealert;
}
mp->b_wptr = recend;
DTRACE_PROBE1(kssl_mblk__dblk_cooked, mblk_t *, mp);
KSSL_COUNTER(appdata_record_ins, 1);
prevmp = mp;
mp = mp->b_cont;
}
return (kssl_cmd);
makealert:
nextmp = mp->b_cont;
freeb(mp);
mp = nextmp;
mutex_enter(&ssl->kssl_lock);
kssl_send_alert(ssl, alert_fatal, desc);
if (ssl->alert_sendbuf == NULL) {
/* internal memory allocation failure. just return. */
DTRACE_PROBE(kssl_err__alert_msg_alloc_failed);
mutex_exit(&ssl->kssl_lock);
if (mp) {
prevmp = NULL;
goto more;
}
return (KSSL_CMD_NONE);
}
kssl_cmd = KSSL_CMD_SEND;
sendalert:
if (*outmp == NULL) {
*outmp = ssl->alert_sendbuf;
} else {
linkb(*outmp, ssl->alert_sendbuf);
}
ssl->alert_sendbuf = NULL;
mutex_exit(&ssl->kssl_lock);
if (mp) {
prevmp = NULL;
goto more;
}
return (kssl_cmd);
}
/*
* This is the routine that handles incoming SSL records.
* When called the first time, with a NULL context, this routine expects
* a ClientHello SSL Handshake packet and shall allocate a context
* of a new SSL connection.
* During the rest of the handshake packets, the routine adjusts the
* state of the context according to the record received.
* After the ChangeCipherSpec message is received, the routine first
* decrypts/authenticated the packet using the key materials in the
* connection's context.
* The return code tells the caller what to do with the returned packet.
*/
static kssl_cmd_t
kssl_handle_any_record(kssl_ctx_t ctx, mblk_t *mp, mblk_t **decrmp,
kssl_callback_t cbfn, void *arg)
{
uchar_t *recend, *rec_sz_p;
uchar_t version[2];
uchar_t *real_recend, *save_rptr, *save_wptr;
int rhsz = SSL3_HDR_LEN;
uint16_t rec_sz;
int sz;
int mac_sz;
SSL3AlertDescription desc;
SSL3AlertLevel level;
SSL3ContentType content_type;
ssl_t *ssl;
KSSLCipherSpec *spec;
int error = 0, ret;
ASSERT(ctx != NULL);
ssl = (ssl_t *)(ctx);
*decrmp = NULL;
save_rptr = mp->b_rptr;
save_wptr = mp->b_wptr;
ASSERT(MUTEX_HELD(&ssl->kssl_lock));
content_type = (SSL3ContentType)mp->b_rptr[0];
if (content_type == content_handshake_v2) {
if (ssl->hs_waitstate == wait_client_hello) {
/* V2 compatible ClientHello */
if (mp->b_rptr[3] == 0x03 &&
(mp->b_rptr[4] == 0x01 ||
mp->b_rptr[4] == 0x00)) {
ssl->major_version = version[0] = mp->b_rptr[3];
ssl->minor_version = version[1] = mp->b_rptr[4];
} else {
/* We don't support "pure" SSLv2 */
DTRACE_PROBE(kssl_err__no_SSLv2);
ssl->major_version = mp->b_rptr[3];
ssl->minor_version = mp->b_rptr[4];
desc = protocol_version;
goto sendalert;
}
}
rec_sz = (uint16_t)mp->b_rptr[1];
rhsz = 2;
} else {
ssl->major_version = version[0] = mp->b_rptr[1];
ssl->minor_version = version[1] = mp->b_rptr[2];
rec_sz_p = SSL3_REC_SIZE(mp);
rec_sz = BE16_TO_U16(rec_sz_p);
}
mp->b_rptr += rhsz;
recend = mp->b_rptr + rec_sz;
real_recend = recend;
/*
* Check the assumption that each mblk contains exactly
* one complete SSL record. We bail out if the check fails.
*/
ASSERT(recend == mp->b_wptr);
if (recend != mp->b_wptr) {
DTRACE_PROBE3(kssl_mblk__handle_any_record_recszerr,
mblk_t *, mp, int, rhsz, int, rec_sz);
DTRACE_PROBE(kssl_err__record_size);
desc = decode_error;
KSSL_COUNTER(internal_errors, 1);
goto sendalert;
}
spec = &ssl->spec[KSSL_READ];
mac_sz = spec->mac_hashsz;
if (spec->cipher_ctx != 0) {
/*
* The record length must be a multiple of the
* block size for block ciphers.
*/
if ((spec->cipher_type == type_block) &&
((rec_sz & (spec->cipher_bsize - 1)) != 0)) {
DTRACE_PROBE2(kssl_err__bad_record_size,
uint16_t, rec_sz, int, spec->cipher_bsize);
KSSL_COUNTER(record_decrypt_failure, 1);
mp->b_rptr = recend;
desc = decrypt_error;
goto sendalert;
}
spec->cipher_data.cd_length = rec_sz;
spec->cipher_data.cd_raw.iov_base = (char *)mp->b_rptr;
spec->cipher_data.cd_raw.iov_len = rec_sz;
error = crypto_decrypt_update(spec->cipher_ctx,
&spec->cipher_data, NULL, NULL);
if (CRYPTO_ERR(error)) {
DTRACE_PROBE1(kssl_err__crypto_decrypt_update_failed,
int, error);
KSSL_COUNTER(record_decrypt_failure, 1);
mp->b_rptr = recend;
desc = decrypt_error;
goto sendalert;
}
}
if (spec->cipher_type == type_block) {
uint_t pad_sz = recend[-1];
pad_sz++;
if (pad_sz + mac_sz > rec_sz) {
DTRACE_PROBE2(kssl_err__pad_mac_mismatch,
int, pad_sz, int, mac_sz);
mp->b_rptr = recend;
desc = bad_record_mac;
goto sendalert;
}
rec_sz -= pad_sz;
recend -= pad_sz;
}
if (mac_sz != 0) {
uchar_t hash[MAX_HASH_LEN];
if (rec_sz < mac_sz) {
DTRACE_PROBE1(kssl_err__mac_size_too_big,
int, mac_sz);
mp->b_rptr = real_recend;
desc = bad_record_mac;
goto sendalert;
}
rec_sz -= mac_sz;
recend -= mac_sz;
ret = kssl_compute_record_mac(ssl, KSSL_READ,
ssl->seq_num[KSSL_READ], content_type,
version, mp->b_rptr, rec_sz, hash);
if (ret != CRYPTO_SUCCESS ||
bcmp(hash, recend, mac_sz)) {
DTRACE_PROBE1(kssl_mblk__MACmismatch_anyrecord,
mblk_t *, mp);
mp->b_rptr = real_recend;
desc = bad_record_mac;
DTRACE_PROBE(kssl_err__msg_MAC_mismatch);
KSSL_COUNTER(verify_mac_failure, 1);
goto sendalert;
}
ssl->seq_num[KSSL_READ]++;
DTRACE_PROBE1(kssl_mblk__after_compute_MAC,
mblk_t *, mp);
}
switch (content_type) {
case content_handshake:
do {
DTRACE_PROBE1(kssl_mblk__content_handshake_cycle,
mblk_t *, mp);
if (error != 0 ||
/* ignore client renegotiation for now */
ssl->hs_waitstate == idle_handshake) {
mp->b_rptr = recend;
DTRACE_PROBE(kssl_renegotiation_request);
}
if (mp->b_rptr == recend) {
mp->b_rptr = real_recend;
if (error != 0) {
goto error;
}
freeb(mp);
if (ssl->hs_waitstate == wait_client_key_done)
return (KSSL_CMD_QUEUED);
return ((ssl->handshake_sendbuf != NULL) ?
KSSL_CMD_SEND : KSSL_CMD_NONE);
}
if (ssl->msg.state < MSG_BODY) {
if (ssl->msg.state == MSG_INIT) {
ssl->msg.type =
(SSL3HandshakeType)*mp->b_rptr++;
ssl->msg.state = MSG_INIT_LEN;
}
if (ssl->msg.state == MSG_INIT_LEN) {
int msglenb =
ssl->msg.msglen_bytes;
int msglen = ssl->msg.msglen;
while (mp->b_rptr < recend &&
msglenb < 3) {
msglen = (msglen << 8) +
(uint_t)(*mp->b_rptr++);
msglenb++;
}
ssl->msg.msglen_bytes = msglenb;
ssl->msg.msglen = msglen;
if (msglenb == 3) {
ssl->msg.state = MSG_BODY;
}
}
if (mp->b_rptr == recend) {
mp->b_rptr = real_recend;
freeb(mp);
return (KSSL_CMD_NONE);
}
}
ASSERT(ssl->msg.state == MSG_BODY);
sz = recend - mp->b_rptr;
if (ssl->msg.head == NULL &&
ssl->msg.msglen <= sz) {
continue;
}
if (ssl->msg.head != NULL) {
sz += msgdsize(ssl->msg.head);
if (ssl->msg.msglen <= sz) {
ssl->msg.tail->b_cont = mp;
mp = ssl->msg.head;
ssl->sslcnt = 100;
ssl->msg.head = NULL;
ssl->msg.tail = NULL;
if (pullupmsg(mp, -1)) {
recend = mp->b_rptr + sz;
ASSERT(recend <= mp->b_wptr);
continue;
}
mp->b_rptr = real_recend;
error = ENOMEM;
KSSL_COUNTER(alloc_fails, 1);
goto error;
}
}
mp->b_wptr = recend;
if (ssl->msg.head == NULL) {
ssl->msg.head = mp;
ssl->msg.tail = mp;
return (KSSL_CMD_NONE);
} else {
ssl->msg.tail->b_cont = mp;
ssl->msg.tail = mp;
return (KSSL_CMD_NONE);
}
} while (kssl_handle_handshake_message(ssl, mp, &error, cbfn,
arg));
if (error == SSL_MISS) {
mp->b_rptr = save_rptr;
mp->b_wptr = save_wptr;
KSSL_COUNTER(fallback_connections, 1);
return (KSSL_CMD_NOT_SUPPORTED);
}
if (ssl->hs_waitstate == wait_client_key_done) {
return (KSSL_CMD_QUEUED);
} else {
return (KSSL_CMD_NONE);
}
case content_alert:
DTRACE_PROBE1(kssl_mblk__content_alert, mblk_t *, mp);
if (rec_sz != 2) {
DTRACE_PROBE(kssl_err__illegal_param);
mp->b_rptr = real_recend;
desc = illegal_parameter;
goto sendalert;
} else {
level = *mp->b_rptr++;
desc = *mp->b_rptr++;
mp->b_rptr = real_recend;
if (level != alert_warning || desc != close_notify) {
if (ssl->sid.cached == B_TRUE) {
kssl_uncache_sid(&ssl->sid,
ssl->kssl_entry);
}
DTRACE_PROBE2(kssl_err__bad_content_alert,
SSL3AlertLevel, level,
SSL3AlertDescription, desc);
ssl->fatal_alert = B_TRUE;
error = EBADMSG;
goto error;
} else {
ssl->close_notify_clnt = B_TRUE;
ssl->activeinput = B_FALSE;
freeb(mp);
return (KSSL_CMD_NONE);
}
}
case content_change_cipher_spec:
DTRACE_PROBE1(kssl_mblk__change_cipher_spec,
mblk_t *, mp);
if (ssl->hs_waitstate != wait_change_cipher) {
desc = unexpected_message;
} else if (rec_sz != 1 || *mp->b_rptr != 1) {
desc = illegal_parameter;
} else {
mp->b_rptr = real_recend;
ssl->hs_waitstate = wait_finished;
ssl->seq_num[KSSL_READ] = 0;
if ((error = kssl_spec_init(ssl, KSSL_READ)) != 0) {
DTRACE_PROBE1(kssl_err__kssl_spec_init_error,
int, error);
goto error;
}
ssl->activeinput = B_FALSE;
freeb(mp);
return (KSSL_CMD_NONE);
}
mp->b_rptr = real_recend;
DTRACE_PROBE(kssl_err__change_cipher_spec);
goto sendalert;
case content_application_data:
DTRACE_PROBE1(kssl_mblk__content_app_data,
mblk_t *, mp);
if (ssl->hs_waitstate != idle_handshake) {
DTRACE_PROBE(kssl_err__content_app_data);
mp->b_rptr = real_recend;
desc = unexpected_message;
goto sendalert;
}
mp->b_wptr = recend;
*decrmp = mp;
ssl->activeinput = B_FALSE;
return (KSSL_CMD_DELIVER_PROXY);
case content_handshake_v2:
DTRACE_PROBE1(kssl_mblk__content_handshake_v2,
mblk_t *, mp);
error = kssl_handle_v2client_hello(ssl, mp, rec_sz);
if (error == SSL_MISS) {
mp->b_rptr = save_rptr;
mp->b_wptr = save_wptr;
KSSL_COUNTER(fallback_connections, 1);
return (KSSL_CMD_NOT_SUPPORTED);
} else if (error != 0) {
DTRACE_PROBE(kssl_err__v2client_hello_failed);
goto error;
}
freeb(mp);
return (KSSL_CMD_SEND);
default:
DTRACE_PROBE1(kssl_mblk__unexpected_msg,
mblk_t *, mp);
mp->b_rptr = real_recend;
desc = unexpected_message;
break;
}
sendalert:
kssl_send_alert(ssl, alert_fatal, desc);
*decrmp = ssl->alert_sendbuf;
ssl->alert_sendbuf = NULL;
freeb(mp);
return ((*decrmp != NULL) ? KSSL_CMD_SEND : KSSL_CMD_NONE);
error:
freeb(mp);
return (KSSL_CMD_NONE);
}
/*
* Initialize the context of an SSL connection, coming to the specified
* address. The ssl structure is returned held.
*/
kssl_status_t
kssl_init_context(kssl_ent_t kssl_ent, struct sockaddr *addr, int mss,
kssl_ctx_t *kssl_ctxp)
{
ssl_t *ssl = kmem_cache_alloc(kssl_cache, KM_NOSLEEP);
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
if (ssl == NULL) {
return (KSSL_STS_ERR);
}
bzero(ssl, sizeof (ssl_t));
ssl->kssl_entry = (kssl_entry_t *)kssl_ent;
KSSL_ENTRY_REFHOLD(ssl->kssl_entry);
if (sin->sin_family == AF_INET) {
IN6_IPADDR_TO_V4MAPPED(sin->sin_addr.s_addr, &ssl->faddr);
} else {
/* struct assignment */
ssl->faddr = ((struct sockaddr_in6 *)addr)->sin6_addr;
}
ssl->tcp_mss = mss;
ssl->sendalert_level = alert_warning;
ssl->sendalert_desc = close_notify;
ssl->sid.cached = B_FALSE;
*kssl_ctxp = (kssl_ctx_t)ssl;
return (KSSL_STS_OK);
}
void
kssl_set_mss(kssl_ctx_t ctx, uint32_t mss)
{
ssl_t *ssl = (ssl_t *)ctx;
ssl->tcp_mss = mss;
}
/*
* Builds SSL records out of the chain of mblks, and returns it.
* Takes a copy of the message before encrypting it if it has another
* reference.
* In case of failure, NULL is returned, and the message will be
* freed by the caller.
* A NULL mp means a close_notify is requested.
*/
mblk_t *
kssl_build_record(kssl_ctx_t ctx, mblk_t *mp)
{
ssl_t *ssl = (ssl_t *)ctx;
mblk_t *retmp = mp, *bp = mp, *prevbp = mp, *copybp;
ASSERT(ssl != NULL);
/*
* Produce new close_notify message. This is necessary to perform
* proper cleanup w.r.t. SSL protocol spec by sending close_notify SSL
* alert record if running with KSSL proxy.
* This should be done prior to sending the FIN so the client side can
* attempt to do graceful cleanup. Ideally, we should wait for client's
* close_notify but not all clients send it which would hang the
* connection. This way of closing the SSL session (Incomplete Close)
* prevents truncation attacks for protocols without end-of-data
* markers (as opposed to the Premature Close).
* Checking the close_notify_srvr flag will prevent from sending the
* close_notify message twice in case of duplicate shutdown() calls.
*/
if (mp == NULL && !ssl->close_notify_srvr) {
kssl_send_alert(ssl, alert_warning, close_notify);
if (ssl->alert_sendbuf == NULL)
return (NULL);
mp = bp = retmp = prevbp = ssl->alert_sendbuf;
ssl->alert_sendbuf = NULL;
ssl->close_notify_srvr = B_TRUE;
}
ASSERT(mp != NULL);
ASSERT(bp != NULL);
do {
if (DB_REF(bp) > 1) {
/*
* Fortunately copyb() preserves the offset,
* tail space and alignment so the copy is
* ready to be made an SSL record.
*/
if ((copybp = copyb(bp)) == NULL)
return (NULL);
copybp->b_cont = bp->b_cont;
if (bp == mp) {
retmp = copybp;
} else {
prevbp->b_cont = copybp;
}
freeb(bp);
bp = copybp;
}
if (kssl_build_single_record(ssl, bp) != KSSL_STS_OK)
return (NULL);
prevbp = bp;
bp = bp->b_cont;
} while (bp != NULL);
return (retmp);
}
/*
* Builds a single SSL record by prepending SSL header (optional) and performing
* encryption and MAC. The encryption of the record is done in-line.
* Expects an mblk with associated dblk's base to have space for the SSL header
* or an mblk which already has the header present. In both cases it presumes
* that the mblk's dblk limit has space for the MAC + padding.
* If the close_notify_srvr flag is set it is presumed that the mblk already
* contains SSL header in which case only the record length field will be
* adjusted with the MAC/padding size.
*/
static kssl_status_t
kssl_build_single_record(ssl_t *ssl, mblk_t *mp)
{
int len;
int reclen;
uchar_t *recstart, *versionp;
KSSLCipherSpec *spec;
int mac_sz;
int pad_sz;
spec = &ssl->spec[KSSL_WRITE];
mac_sz = spec->mac_hashsz;
ASSERT(DB_REF(mp) == 1);
/* The dblk must always have space for the padding and MAC suffix. */
ASSERT(mp->b_datap->db_lim - mp->b_wptr >= mac_sz + spec->cipher_bsize);
/* kssl_send_alert() constructs the SSL header by itself. */
if (!ssl->close_notify_srvr)
len = MBLKL(mp) - SSL3_HDR_LEN;
else
len = MBLKL(mp);
ASSERT(len > 0);
mutex_enter(&ssl->kssl_lock);
recstart = mp->b_rptr;
if (!ssl->close_notify_srvr) {
/* The dblk must have space for the SSL header prefix. */
ASSERT(mp->b_rptr - mp->b_datap->db_base >= SSL3_HDR_LEN);
recstart = mp->b_rptr = mp->b_rptr - SSL3_HDR_LEN;
recstart[0] = content_application_data;
recstart[1] = ssl->major_version;
recstart[2] = ssl->minor_version;
}
versionp = &recstart[1];
reclen = len + mac_sz;
if (spec->cipher_type == type_block) {
pad_sz = spec->cipher_bsize -
(reclen & (spec->cipher_bsize - 1));
ASSERT(reclen + pad_sz <=
SSL3_MAX_RECORD_LENGTH);
reclen += pad_sz;
}
recstart[3] = (reclen >> 8) & 0xff;
recstart[4] = reclen & 0xff;
if (kssl_mac_encrypt_record(ssl, recstart[0], versionp,
recstart, mp) != 0) {
/* Do we need an internal_error Alert here? */
mutex_exit(&ssl->kssl_lock);
return (KSSL_STS_ERR);
}
/* Alert messages are accounted in kssl_send_alert(). */
if (recstart[0] == content_application_data)
KSSL_COUNTER(appdata_record_outs, 1);
mutex_exit(&ssl->kssl_lock);
return (KSSL_STS_OK);
}