#include <sys/uio.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
static void
smb2_sign_fini(smb_session_t *s)
{
smb_crypto_mech_t *mech;
if ((mech = s->sign_mech) != NULL) {
kmem_free(mech, sizeof (*mech));
s->sign_mech = NULL;
}
}
void
smb2_sign_init_mech(smb_session_t *s)
{
smb_crypto_mech_t *mech;
int (*get_mech)(smb_crypto_mech_t *);
int rc;
if (s->sign_mech != NULL)
return;
switch (s->smb31_sign_algid) {
case SMB3_SIGN_SHA256_HMAC:
get_mech = smb2_hmac_getmech;
break;
case SMB3_SIGN_AES128_CMAC:
get_mech = smb3_cmac_getmech;
break;
case SMB3_SIGN_AES128_GMAC:
get_mech = smb3_gmac_getmech;
break;
default:
return;
}
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_fini = smb2_sign_fini;
}
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;
sign_key->len = 0;
if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 ||
s->sign_mech == NULL)
return;
if (s->dialect >= SMB_VERS_3_0) {
uint32_t ssnkey_len = MIN(token->tkn_ssnkey.len, SMB2_KEYLEN);
if (s->dialect >= SMB_VERS_3_11) {
if (smb3_kdf(sign_key->key, SMB2_KEYLEN,
token->tkn_ssnkey.val, ssnkey_len,
(uint8_t *)"SMBSigningKey", 14,
u->u_preauth_hashval, SHA512_DIGEST_LENGTH)
!= 0)
return;
} else {
if (smb3_kdf(sign_key->key, SMB2_KEYLEN,
token->tkn_ssnkey.val, ssnkey_len,
(uint8_t *)"SMB2AESCMAC", 12,
(uint8_t *)"SmbSign", 8)
!= 0)
return;
}
sign_key->len = SMB2_KEYLEN;
} else {
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->srv_secmode & SMB2_NEGOTIATE_SIGNING_ENABLED) != 0)
u->u_sign_flags |= SMB_SIGNING_ENABLED;
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);
if (u->u_sign_flags & SMB_SIGNING_ENABLED)
sr->smb2_hdr_flags |= SMB2_FLAGS_SIGNED;
}
static void
smb2_construct_gmac_iv(smb_request_t *sr, boolean_t is_server_msg, uint8_t *iv)
{
uint64_t msgid = sr->smb2_messageid;
int i;
for (i = 0; i < 8; i++) {
iv[i] = msgid & 0xff;
msgid >>= 8;
}
iv[i] = 0;
if (is_server_msg)
iv[i] |= 1;
if (sr->smb2_cmd_code == SMB2_CANCEL)
iv[i] |= 2;
for (i++; i < SMB3_AES_GMAC_NONCE_SIZE; i++)
iv[i] = 0;
}
static int
smb2_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc,
uint8_t *digest, boolean_t is_server_msg)
{
uint8_t tmp_hdr[SMB2_HDR_SIZE];
uint8_t iv[SMB3_AES_GMAC_NONCE_SIZE];
smb_crypto_mech_t mech;
smb_crypto_param_t param;
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;
smb_vdb_t *in_vdb = NULL;
int offset, resid, tlen, rc;
int hdr_iov_cnt = 0;
if (s->sign_mech == NULL || sign_key->len == 0)
return (-1);
mech = *((smb_crypto_mech_t *)s->sign_mech);
switch (s->smb31_sign_algid) {
case SMB3_SIGN_AES128_CMAC:
break;
case SMB3_SIGN_SHA256_HMAC:
smb2_sign_init_hmac_param(&mech, ¶m, SMB2_SIG_SIZE);
break;
case SMB3_SIGN_AES128_GMAC:
smb2_construct_gmac_iv(sr, is_server_msg, iv);
smb3_sign_init_gmac_param(&mech, ¶m, iv);
break;
default:
ASSERT(0);
return (-1);
}
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);
in_vdb = smb_get_vdb(sr);
in_vdb->vdb_uio.uio_resid = resid;
in_vdb->vdb_uio.uio_iov[hdr_iov_cnt].iov_base = (char *)tmp_hdr;
in_vdb->vdb_uio.uio_iov[hdr_iov_cnt++].iov_len = tlen;
offset += tlen;
resid -= tlen;
mbuf = mbc->chain;
while (mbuf != NULL && (offset >= mbuf->m_len)) {
offset -= mbuf->m_len;
mbuf = mbuf->m_next;
}
if (mbuf == NULL)
return (-1);
tlen = mbuf->m_len - offset;
if (tlen > resid)
tlen = resid;
in_vdb->vdb_uio.uio_iov[hdr_iov_cnt].iov_base = mbuf->m_data + offset;
in_vdb->vdb_uio.uio_iov[hdr_iov_cnt++].iov_len = tlen;
rc = smb_mbuf_mkuio_cont(mbuf->m_next, &in_vdb->vdb_uio, hdr_iov_cnt);
if (rc != 0)
return (-1);
if ((rc = smb2_mac_uio(&mech, sign_key->key, sign_key->len,
&in_vdb->vdb_uio, digest)) != 0)
return (rc);
return (0);
}
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_session_t *s = sr->session;
smb_user_t *u = sr->uid_user;
int sig_off;
if (sr->smb2_ssnid == 0 || u == NULL)
return (0);
if (s->sign_fini == NULL)
return (-1);
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);
if (smb2_sign_calc(sr, mbc, vfy_sig, B_FALSE) != 0)
return (-1);
if (memcmp(vfy_sig, req_sig, SMB2_SIG_SIZE) == 0) {
return (0);
}
DTRACE_PROBE2(signature__mismatch, smb_request_t *, sr,
uint8_t *, vfy_sig);
return (-1);
}
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_fini == 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);
if (smb2_sign_calc(sr, &tmp_mbc, reply_sig, B_TRUE) != 0)
return;
hdr_off = sr->smb2_reply_hdr + SMB2_SIG_OFFS;
(void) smb_mbc_poke(&sr->reply, hdr_off, "#c",
SMB2_SIG_SIZE, reply_sig);
}