#include <smbsrv/smb2_kproto.h>
#include <smbsrv/smb_kcrypt.h>
#include <sys/random.h>
#include <sys/cmn_err.h>
#define SMB3_NONCE_OFFS 20
#define SMB3_SIG_OFFS 4
static uint64_t smb3_max_nonce = 0xffffffff00000000ULL;
void
smb3_encrypt_init_nonce(smb_user_t *user)
{
user->u_nonce_cnt = 0;
(void) random_get_pseudo_bytes(user->u_nonce_fixed,
sizeof (user->u_nonce_fixed));
(void) random_get_pseudo_bytes((uint8_t *)&user->u_salt,
sizeof (user->u_salt));
}
static int
smb3_encrypt_gen_nonce(smb_user_t *user, uint8_t *buf, size_t len)
{
uint64_t cnt;
cnt = atomic_inc_64_nv(&user->u_nonce_cnt);
if (cnt > smb3_max_nonce)
return (-1);
cnt ^= user->u_salt;
bcopy((uint8_t *)&cnt, buf, sizeof (cnt));
ASSERT(len <= 16);
ASSERT(len > sizeof (cnt));
bcopy(user->u_nonce_fixed, buf + sizeof (cnt), len - sizeof (cnt));
return (0);
}
int
smb3_encrypt_init_mech(smb_session_t *s)
{
smb_crypto_mech_t *mech;
int rc;
if (s->enc_mech != NULL)
return (0);
mech = kmem_zalloc(sizeof (*mech), KM_SLEEP);
switch (s->smb31_enc_cipherid) {
case SMB3_CIPHER_AES256_GCM:
case SMB3_CIPHER_AES128_GCM:
rc = smb3_aes_gcm_getmech(mech);
break;
case SMB3_CIPHER_AES256_CCM:
case SMB3_CIPHER_AES128_CCM:
rc = smb3_aes_ccm_getmech(mech);
break;
default:
rc = -1;
break;
}
if (rc != 0) {
kmem_free(mech, sizeof (*mech));
return (rc);
}
s->enc_mech = mech;
return (0);
}
void
smb3_encrypt_begin(smb_user_t *u, smb_token_t *token)
{
smb_session_t *s = u->u_session;
struct smb_key *enc_key = &u->u_enc_key;
struct smb_key *dec_key = &u->u_dec_key;
uint32_t derived_keylen, input_keylen;
u->u_encrypt = s->s_server->sv_cfg.skc_encrypt;
enc_key->len = 0;
dec_key->len = 0;
if (token->tkn_ssnkey.val == NULL || token->tkn_ssnkey.len == 0 ||
s->enc_mech == NULL)
return;
if (s->dialect >= SMB_VERS_3_11) {
if (s->smb31_enc_cipherid == SMB3_CIPHER_AES256_GCM ||
s->smb31_enc_cipherid == SMB3_CIPHER_AES256_CCM) {
derived_keylen = AES256_KEY_LENGTH;
input_keylen = token->tkn_ssnkey.len;
} else {
derived_keylen = AES128_KEY_LENGTH;
input_keylen = MIN(SMB2_SSN_KEYLEN,
token->tkn_ssnkey.len);
}
if (smb3_kdf(enc_key->key, derived_keylen,
token->tkn_ssnkey.val, input_keylen,
(uint8_t *)"SMBS2CCipherKey", 16,
u->u_preauth_hashval, SHA512_DIGEST_LENGTH) != 0)
return;
if (smb3_kdf(dec_key->key, derived_keylen,
token->tkn_ssnkey.val, input_keylen,
(uint8_t *)"SMBC2SCipherKey", 16,
u->u_preauth_hashval, SHA512_DIGEST_LENGTH) != 0)
return;
enc_key->len = derived_keylen;
dec_key->len = derived_keylen;
} else {
derived_keylen = AES128_KEY_LENGTH;
input_keylen = MIN(SMB2_SSN_KEYLEN, token->tkn_ssnkey.len);
if (smb3_kdf(enc_key->key, derived_keylen,
token->tkn_ssnkey.val, input_keylen,
(uint8_t *)"SMB2AESCCM", 11,
(uint8_t *)"ServerOut", 10) != 0)
return;
if (smb3_kdf(dec_key->key, derived_keylen,
token->tkn_ssnkey.val, input_keylen,
(uint8_t *)"SMB2AESCCM", 11,
(uint8_t *)"ServerIn ", 10) != 0)
return;
enc_key->len = derived_keylen;
dec_key->len = derived_keylen;
}
smb3_encrypt_init_nonce(u);
}
static int
smb3_decode_tform_header(smb_request_t *sr, struct mbuf_chain *mbc)
{
uint32_t protocolid;
uint16_t flags;
int rc;
rc = smb_mbc_decodef(
mbc, "l16c16cl..wq",
&protocolid,
sr->smb2_sig,
sr->th_nonce,
&sr->th_msglen,
&flags,
&sr->th_ssnid);
if (rc)
return (rc);
ASSERT3U(protocolid, ==, SMB3_ENCRYPTED_MAGIC);
if (flags != 1) {
#ifdef DEBUG
cmn_err(CE_NOTE, "flags field not 1: %x", flags);
#endif
return (-1);
}
return (rc);
}
static int
smb3_encode_tform_header(smb_request_t *sr, struct mbuf_chain *mbc)
{
int rc;
rc = smb_mbc_encodef(
mbc, "l16.16clwwq",
SMB3_ENCRYPTED_MAGIC,
sr->th_nonce,
sr->th_msglen,
0,
1,
sr->th_ssnid);
return (rc);
}
int
smb3_decrypt_sr(smb_request_t *sr,
struct mbuf_chain *in_mbc,
struct mbuf_chain *out_mbc)
{
smb_enc_ctx_t ctx;
uint8_t th_raw[SMB3_TFORM_HDR_SIZE];
uint8_t *authdata;
size_t authlen;
size_t cipherlen;
smb_vdb_t *in_vdb = NULL;
smb_vdb_t *out_vdb = NULL;
smb_session_t *s = sr->session;
smb_user_t *u;
struct smb_key *dec_key;
int cnt, rc;
boolean_t gcm;
size_t nonce_size;
uint_t keylen;
if (s->enc_mech == NULL)
return (SET_ERROR(-1));
switch (s->smb31_enc_cipherid) {
default:
ASSERT(0);
case SMB3_CIPHER_AES128_CCM:
gcm = B_FALSE;
nonce_size = SMB3_AES_CCM_NONCE_SIZE;
keylen = AES128_KEY_LENGTH;
break;
case SMB3_CIPHER_AES128_GCM:
gcm = B_TRUE;
nonce_size = SMB3_AES_GCM_NONCE_SIZE;
keylen = AES128_KEY_LENGTH;
break;
case SMB3_CIPHER_AES256_CCM:
gcm = B_FALSE;
nonce_size = SMB3_AES_CCM_NONCE_SIZE;
keylen = AES256_KEY_LENGTH;
break;
case SMB3_CIPHER_AES256_GCM:
gcm = B_TRUE;
nonce_size = SMB3_AES_GCM_NONCE_SIZE;
keylen = AES256_KEY_LENGTH;
break;
}
if (smb_mbc_peek(in_mbc, 0, "#c",
SMB3_TFORM_HDR_SIZE, th_raw) != 0) {
return (SET_ERROR(-2));
}
rc = smb3_decode_tform_header(sr, in_mbc);
if (rc != 0) {
return (SET_ERROR(-3));
}
m_adjust(in_mbc->chain, SMB3_TFORM_HDR_SIZE);
ASSERT(in_mbc->max_bytes > SMB3_TFORM_HDR_SIZE);
in_mbc->max_bytes -= SMB3_TFORM_HDR_SIZE;
in_mbc->chain_offset = 0;
if (sr->th_msglen < SMB2_HDR_SIZE ||
sr->th_msglen > in_mbc->max_bytes) {
return (SET_ERROR(-4));
}
cipherlen = sr->th_msglen + SMB2_SIG_SIZE;
u = smb_session_lookup_ssnid(s, sr->th_ssnid);
if (u == NULL) {
return (SET_ERROR(-5));
}
sr->th_sid_user = u;
dec_key = &u->u_dec_key;
if (dec_key->len != keylen) {
return (SET_ERROR(-6));
}
bzero(&ctx, sizeof (ctx));
ctx.mech = *((smb_crypto_mech_t *)s->enc_mech);
authdata = th_raw + SMB3_NONCE_OFFS;
authlen = SMB3_TFORM_HDR_SIZE - SMB3_NONCE_OFFS;
if (gcm) {
smb3_crypto_init_gcm_param(&ctx,
sr->th_nonce, nonce_size,
authdata, authlen);
} else {
smb3_crypto_init_ccm_param(&ctx,
sr->th_nonce, nonce_size,
authdata, authlen, cipherlen);
}
rc = smb3_decrypt_init(&ctx,
dec_key->key, dec_key->len);
if (rc != 0)
return (SET_ERROR(-7));
in_vdb = smb_get_vdb(sr);
in_vdb->vdb_uio.uio_resid = sr->th_msglen;
rc = smb_mbuf_mkuio(in_mbc->chain, &in_vdb->vdb_uio);
if (rc != 0)
return (SET_ERROR(-8));
cnt = in_vdb->vdb_uio.uio_iovcnt;
if ((cnt + 1) > MAX_IOVEC)
return (SET_ERROR(-9));
in_vdb->vdb_uio.uio_iov[cnt].iov_base = (void *)sr->smb2_sig;
in_vdb->vdb_uio.uio_iov[cnt].iov_len = SMB2_SIG_SIZE;
in_vdb->vdb_uio.uio_iovcnt = cnt + 1;
in_vdb->vdb_uio.uio_resid += SMB2_SIG_SIZE;
out_vdb = smb_get_vdb(sr);
out_vdb->vdb_uio.uio_resid = sr->th_msglen;
rc = smb_mbuf_mkuio(out_mbc->chain, &out_vdb->vdb_uio);
if (rc != 0)
return (SET_ERROR(-10));
rc = smb3_decrypt_uio(&ctx, &in_vdb->vdb_uio, &out_vdb->vdb_uio);
if (rc != 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "smb3_decrypt_uio failed");
#endif
return (SET_ERROR(-11));
}
return (rc);
}
int
smb3_encrypt_sr(smb_request_t *sr,
struct mbuf_chain *in_mbc,
struct mbuf_chain *out_mbc)
{
smb_enc_ctx_t ctx;
uint8_t th_raw[SMB3_TFORM_HDR_SIZE];
uint8_t *authdata;
size_t authlen;
smb_vdb_t *in_vdb = NULL;
smb_vdb_t *out_vdb = NULL;
smb_session_t *s = sr->session;
smb_user_t *u = sr->th_sid_user;
struct smb_key *enc_key = &u->u_enc_key;
int cnt, rc;
boolean_t gcm;
size_t nonce_size;
uint_t keylen;
VERIFY(u != NULL);
switch (s->smb31_enc_cipherid) {
default:
ASSERT(0);
case SMB3_CIPHER_AES128_CCM:
gcm = B_FALSE;
nonce_size = SMB3_AES_CCM_NONCE_SIZE;
keylen = AES128_KEY_LENGTH;
break;
case SMB3_CIPHER_AES128_GCM:
gcm = B_TRUE;
nonce_size = SMB3_AES_GCM_NONCE_SIZE;
keylen = AES128_KEY_LENGTH;
break;
case SMB3_CIPHER_AES256_CCM:
gcm = B_FALSE;
nonce_size = SMB3_AES_CCM_NONCE_SIZE;
keylen = AES256_KEY_LENGTH;
break;
case SMB3_CIPHER_AES256_GCM:
gcm = B_TRUE;
nonce_size = SMB3_AES_GCM_NONCE_SIZE;
keylen = AES256_KEY_LENGTH;
break;
}
if (s->enc_mech == NULL || enc_key->len != keylen) {
return (SET_ERROR(-1));
}
rc = smb3_encrypt_gen_nonce(u, sr->th_nonce, nonce_size);
if (rc != 0) {
cmn_err(CE_WARN, "ran out of nonces");
return (SET_ERROR(-2));
}
if (smb3_encode_tform_header(sr, out_mbc) != 0) {
cmn_err(CE_WARN, "couldn't encode transform header");
return (SET_ERROR(-3));
}
if (smb_mbc_peek(out_mbc, 0, "#c",
SMB3_TFORM_HDR_SIZE, th_raw) != 0)
return (SET_ERROR(-4));
bzero(&ctx, sizeof (ctx));
ctx.mech = *((smb_crypto_mech_t *)s->enc_mech);
authdata = th_raw + SMB3_NONCE_OFFS;
authlen = SMB3_TFORM_HDR_SIZE - SMB3_NONCE_OFFS;
if (gcm) {
smb3_crypto_init_gcm_param(&ctx,
sr->th_nonce, nonce_size,
authdata, authlen);
} else {
smb3_crypto_init_ccm_param(&ctx,
sr->th_nonce, nonce_size,
authdata, authlen, sr->th_msglen);
}
rc = smb3_encrypt_init(&ctx,
enc_key->key, enc_key->len);
if (rc != 0)
return (SET_ERROR(-5));
in_vdb = smb_get_vdb(sr);
in_vdb->vdb_uio.uio_resid = sr->th_msglen;
rc = smb_mbuf_mkuio(in_mbc->chain, &in_vdb->vdb_uio);
if (rc != 0)
return (SET_ERROR(-6));
out_vdb = smb_get_vdb(sr);
out_vdb->vdb_uio.uio_resid = sr->th_msglen;
rc = smb_mbuf_mkuio(out_mbc->chain->m_next, &out_vdb->vdb_uio);
if (rc != 0)
return (SET_ERROR(-7));
cnt = out_vdb->vdb_uio.uio_iovcnt;
if ((cnt + 1) > MAX_IOVEC)
return (SET_ERROR(-8));
out_vdb->vdb_uio.uio_iov[cnt].iov_base = (void *)sr->smb2_sig;
out_vdb->vdb_uio.uio_iov[cnt].iov_len = SMB2_SIG_SIZE;
out_vdb->vdb_uio.uio_iovcnt = cnt + 1;
out_vdb->vdb_uio.uio_resid += SMB2_SIG_SIZE;
rc = smb3_encrypt_uio(&ctx, &in_vdb->vdb_uio, &out_vdb->vdb_uio);
if (rc != 0) {
#ifdef DEBUG
cmn_err(CE_WARN, "smb3_encrypt_uio failed");
#endif
return (SET_ERROR(-9));
}
(void) smb_mbc_poke(out_mbc, SMB3_SIG_OFFS, "#c",
SMB2_SIG_SIZE, sr->smb2_sig);
return (rc);
}
void
smb3_encrypt_ssn_fini(smb_session_t *s)
{
smb_crypto_mech_t *mech;
if ((mech = s->enc_mech) != NULL) {
kmem_free(mech, sizeof (*mech));
s->enc_mech = NULL;
}
}