#include <sys/uio.h>
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_kcrypt.h>
#include <sys/isa_defs.h>
#include <sys/byteorder.h>
#define SMB_SIG_SIZE 8
#define SMB_SIG_OFFS 14
#define SMB_HDRLEN 32
#ifdef _LITTLE_ENDIAN
#define htolel(x) ((uint32_t)(x))
#else
#define htolel(x) BSWAP_32(x)
#endif
static int
smb_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc,
uint32_t seqnum, unsigned char *sig);
#ifdef DEBUG
uint32_t smb_sign_debug_search = 10;
static int
smb_sign_find_seqnum(
smb_request_t *sr,
struct mbuf_chain *mbc,
unsigned char *mac_sig,
unsigned char *sr_sig)
{
struct smb_sign *sign = &sr->session->signing;
uint32_t i, t;
for (i = 1; i < smb_sign_debug_search; i++) {
t = sr->sr_seqnum + i;
(void) smb_sign_calc(sr, mbc, t, mac_sig);
if (memcmp(mac_sig, sr_sig, SMB_SIG_SIZE) == 0) {
goto found;
}
t = sr->sr_seqnum - i;
(void) smb_sign_calc(sr, mbc, t, mac_sig);
if (memcmp(mac_sig, sr_sig, SMB_SIG_SIZE) == 0) {
goto found;
}
}
cmn_err(CE_WARN, "smb_sign_find_seqnum: failed after %d", i);
return (-1);
found:
cmn_err(CE_WARN, "smb_sign_find_seqnum: found! %d <- %d",
sign->seqnum, t);
sign->seqnum = t;
return (0);
}
#endif
static void
smb_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
smb_sign_begin(smb_request_t *sr, smb_token_t *token)
{
smb_arg_sessionsetup_t *sinfo = sr->sr_ssetup;
smb_session_t *session = sr->session;
struct smb_sign *sign = &session->signing;
smb_crypto_mech_t *mech;
int rc;
if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0)
return;
smb_rwx_rwenter(&session->s_lock, RW_WRITER);
if (sign->mackey != NULL) {
smb_rwx_rwexit(&session->s_lock);
return;
}
if (session->sign_mech == NULL) {
mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
rc = smb_md5_getmech(mech);
if (rc != 0) {
kmem_free(mech, sizeof (*mech));
smb_rwx_rwexit(&session->s_lock);
return;
}
session->sign_mech = mech;
session->sign_fini = smb_sign_fini;
}
sign->mackey_len = token->tkn_ssnkey.len + sinfo->ssi_ntpwlen;
sign->mackey = kmem_alloc(sign->mackey_len, KM_SLEEP);
bcopy(token->tkn_ssnkey.val, sign->mackey, token->tkn_ssnkey.len);
if (sinfo->ssi_ntpwlen > 0) {
bcopy(sinfo->ssi_ntpwd, sign->mackey + token->tkn_ssnkey.len,
sinfo->ssi_ntpwlen);
}
session->signing.seqnum = 0;
sr->sr_seqnum = 2;
sr->reply_seqnum = 1;
sign->flags = 0;
if (session->srv_secmode & NEGOTIATE_SECURITY_SIGNATURES_ENABLED) {
sign->flags |= SMB_SIGNING_ENABLED;
if (session->srv_secmode &
NEGOTIATE_SECURITY_SIGNATURES_REQUIRED)
sign->flags |= SMB_SIGNING_CHECK;
}
smb_rwx_rwexit(&session->s_lock);
}
static int
smb_sign_calc(smb_request_t *sr, struct mbuf_chain *mbc,
uint32_t seqnum, unsigned char *mac_sign)
{
smb_session_t *s = sr->session;
struct smb_sign *sign = &s->signing;
smb_sign_ctx_t ctx = 0;
uchar_t digest[MD5_DIGEST_LENGTH];
uchar_t *hdrp;
struct mbuf *mbuf = mbc->chain;
int offset = mbc->chain_offset;
int size;
int rc;
union {
struct {
uint8_t skip[2];
uint8_t raw[SMB_HDRLEN];
} r;
struct {
uint8_t skip[2];
uint8_t hdr[SMB_SIG_OFFS];
uint32_t sig[2];
uint16_t ids[5];
} s;
} smbhdr;
if (s->sign_mech == NULL || sign->mackey == NULL)
return (-1);
if ((rc = smb_md5_init(&ctx, s->sign_mech)) != 0)
return (rc);
rc = smb_md5_update(ctx, sign->mackey, sign->mackey_len);
if (rc != 0)
return (rc);
hdrp = (unsigned char *)&smbhdr.r.raw;
size = SMB_HDRLEN;
if (smb_mbc_peek(mbc, offset, "#c", size, hdrp) != 0)
return (-1);
smbhdr.s.sig[0] = htolel(seqnum);
smbhdr.s.sig[1] = 0;
rc = smb_md5_update(ctx, &smbhdr.r.raw, size);
if (rc != 0)
return (rc);
offset += size;
while (mbuf != NULL && (offset >= mbuf->m_len)) {
offset -= mbuf->m_len;
mbuf = mbuf->m_next;
}
if (mbuf != NULL && (size = (mbuf->m_len - offset)) > 0) {
rc = smb_md5_update(ctx, &mbuf->m_data[offset], size);
if (rc != 0)
return (rc);
offset = 0;
mbuf = mbuf->m_next;
}
while (mbuf != NULL) {
rc = smb_md5_update(ctx, mbuf->m_data, mbuf->m_len);
if (rc != 0)
return (rc);
mbuf = mbuf->m_next;
}
rc = smb_md5_final(ctx, digest);
if (rc == 0)
bcopy(digest, mac_sign, SMB_SIG_SIZE);
return (rc);
}
int
smb_sign_check_request(smb_request_t *sr)
{
struct mbuf_chain mbc = sr->command;
unsigned char mac_sig[SMB_SIG_SIZE];
if (sr->smb_com == SMB_COM_TRANSACTION_SECONDARY ||
sr->smb_com == SMB_COM_TRANSACTION2_SECONDARY ||
sr->smb_com == SMB_COM_NT_TRANSACT_SECONDARY)
return (0);
mbc.chain_offset = sr->orig_request_hdr;
if (smb_sign_calc(sr, &mbc, sr->sr_seqnum, mac_sig) != 0)
return (-1);
if (memcmp(mac_sig, sr->smb_sig, SMB_SIG_SIZE) == 0) {
return (0);
}
DTRACE_PROBE2(signature__mismatch, smb_request_t *, sr,
unsigned char *, mac_sig);
#ifdef DEBUG
if (smb_sign_debug) {
return (smb_sign_find_seqnum(sr, &mbc, mac_sig, sr->smb_sig));
}
#endif
return (-1);
}
int
smb_sign_check_secondary(smb_request_t *sr, unsigned int reply_seqnum)
{
struct mbuf_chain mbc = sr->command;
unsigned char mac_sig[SMB_SIG_SIZE];
int rtn = 0;
mbc.chain_offset = sr->orig_request_hdr;
if (smb_sign_calc(sr, &mbc, reply_seqnum - 1, mac_sig) != 0)
return (-1);
if (memcmp(mac_sig, sr->smb_sig, SMB_SIG_SIZE) != 0) {
cmn_err(CE_WARN, "SmbSignCheckSecond: bad signature");
rtn = -1;
}
sr->reply_seqnum = reply_seqnum;
return (rtn);
}
void
smb_sign_reply(smb_request_t *sr, struct mbuf_chain *reply)
{
struct mbuf_chain mbc;
unsigned char mac[SMB_SIG_SIZE];
if (reply)
mbc = *reply;
else
mbc = sr->reply;
mbc.chain_offset = 0;
if (smb_sign_calc(sr, &mbc, sr->reply_seqnum, mac) != 0) {
cmn_err(CE_WARN, "smb_sign_reply: error in smb_sign_calc");
return;
}
(void) smb_mbc_poke(&mbc, SMB_SIG_OFFS, "#c",
SMB_SIG_SIZE, mac);
}