Print this page
NEX-15724 SMB3 clients fail to connect using FQDN
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15724 SMB3 clients fail to connect using FQDN
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-10231 SMB logon fails in fksmbd
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
NEX-5273 SMB 3 Encryption
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-5560 smb2 should use 64-bit server-global uids
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-3728 SMB1 signing should use KCF like SMB2/3
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
NEX-3610 CLONE NEX-3591 SMB3 signing
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
NEX-3080 SMB1 signing problem with Kerberos auth.
Reviewed by: Bayard Bell <bayard.bell@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-2869 SMB2 signing fails for multi-user clients like Citrix RDS
SMB-55 SMB2 signing (syslog noise)
SMB-55 SMB2 signing
@@ -18,11 +18,11 @@
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2018 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.
*
@@ -37,95 +37,188 @@
* -------------+---------------+-------------+----------------
* Disabled | Fail | Not Signed | Not Signed
*/
#include <sys/uio.h>
-#include <smbsrv/smb_kproto.h>
-#include <smbsrv/smb_signing.h>
+#include <smbsrv/smb2_kproto.h>
+#include <smbsrv/smb_kcrypt.h>
#include <sys/isa_defs.h>
#include <sys/byteorder.h>
#include <sys/cmn_err.h>
#define SMB2_SIG_OFFS 48
#define SMB2_SIG_SIZE 16
+typedef struct mac_ops {
+ int (*mac_init)(smb_sign_ctx_t *, smb_crypto_mech_t *,
+ uint8_t *, size_t);
+ int (*mac_update)(smb_sign_ctx_t, uint8_t *, size_t);
+ int (*mac_final)(smb_sign_ctx_t, uint8_t *);
+} mac_ops_t;
+
+static int smb2_sign_calc_common(smb_request_t *, struct mbuf_chain *,
+ uint8_t *, mac_ops_t *);
+
/*
+ * SMB2 wrapper functions
+ */
+
+static mac_ops_t
+smb2_sign_ops = {
+ smb2_hmac_init,
+ smb2_hmac_update,
+ smb2_hmac_final
+};
+
+static int
+smb2_sign_calc(smb_request_t *sr,
+ struct mbuf_chain *mbc,
+ uint8_t *digest16)
+{
+ int rv;
+
+ rv = smb2_sign_calc_common(sr, mbc, digest16, &smb2_sign_ops);
+
+ return (rv);
+}
+
+/*
* Called during session destroy.
*/
static void
smb2_sign_fini(smb_session_t *s)
{
- smb_sign_mech_t *mech;
+ smb_crypto_mech_t *mech;
if ((mech = s->sign_mech) != NULL) {
kmem_free(mech, sizeof (*mech));
s->sign_mech = NULL;
}
}
/*
+ * SMB3 wrapper functions
+ */
+
+static struct mac_ops
+smb3_sign_ops = {
+ smb3_cmac_init,
+ smb3_cmac_update,
+ smb3_cmac_final
+};
+
+static int
+smb3_sign_calc(smb_request_t *sr,
+ struct mbuf_chain *mbc,
+ uint8_t *digest16)
+{
+ int rv;
+
+ rv = smb2_sign_calc_common(sr, mbc, digest16, &smb3_sign_ops);
+
+ return (rv);
+}
+
+/*
+ * Input to KDF for SigningKey.
+ * See comment for smb3_do_kdf for content.
+ */
+static uint8_t sign_kdf_input[29] = {
+ 0, 0, 0, 1, 'S', 'M', 'B', '2',
+ 'A', 'E', 'S', 'C', 'M', 'A', 'C', 0,
+ 0, 'S', 'm', 'b', 'S', 'i', 'g', 'n',
+ 0, 0, 0, 0, 0x80 };
+
+void
+smb2_sign_init_mech(smb_session_t *s)
+{
+ smb_crypto_mech_t *mech;
+ int (*get_mech)(smb_crypto_mech_t *);
+ int (*sign_calc)(smb_request_t *, struct mbuf_chain *, uint8_t *);
+ int rc;
+
+ if (s->sign_mech != NULL)
+ return;
+
+ if (s->dialect >= SMB_VERS_3_0) {
+ get_mech = smb3_cmac_getmech;
+ sign_calc = smb3_sign_calc;
+ } else {
+ get_mech = smb2_hmac_getmech;
+ sign_calc = smb2_sign_calc;
+ }
+
+ mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
+ rc = get_mech(mech);
+ if (rc != 0) {
+ kmem_free(mech, sizeof (*mech));
+ return;
+ }
+ s->sign_mech = mech;
+ s->sign_calc = sign_calc;
+ s->sign_fini = smb2_sign_fini;
+}
+
+/*
* smb2_sign_begin
+ * Handles both SMB2 & SMB3
*
* 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
+void
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;
+ sign_key->len = 0;
+
/*
* 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.
+ * Also, don't bother initializing if we don't have a mechanism.
*/
- if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0)
- return (0);
+ if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 ||
+ s->sign_mech == NULL)
+ return;
/*
- * 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;
-
+ if (s->dialect >= SMB_VERS_3_0) {
/*
+ * For SMB3, the signing key is a "KDF" hash of the
+ * session key.
+ */
+ if (smb3_do_kdf(sign_key->key, sign_kdf_input,
+ sizeof (sign_kdf_input), token->tkn_ssnkey.val,
+ token->tkn_ssnkey.len) != 0)
+ return;
+ sign_key->len = SMB3_KEYLEN;
+ } else {
+ /*
* 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
*/
+ sign_key->len = SMB2_KEYLEN;
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)
+ if ((s->srv_secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) != 0)
u->u_sign_flags |= SMB_SIGNING_ENABLED;
- if (s->secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED)
+ if ((s->srv_secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) != 0 ||
+ (s->cli_secmode & SMB2_NEGOTIATE_SIGNING_REQUIRED) != 0)
u->u_sign_flags |=
SMB_SIGNING_ENABLED | SMB_SIGNING_CHECK;
mutex_exit(&u->u_mutex);
/*
@@ -134,29 +227,27 @@
* 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
+ * smb2_sign_calc_common
*
* 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.
+ * The signature algorithm is to compute HMAC SHA256 or AES_CMAC
+ * 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)
+smb2_sign_calc_common(smb_request_t *sr, struct mbuf_chain *mbc,
+ uint8_t *digest, mac_ops_t *ops)
{
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;
@@ -165,11 +256,12 @@
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);
+ /* smb2_hmac_init or smb3_cmac_init */
+ rc = ops->mac_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
@@ -180,11 +272,12 @@
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)
+ /* smb2_hmac_update or smb3_cmac_update */
+ if ((rc = ops->mac_update(ctx, tmp_hdr, tlen)) != 0)
return (rc);
offset += tlen;
resid -= tlen;
/*
@@ -208,11 +301,12 @@
* (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);
+ /* smb2_hmac_update or smb3_cmac_update */
+ rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data + offset, tlen);
if (rc != 0)
return (rc);
resid -= tlen;
/*
@@ -223,21 +317,24 @@
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);
+ rc = ops->mac_update(ctx, (uint8_t *)mbuf->m_data, tlen);
if (rc != 0)
return (rc);
resid -= tlen;
}
/*
+ * smb2_hmac_final or smb3_cmac_final
* Note: digest is _always_ SMB2_SIG_SIZE,
* even if the mech uses a longer one.
+ *
+ * smb2_hmac_update or smb3_cmac_update
*/
- if ((rc = smb2_hmac_final(ctx, digest)) != 0)
+ if ((rc = ops->mac_final(ctx, digest)) != 0)
return (rc);
return (0);
}
@@ -258,29 +355,35 @@
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_session_t *s = sr->session;
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)
+ if (sr->smb2_ssnid == 0 || u == NULL)
return (0);
+ /* In case _sign_begin failed. */
+ if (s->sign_calc == NULL)
+ return (-1);
+
/* 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.
+ * smb2_sign_calc() or smb3_sign_calc()
*/
- if (smb2_sign_calc(sr, mbc, vfy_sig) != 0)
+ if (s->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);
}
@@ -298,28 +401,92 @@
void
smb2_sign_reply(smb_request_t *sr)
{
uint8_t reply_sig[SMB2_SIG_SIZE];
struct mbuf_chain tmp_mbc;
+ smb_session_t *s = sr->session;
smb_user_t *u = sr->uid_user;
int hdr_off, msg_len;
if (u == NULL)
return;
+ if (s->sign_calc == 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.
+ * smb2_sign_calc() or smb3_sign_calc()
*/
- if (smb2_sign_calc(sr, &tmp_mbc, reply_sig) != 0)
+ if (s->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);
+}
+
+/*
+ * Derive SMB3 key as described in [MS-SMB2] 3.1.4.2
+ * and [NIST SP800-108]
+ *
+ * r = 32, L = 128, PRF = HMAC-SHA256, key = (session key)
+ *
+ * Note that these describe pre-3.1.1 inputs.
+ *
+ * Session.SigningKey for binding a session:
+ * - Session.SessionKey as K1
+ * - label = SMB2AESCMAC (size 12)
+ * - context = SmbSign (size 8)
+ * Channel.SigningKey for for all other requests
+ * - if SMB2_SESSION_FLAG_BINDING, GSS key (in Session.SessionKey?) as K1;
+ * - otherwise, Session.SessionKey as K1
+ * - label = SMB2AESCMAC (size 12)
+ * - context = SmbSign (size 8)
+ * Session.ApplicationKey for ... (not sure what yet)
+ * - Session.SessionKey as K1
+ * - label = SMB2APP (size 8)
+ * - context = SmbRpc (size 7)
+ * Session.EncryptionKey for encrypting server messages
+ * - Session.SessionKey as K1
+ * - label = "SMB2AESCCM" (size 11)
+ * - context = "ServerOut" (size 10)
+ * Session.DecryptionKey for decrypting client requests
+ * - Session.SessionKey as K1
+ * - label = "SMB2AESCCM" (size 11)
+ * - context = "ServerIn " (size 10) (Note the space)
+ */
+
+int
+smb3_do_kdf(void *outbuf, void *input, size_t input_len,
+ uint8_t *key, uint32_t key_len)
+{
+ uint8_t digest32[SHA256_DIGEST_LENGTH];
+ smb_crypto_mech_t mech;
+ smb_sign_ctx_t hctx = 0;
+ int rc;
+
+ bzero(&mech, sizeof (mech));
+ if ((rc = smb2_hmac_getmech(&mech)) != 0)
+ return (rc);
+
+ /* Limit the SessionKey input to its maximum size (16 bytes) */
+ rc = smb2_hmac_init(&hctx, &mech, key, MIN(key_len, SMB2_KEYLEN));
+ if (rc != 0)
+ return (rc);
+
+ if ((rc = smb2_hmac_update(hctx, input, input_len)) != 0)
+ return (rc);
+
+ if ((rc = smb2_hmac_final(hctx, digest32)) != 0)
+ return (rc);
+
+ /* Output is first 16 bytes of digest. */
+ bcopy(digest32, outbuf, SMB3_KEYLEN);
+ return (0);
}