/* * 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) 2007, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * These routines provide the SMB MAC signing for the SMB2 server. * The routines calculate the signature of a SMB message in an mbuf chain. * * The following table describes the client server * signing registry relationship * * | Required | Enabled | Disabled * -------------+---------------+------------ +-------------- * Required | Signed | Signed | Fail * -------------+---------------+-------------+----------------- * Enabled | Signed | Signed | Not Signed * -------------+---------------+-------------+---------------- * Disabled | Fail | Not Signed | Not Signed */ #include #include #include #include #include #include #define SMB2_SIG_OFFS 48 #define SMB2_SIG_SIZE 16 /* * Called during session destroy. */ static void smb2_sign_fini(smb_session_t *s) { smb_sign_mech_t *mech; if ((mech = s->sign_mech) != NULL) { kmem_free(mech, sizeof (*mech)); s->sign_mech = NULL; } } /* * smb2_sign_begin * * Get the mechanism info. * Intializes MAC key based on the user session key and store it in * the signing structure. This begins signing on this session. */ int smb2_sign_begin(smb_request_t *sr, smb_token_t *token) { smb_session_t *s = sr->session; smb_user_t *u = sr->uid_user; struct smb_key *sign_key = &u->u_sign_key; smb_sign_mech_t *mech; int rc; /* * We should normally have a session key here because * our caller filters out Anonymous and Guest logons. * However, buggy clients could get us here without a * session key, in which case we'll fail later when a * request that requires signing can't be checked. */ if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0) return (0); /* * Session-level initialization (once per session) * Get mech handle, sign_fini function. */ smb_rwx_rwenter(&s->s_lock, RW_WRITER); if (s->sign_mech == NULL) { mech = kmem_zalloc(sizeof (*mech), KM_SLEEP); rc = smb2_hmac_getmech(mech); if (rc != 0) { kmem_free(mech, sizeof (*mech)); smb_rwx_rwexit(&s->s_lock); return (rc); } s->sign_mech = mech; s->sign_fini = smb2_sign_fini; } smb_rwx_rwexit(&s->s_lock); /* * Compute and store the signing key, which lives in * the user structure. */ sign_key->len = SMB2_SIG_SIZE; /* * For SMB2, the signing key is just the first 16 bytes * of the session key (truncated or padded with zeros). * [MS-SMB2] 3.2.5.3.1 */ bcopy(token->tkn_ssnkey.val, sign_key->key, MIN(token->tkn_ssnkey.len, sign_key->len)); mutex_enter(&u->u_mutex); if (s->secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) u->u_sign_flags |= SMB_SIGNING_ENABLED; if (s->secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) u->u_sign_flags |= SMB_SIGNING_ENABLED | SMB_SIGNING_CHECK; mutex_exit(&u->u_mutex); /* * If we just turned on signing, the current request * (an SMB2 session setup) will have come in without * SMB2_FLAGS_SIGNED (and not signed) but the response * is is supposed to be signed. [MS-SMB2] 3.3.5.5 */ if (u->u_sign_flags & SMB_SIGNING_ENABLED) sr->smb2_hdr_flags |= SMB2_FLAGS_SIGNED; return (0); } /* * smb2_sign_calc * * Calculates MAC signature for the given buffer and returns * it in the mac_sign parameter. * * The signature is in the last 16 bytes of the SMB2 header. * The signature algorighm is to compute HMAC SHA256 over the * entire command, with the signature field set to zeros. * * Return 0 if success else -1 */ static int smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc, uint8_t *digest) { uint8_t tmp_hdr[SMB2_HDR_SIZE]; smb_sign_ctx_t ctx = 0; smb_session_t *s = sr->session; smb_user_t *u = sr->uid_user; struct smb_key *sign_key = &u->u_sign_key; struct mbuf *mbuf; int offset, resid, tlen, rc; if (s->sign_mech == NULL || sign_key->len == 0) return (-1); rc = smb2_hmac_init(&ctx, s->sign_mech, sign_key->key, sign_key->len); if (rc != 0) return (rc); /* * Work with a copy of the SMB2 header so we can * clear the signature field without modifying * the original message. */ tlen = SMB2_HDR_SIZE; offset = mbc->chain_offset; resid = mbc->max_bytes - offset; if (smb_mbc_peek(mbc, offset, "#c", tlen, tmp_hdr) != 0) return (-1); bzero(tmp_hdr + SMB2_SIG_OFFS, SMB2_SIG_SIZE); if ((rc = smb2_hmac_update(ctx, tmp_hdr, tlen)) != 0) return (rc); offset += tlen; resid -= tlen; /* * Digest the rest of the SMB packet, starting at the data * just after the SMB header. * * Advance to the src mbuf where we start digesting. */ mbuf = mbc->chain; while (mbuf != NULL && (offset >= mbuf->m_len)) { offset -= mbuf->m_len; mbuf = mbuf->m_next; } if (mbuf == NULL) return (-1); /* * Digest the remainder of this mbuf, limited to the * residual count, and starting at the current offset. * (typically SMB2_HDR_SIZE) */ tlen = mbuf->m_len - offset; if (tlen > resid) tlen = resid; rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen); if (rc != 0) return (rc); resid -= tlen; /* * Digest any more mbufs in the chain. */ while (resid > 0) { mbuf = mbuf->m_next; if (mbuf == NULL) return (-1); tlen = mbuf->m_len; if (tlen > resid) tlen = resid; rc = smb2_hmac_update(ctx, (uint8_t *)mbuf->m_data, tlen); if (rc != 0) return (rc); resid -= tlen; } /* * Note: digest is _always_ SMB2_SIG_SIZE, * even if the mech uses a longer one. */ if ((rc = smb2_hmac_final(ctx, digest)) != 0) return (rc); return (0); } /* * smb2_sign_check_request * * Calculates MAC signature for the request mbuf chain * using the next expected sequence number and compares * it to the given signature. * * Note it does not check the signature for secondary transactions * as their sequence number is the same as the original request. * * Return 0 if the signature verifies, otherwise, returns -1; * */ int smb2_sign_check_request(smb_request_t *sr) { uint8_t req_sig[SMB2_SIG_SIZE]; uint8_t vfy_sig[SMB2_SIG_SIZE]; struct mbuf_chain *mbc = &sr->smb_data; smb_user_t *u = sr->uid_user; int sig_off; /* * Don't check commands with a zero session ID. * [MS-SMB2] 3.3.4.1.1 */ if (sr->smb_uid == 0 || u == NULL) return (0); /* Get the request signature. */ sig_off = sr->smb2_cmd_hdr + SMB2_SIG_OFFS; if (smb_mbc_peek(mbc, sig_off, "#c", SMB2_SIG_SIZE, req_sig) != 0) return (-1); /* * Compute the correct signature and compare. */ if (smb2_sign_calc(sr, mbc, vfy_sig) != 0) return (-1); if (memcmp(vfy_sig, req_sig, SMB2_SIG_SIZE) != 0) { cmn_err(CE_NOTE, "smb2_sign_check_request: bad signature"); return (-1); } return (0); } /* * smb2_sign_reply * * Calculates MAC signature for the given mbuf chain, * and write it to the signature field in the mbuf. * */ void smb2_sign_reply(smb_request_t *sr) { uint8_t reply_sig[SMB2_SIG_SIZE]; struct mbuf_chain tmp_mbc; smb_user_t *u = sr->uid_user; int hdr_off, msg_len; if (u == NULL) return; msg_len = sr->reply.chain_offset - sr->smb2_reply_hdr; (void) MBC_SHADOW_CHAIN(&tmp_mbc, &sr->reply, sr->smb2_reply_hdr, msg_len); /* * Calculate the MAC signature for this reply. */ if (smb2_sign_calc(sr, &tmp_mbc, reply_sig) != 0) return; /* * Poke the signature into the response. */ hdr_off = sr->smb2_reply_hdr + SMB2_SIG_OFFS; (void) smb_mbc_poke(&sr->reply, hdr_off, "#c", SMB2_SIG_SIZE, reply_sig); }