#include <sys/md5.h>
#include <sys/debug.h>
#include <strings.h>
#include <stdio.h>
#include <smbsrv/netrauth.h>
#include <smbsrv/string.h>
#include <smbsrv/libsmb.h>
#include <libmlsvc.h>
#include <resolv.h>
enum nl_token_type {
NL_AUTH_REQUEST = 0x00000000,
NL_AUTH_RESPONSE = 0x00000001
};
#define NL_DOMAIN_NB_OEM_FLAG 0x00000001
#define NL_COMPUTER_NB_OEM_FLAG 0x00000002
#define NL_DOMAIN_DNS_COMPRESSED_FLAG 0x00000004
#define NL_HOST_DNS_COMPRESSED_FLAG 0x00000008
#define NL_COMPUTER_NB_COMPRESSED_FLAG 0x00000010
#define NL_DOMAIN_FLAGS \
(NL_DOMAIN_NB_OEM_FLAG|NL_DOMAIN_DNS_COMPRESSED_FLAG)
#define NL_COMPUTER_FLAGS \
(NL_COMPUTER_NB_OEM_FLAG| \
NL_HOST_DNS_COMPRESSED_FLAG| \
NL_COMPUTER_NB_COMPRESSED_FLAG)
#define MD_DIGEST_LEN 16
typedef struct nl_auth_message {
uint32_t nam_type;
uint32_t nam_flags;
uchar_t nam_str[1];
} nl_auth_message_t;
#define NL_AUTH_CONFOUNDER_SIZE 8
typedef struct nl_auth_sig {
uint16_t nas_sig_alg;
uint16_t nas_seal_alg;
uint16_t nas_pad;
uint16_t nas_flags;
uchar_t nas_seqnum[8];
uchar_t nas_sig[8];
uchar_t nas_confounder[NL_AUTH_CONFOUNDER_SIZE];
} nl_auth_sig_t;
#define NL_AUTH_SIG_SIGN_SIZE 24
#define NL_AUTH_SIG_SEAL_SIZE 32
void
netr_show_msg(nl_auth_message_t *nam, ndr_stream_t *nds)
{
ndo_printf(nds, NULL, "nl_auth_message: type=0x%x flags=0x%x");
}
void
netr_show_sig(nl_auth_sig_t *nas, ndr_stream_t *nds)
{
ndo_printf(nds, NULL, "nl_auth_sig: SignatureAlg=0x%x SealAlg=0x%x "
"pad=0x%x flags=0x%x SequenceNumber=%llu Signature=0x%x "
"Confounder=0x%x",
nas->nas_sig_alg, nas->nas_seal_alg, nas->nas_pad,
nas->nas_flags, *(uint64_t *)nas->nas_seqnum,
*(uint64_t *)nas->nas_sig, *(uint64_t *)nas->nas_confounder);
}
int
netr_ssp_init(void *arg, ndr_xa_t *mxa)
{
netr_info_t *auth = arg;
ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
nl_auth_message_t *nam;
size_t domain_len, comp_len, len;
int slen;
uchar_t *dnptrs[3], **dnlastptr;
domain_len = smb_sbequiv_strlen(auth->nb_domain);
comp_len = smb_sbequiv_strlen(auth->hostname);
len = domain_len + 1 + comp_len + 1 +
strlen(auth->hostname) * 2 + strlen(auth->fqdn_domain) * 2;
hdr->auth_length = 0;
nam = NDR_MALLOC(mxa, len);
if (nam == NULL)
return (NDR_DRC_FAULT_SEC_OUT_OF_MEMORY);
nam->nam_type = NL_AUTH_REQUEST;
nam->nam_flags = 0;
if (domain_len != -1) {
slen = smb_mbstooem(nam->nam_str, auth->nb_domain, domain_len);
if (slen >= 0) {
hdr->auth_length += slen + 1;
nam->nam_str[hdr->auth_length - 1] = '\0';
nam->nam_flags |= NL_DOMAIN_NB_OEM_FLAG;
}
}
if (comp_len != -1) {
slen = smb_mbstooem(nam->nam_str + hdr->auth_length,
auth->hostname, comp_len);
if (slen >= 0) {
hdr->auth_length += slen + 1;
nam->nam_str[hdr->auth_length - 1] = '\0';
nam->nam_flags |= NL_COMPUTER_NB_OEM_FLAG;
}
}
dnptrs[0] = NULL;
dnlastptr = &dnptrs[sizeof (dnptrs) / sizeof (dnptrs[0])];
slen = dn_comp(auth->fqdn_domain, nam->nam_str + hdr->auth_length,
len - hdr->auth_length, dnptrs, dnlastptr);
if (slen >= 0) {
hdr->auth_length += slen;
nam->nam_str[hdr->auth_length] = '\0';
nam->nam_flags |= NL_DOMAIN_DNS_COMPRESSED_FLAG;
}
slen = dn_comp(auth->hostname, nam->nam_str + hdr->auth_length,
len - hdr->auth_length, dnptrs, dnlastptr);
if (slen >= 0) {
hdr->auth_length += slen;
nam->nam_str[hdr->auth_length] = '\0';
nam->nam_flags |= NL_COMPUTER_NB_COMPRESSED_FLAG;
}
if ((nam->nam_flags & NL_DOMAIN_FLAGS) == 0 ||
(nam->nam_flags & NL_COMPUTER_FLAGS) == 0)
return (NDR_DRC_FAULT_SEC_ENCODE_FAILED);
mxa->send_auth.auth_value = (void *)nam;
hdr->auth_length += sizeof (nam->nam_flags) + sizeof (nam->nam_type);
return (0);
}
int
netr_ssp_recv(void *arg, ndr_xa_t *mxa)
{
netr_info_t *auth = arg;
ndr_common_header_t *ahdr = &mxa->recv_hdr.common_hdr;
ndr_sec_t *ack_secp = &mxa->recv_auth;
nl_auth_message_t *nam;
int rc;
nam = (nl_auth_message_t *)ack_secp->auth_value;
if (ahdr->auth_length < 12) {
rc = NDR_DRC_FAULT_SEC_AUTH_LENGTH_INVALID;
goto errout;
}
if (nam->nam_type != NL_AUTH_RESPONSE) {
rc = NDR_DRC_FAULT_SEC_META_INVALID;
goto errout;
}
auth->clh_seqnum = 0;
return (NDR_DRC_OK);
errout:
netr_show_msg(nam, &mxa->recv_nds);
return (rc);
}
#define CLS_BYTE(n, seqnum) ((seqnum >> (8 * (n))) & 0xff)
int
netr_ssp_derive_key(uchar_t *input_key, uint_t key_len,
uchar_t *data, uint_t data_len, uchar_t *out_key)
{
int rc;
uint32_t zeroes = 0;
rc = smb_auth_hmac_md5((uchar_t *)&zeroes, 4,
input_key, key_len,
out_key);
if (rc == 0)
rc = smb_auth_hmac_md5(data, data_len,
out_key, MD_DIGEST_LEN,
out_key);
return (rc);
}
int
netr_ssp_make_token(netr_info_t *auth, ndr_stream_t *nds, nl_auth_sig_t *nas,
uchar_t *confounder)
{
uint32_t zeroes = 0;
MD5_CTX md5h;
BYTE local_sig[MD_DIGEST_LEN];
BYTE enc_key[MD_DIGEST_LEN];
uchar_t *pdu_body = nds->pdu_base_addr + nds->pdu_body_offset;
nas->nas_sig_alg = 0x0077;
if (confounder != NULL)
nas->nas_seal_alg = 0x007A;
else
nas->nas_seal_alg = 0xffff;
nas->nas_pad = 0xffff;
nas->nas_flags = 0;
nas->nas_seqnum[0] = CLS_BYTE(3, auth->clh_seqnum);
nas->nas_seqnum[1] = CLS_BYTE(2, auth->clh_seqnum);
nas->nas_seqnum[2] = CLS_BYTE(1, auth->clh_seqnum);
nas->nas_seqnum[3] = CLS_BYTE(0, auth->clh_seqnum);
nas->nas_seqnum[4] = CLS_BYTE(7, auth->clh_seqnum) | 0x80;
nas->nas_seqnum[5] = CLS_BYTE(6, auth->clh_seqnum);
nas->nas_seqnum[6] = CLS_BYTE(5, auth->clh_seqnum);
nas->nas_seqnum[7] = CLS_BYTE(4, auth->clh_seqnum);
auth->clh_seqnum++;
MD5Init(&md5h);
MD5Update(&md5h, (uchar_t *)&zeroes, 4);
MD5Update(&md5h, (uchar_t *)nas, 8);
if (confounder != NULL)
MD5Update(&md5h, confounder, NL_AUTH_CONFOUNDER_SIZE);
MD5Update(&md5h, pdu_body, nds->pdu_body_size);
MD5Final(local_sig, &md5h);
if (smb_auth_hmac_md5(local_sig, sizeof (local_sig),
auth->session_key.key, auth->session_key.len,
local_sig) != 0)
return (NDR_DRC_FAULT_SEC_SSP_FAILED);
bcopy(local_sig, nas->nas_sig, 8);
if (confounder != NULL) {
int rc;
rc = netr_ssp_derive_key(
auth->rpc_seal_key.key, auth->rpc_seal_key.len,
(uchar_t *)nas->nas_seqnum, sizeof (nas->nas_seqnum),
enc_key);
if (rc != 0)
goto errout;
if (smb_auth_RC4(nas->nas_confounder,
sizeof (nas->nas_confounder),
enc_key, sizeof (enc_key),
confounder, NL_AUTH_CONFOUNDER_SIZE) != 0)
goto errout;
if (smb_auth_RC4(pdu_body, nds->pdu_body_size,
enc_key, sizeof (enc_key),
pdu_body, nds->pdu_body_size) != 0)
goto errout;
}
if (netr_ssp_derive_key(auth->session_key.key, auth->session_key.len,
(uchar_t *)nas->nas_sig, sizeof (nas->nas_sig), enc_key) != 0)
goto errout;
if (smb_auth_RC4(nas->nas_seqnum, sizeof (nas->nas_seqnum),
enc_key, sizeof (enc_key),
nas->nas_seqnum, sizeof (nas->nas_seqnum)) != 0)
goto errout;
explicit_bzero(enc_key, sizeof (enc_key));
return (NDR_DRC_OK);
errout:
explicit_bzero(enc_key, sizeof (enc_key));
return (NDR_DRC_FAULT_SEC_SSP_FAILED);
}
int
netr_ssp_check_token(netr_info_t *auth, ndr_stream_t *nds, nl_auth_sig_t *nas,
boolean_t verify_resp, boolean_t has_confounder)
{
uint32_t zeroes = 0;
MD5_CTX md5h;
BYTE local_sig[MD_DIGEST_LEN];
BYTE dec_key[MD_DIGEST_LEN];
BYTE local_seqnum[8];
uchar_t confounder[NL_AUTH_CONFOUNDER_SIZE];
uchar_t *pdu_body = nds->pdu_base_addr + nds->pdu_body_offset;
int rc;
boolean_t seqnum_bumped = B_FALSE;
if (nas->nas_sig_alg != 0x0077 ||
nas->nas_seal_alg != (has_confounder ? 0x007A : 0xffff) ||
nas->nas_pad != 0xffff) {
rc = NDR_DRC_FAULT_SEC_META_INVALID;
goto errout;
}
if (netr_ssp_derive_key(auth->session_key.key, auth->session_key.len,
(uchar_t *)nas->nas_sig, sizeof (nas->nas_sig), dec_key) != 0) {
rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
goto errout;
}
if (smb_auth_RC4(nas->nas_seqnum, sizeof (nas->nas_seqnum),
dec_key, sizeof (dec_key),
nas->nas_seqnum, sizeof (nas->nas_seqnum)) != 0) {
rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
goto errout;
}
local_seqnum[0] = CLS_BYTE(3, auth->clh_seqnum);
local_seqnum[1] = CLS_BYTE(2, auth->clh_seqnum);
local_seqnum[2] = CLS_BYTE(1, auth->clh_seqnum);
local_seqnum[3] = CLS_BYTE(0, auth->clh_seqnum);
local_seqnum[4] = CLS_BYTE(7, auth->clh_seqnum);
local_seqnum[5] = CLS_BYTE(6, auth->clh_seqnum);
local_seqnum[6] = CLS_BYTE(5, auth->clh_seqnum);
local_seqnum[7] = CLS_BYTE(4, auth->clh_seqnum);
if (bcmp(local_seqnum, nas->nas_seqnum, sizeof (local_seqnum)) != 0) {
ndo_printf(nds, NULL, "CalculatedSeqnum: %llu "
"DecryptedSeqnum: %llu",
*(uint64_t *)local_seqnum, *(uint64_t *)nas->nas_seqnum);
rc = NDR_DRC_FAULT_SEC_SEQNUM_INVALID;
goto errout;
}
auth->clh_seqnum++;
seqnum_bumped = B_TRUE;
if (has_confounder) {
if (netr_ssp_derive_key(
auth->rpc_seal_key.key, auth->rpc_seal_key.len,
(uchar_t *)local_seqnum, sizeof (local_seqnum),
dec_key) != 0) {
rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
goto errout;
}
if (smb_auth_RC4(confounder, NL_AUTH_CONFOUNDER_SIZE,
dec_key, sizeof (dec_key),
nas->nas_confounder, sizeof (nas->nas_confounder)) != 0) {
rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
goto errout;
}
if (smb_auth_RC4(pdu_body, nds->pdu_body_size,
dec_key, sizeof (dec_key),
pdu_body, nds->pdu_body_size) != 0) {
rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
goto errout;
}
}
MD5Init(&md5h);
MD5Update(&md5h, (uchar_t *)&zeroes, 4);
MD5Update(&md5h, (uchar_t *)nas, 8);
if (has_confounder)
MD5Update(&md5h, confounder, NL_AUTH_CONFOUNDER_SIZE);
MD5Update(&md5h, pdu_body, nds->pdu_body_size);
MD5Final(local_sig, &md5h);
if (smb_auth_hmac_md5(local_sig, sizeof (local_sig),
auth->session_key.key, auth->session_key.len,
local_sig) != 0) {
rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
goto errout;
}
if (bcmp(local_sig, nas->nas_sig, 8) != 0) {
ndo_printf(nds, NULL, "CalculatedSig: %llu "
"PacketSig: %llu",
*(uint64_t *)local_sig, *(uint64_t *)nas->nas_sig);
rc = NDR_DRC_FAULT_SEC_SIG_INVALID;
goto errout;
}
explicit_bzero(dec_key, sizeof (dec_key));
return (NDR_DRC_OK);
errout:
netr_show_sig(nas, nds);
explicit_bzero(dec_key, sizeof (dec_key));
if (!verify_resp) {
if (!seqnum_bumped)
auth->clh_seqnum++;
return (NDR_DRC_OK);
}
return (rc);
}
int
netr_ssp_sign(void *arg, ndr_xa_t *mxa)
{
ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
nl_auth_sig_t *nas;
hdr->auth_length = NL_AUTH_SIG_SIGN_SIZE;
CTASSERT(NL_AUTH_SIG_SIGN_SIZE ==
(offsetof(nl_auth_sig_t, nas_sig) + sizeof (nas->nas_sig)));
nas = NDR_MALLOC(mxa, sizeof (*nas));
if (nas == NULL)
return (NDR_DRC_FAULT_SEC_OUT_OF_MEMORY);
mxa->send_auth.auth_value = (void *)nas;
return (netr_ssp_make_token(arg, &mxa->send_nds, nas, NULL));
}
int
netr_ssp_verify(void *arg, ndr_xa_t *mxa, boolean_t verify_resp)
{
ndr_common_header_t *hdr = &mxa->recv_hdr.common_hdr;
ndr_sec_t *secp = &mxa->recv_auth;
nl_auth_sig_t *nas = (nl_auth_sig_t *)secp->auth_value;
if (hdr->auth_length < NL_AUTH_SIG_SIGN_SIZE)
return (NDR_DRC_FAULT_SEC_AUTH_LENGTH_INVALID);
return (netr_ssp_check_token(arg, &mxa->recv_nds, nas, verify_resp,
B_FALSE));
}
int
netr_ssp_encrypt(void *arg, ndr_xa_t *mxa)
{
uchar_t confounder[NL_AUTH_CONFOUNDER_SIZE];
ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
nl_auth_sig_t *nas;
hdr->auth_length = NL_AUTH_SIG_SEAL_SIZE;
CTASSERT(NL_AUTH_SIG_SEAL_SIZE == sizeof (*nas));
nas = NDR_MALLOC(mxa, sizeof (*nas));
if (nas == NULL)
return (NDR_DRC_FAULT_SEC_OUT_OF_MEMORY);
mxa->send_auth.auth_value = (void *)nas;
arc4random_buf(confounder, NL_AUTH_CONFOUNDER_SIZE);
return (netr_ssp_make_token(arg, &mxa->send_nds, nas, confounder));
}
int
netr_ssp_decrypt(void *arg, ndr_xa_t *mxa, boolean_t verify_resp)
{
ndr_common_header_t *hdr = &mxa->recv_hdr.common_hdr;
ndr_sec_t *secp = &mxa->recv_auth;
nl_auth_sig_t *nas = (nl_auth_sig_t *)secp->auth_value;
if (hdr->auth_length < NL_AUTH_SIG_SEAL_SIZE)
return (NDR_DRC_FAULT_SEC_AUTH_LENGTH_INVALID);
return (netr_ssp_check_token(arg, &mxa->recv_nds, nas, verify_resp,
B_TRUE));
}
extern struct netr_info netr_global_info;
ndr_auth_ctx_t netr_ssp_ctx = {
.auth_ops = {
.nao_init = netr_ssp_init,
.nao_recv = netr_ssp_recv,
.nao_sign = netr_ssp_sign,
.nao_verify = netr_ssp_verify,
.nao_encrypt = netr_ssp_encrypt,
.nao_decrypt = netr_ssp_decrypt
},
.auth_ctx = &netr_global_info,
.auth_context_id = 0,
.auth_type = NDR_C_AUTHN_GSS_NETLOGON,
.auth_level = NDR_C_AUTHN_LEVEL_PKT_PRIVACY,
.auth_verify_resp = B_TRUE
};