#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <libkern/crypto/md5.h>
#include <sys/smb_apple.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_rq.h>
#include <netsmb/smb_dev.h>
#include <netsmb/md4.h>
#include <fs/smbfs/smbfs_subr.h>
#include <netsmb/smb_converter.h>
#include <crypto/des.h>
#define SMBSIGLEN (8)
#define SMBSIGOFF (14)
#define SMBPASTSIG (SMBSIGOFF + SMBSIGLEN)
#define SMBFUDGESIGN 4
#ifdef SMB_DEBUG
#define SSNDEBUG 0
#endif // SMB_DEBUG
static u_char N8[] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
static void
smb_E(const u_char *key, u_char *data, u_char *dest)
{
des_key_schedule *ksp;
u_char kk[8];
kk[0] = key[0] & 0xfe;
kk[1] = key[0] << 7 | (key[1] >> 1 & 0xfe);
kk[2] = key[1] << 6 | (key[2] >> 2 & 0xfe);
kk[3] = key[2] << 5 | (key[3] >> 3 & 0xfe);
kk[4] = key[3] << 4 | (key[4] >> 4 & 0xfe);
kk[5] = key[4] << 3 | (key[5] >> 5 & 0xfe);
kk[6] = key[5] << 2 | (key[6] >> 6 & 0xfe);
kk[7] = key[6] << 1;
ksp = malloc(sizeof(des_key_schedule), M_SMBTEMP, M_WAITOK);
des_set_key((des_cblock*)kk, *ksp);
des_ecb_encrypt((des_cblock*)data, (des_cblock*)dest, *ksp, 1);
free(ksp, M_SMBTEMP);
}
int smb_lmresponse(const u_char *apwd, u_char *C8, u_char *RN)
{
u_char *p, *P14, *S21;
p = malloc(14 + 21, M_SMBTEMP, M_WAITOK);
bzero(p, 14 + 21);
P14 = p;
S21 = p + 14;
bcopy(apwd, P14, MIN(14, strnlen((char *)apwd, SMB_MAXPASSWORDLEN + 1)));
smb_E(P14, N8, S21);
smb_E(P14 + 7, N8, S21 + 8);
smb_E(S21, C8, RN);
smb_E(S21 + 7, C8, RN + 8);
smb_E(S21 + 14, C8, RN + 16);
free(p, M_SMBTEMP);
return 0;
}
static void smb_ntlmhash(const uint8_t *passwd, uint8_t *ntlmHash, size_t ntlmHash_len)
{
u_int16_t *unicode_passwd = NULL;
MD4_CTX md4;
size_t len;
bzero(ntlmHash, ntlmHash_len);
len = strnlen((char *)passwd, SMB_MAXPASSWORDLEN + 1);
unicode_passwd = malloc(len * sizeof(u_int16_t), M_SMBTEMP, M_WAITOK);
if (unicode_passwd == NULL)
return;
len = smb_strtouni(unicode_passwd, (char *)passwd, len, UTF_PRECOMPOSED|UTF_NO_NULL_TERM);
bzero(&md4, sizeof(md4));
MD4Init(&md4);
MD4Update(&md4, (uint8_t *)unicode_passwd, (unsigned int)len);
MD4Final(ntlmHash, &md4);
free(unicode_passwd, M_SMBTEMP);
#ifdef SSNDEBUG
smb_hexdump(__FUNCTION__, "ntlmHash = ", ntlmHash, 16);
#endif // SSNDEBUG
}
int smb_ntlmresponse(const u_char *apwd, u_char *C8, u_char *RN)
{
u_char S21[SMB_NTLM_LEN];
smb_ntlmhash(apwd, S21, sizeof(S21));
smb_E(S21, C8, RN);
smb_E(S21 + 7, C8, RN + 8);
smb_E(S21 + 14, C8, RN + 16);
return 0;
}
static void HMACT64(const u_char *key, size_t key_len, const u_char *data,
size_t data_len, u_char *digest)
{
MD5_CTX context;
u_char k_ipad[64];
u_char k_opad[64];
int i;
if (key_len > 64)
key_len = 64;
bzero(k_ipad, sizeof k_ipad);
bzero(k_opad, sizeof k_opad);
bcopy(key, k_ipad, key_len);
bcopy(key, k_opad, key_len);
for (i = 0; i < 64; i++) {
k_ipad[i] ^= 0x36;
k_opad[i] ^= 0x5c;
}
MD5Init(&context);
MD5Update(&context, k_ipad, 64);
MD5Update(&context, data, (unsigned int)data_len);
MD5Final(digest, &context);
MD5Init(&context);
MD5Update(&context, k_opad, 64);
MD5Update(&context, digest, 16);
MD5Final(digest, &context);
}
void smb_ntlmv2hash(uint8_t *ntlmv2Hash, const void * domain, const void * user,
const void * password)
{
uint8_t ntlmHash[SMB_NTLM_LEN];
u_int16_t *UserDomainUTF16 = NULL;
char *UserDomain = NULL;
size_t len;
DBG_ASSERT(domain);
DBG_ASSERT(user);
DBG_ASSERT(password);
smb_ntlmhash(password, ntlmHash, sizeof(ntlmHash));
len = strnlen(user, SMB_MAXUSERNAMELEN + 1) + strnlen(domain, SMB_MAXNetBIOSNAMELEN + 1);
UserDomain = malloc(len + 1, M_SMBTEMP, M_WAITOK);
if (UserDomain == NULL)
goto done;
strlcpy(UserDomain, user, len + 1);
strncat(UserDomain, domain, SMB_MAXNetBIOSNAMELEN + 1);
SMBDEBUG("UserDomain = %s\n", UserDomain);
UserDomainUTF16 = malloc(len * sizeof(u_int16_t), M_SMBTEMP, M_WAITOK);
if (UserDomainUTF16 == NULL)
goto done;
len = smb_strtouni(UserDomainUTF16, UserDomain, len, UTF_PRECOMPOSED | UTF_NO_NULL_TERM);
HMACT64(ntlmHash, SMB_NTLMV2_LEN, (const u_char *)UserDomainUTF16, len, ntlmv2Hash);
#ifdef SSNDEBUG
smb_hexdump(__FUNCTION__, "ntlmv2Hash = ", ntlmv2Hash, SMB_NTLMV2_LEN);
#endif // SSNDEBUG
done:
if (UserDomain)
free(UserDomain, M_SMBTEMP);
if (UserDomainUTF16)
free(UserDomainUTF16, M_SMBTEMP);
}
void *smb_lmv2_response(void *ntlmv2Hash, u_int64_t server_nonce, u_int64_t client_nonce, size_t *lmv2_len)
{
void *lmv2;
u_int64_t nonce[2];
nonce[0] = server_nonce;
nonce[1] = client_nonce;
*lmv2_len = SMB_NTLMV2_LEN + sizeof(nonce);
lmv2 = malloc(*lmv2_len, M_SMBTEMP, M_WAITOK);
if (lmv2 == NULL)
return NULL;
HMACT64(ntlmv2Hash, SMB_NTLMV2_LEN, (const u_char *)&nonce, sizeof(nonce), lmv2);
bcopy(&client_nonce, (u_int8_t *)lmv2 + SMB_NTLMV2_LEN, sizeof(client_nonce));
#ifdef SSNDEBUG
smb_hexdump(__FUNCTION__, "lmv2 = ", lmv2, (int32_t)*lmv2_len);
#endif // SSNDEBUG
*lmv2_len = SMB_LMV2_LEN;
return lmv2;
}
void smb_ntlmv2_response(void *ntlmv2Hash, void *ntlmv2, size_t ntlmv2_len, u_int64_t server_nonce)
{
uint8_t *blob = ntlmv2;
size_t blob_len = ntlmv2_len;
blob += 8;
blob_len -= 8;
bcopy((char *)&server_nonce, blob, sizeof(server_nonce));
HMACT64(ntlmv2Hash, SMB_NTLMV2_LEN, blob, blob_len, ntlmv2);
}
uint8_t *make_ntlmv2_blob(u_int64_t client_nonce, void *target_info, u_int16_t target_len, size_t *blob_len)
{
uint8_t *blob = NULL;
struct ntlmv2_blobhdr *blobhdr;
struct timespec now;
u_int64_t timestamp;
u_char *target_offset;
*blob_len = SMB_NTLMV2_LEN + sizeof(struct ntlmv2_blobhdr) + target_len;
blob = malloc(*blob_len, M_SMBTEMP, M_WAITOK);
if (blob == NULL) {
*blob_len = 0;
return NULL;
}
bzero(blob, *blob_len);
blobhdr = (struct ntlmv2_blobhdr *)(blob + SMB_NTLMV2_LEN);
blobhdr->header = htolel(0x00000101);
nanotime(&now);
smb_time_local2NT(&now, 0, ×tamp, 0);
blobhdr->timestamp = htoleq(timestamp);
blobhdr->client_nonce = client_nonce;
target_offset = (uint8_t *)&blobhdr->unknown1 + sizeof(blobhdr->unknown1);
if (target_info)
memcpy(target_offset, (void *)target_info, target_len);
#ifdef SSNDEBUG
smb_hexdump(__FUNCTION__, "ntlmv2 blob = ", blob, (int32_t)*blob_len);
#endif // SSNDEBUG
return (blob);
}
void smb_reset_sig(struct smb_vc *vcp)
{
if (vcp->vc_mackey != NULL)
free(vcp->vc_mackey, M_SMBTEMP);
vcp->vc_mackey = NULL;
vcp->vc_mackeylen = 0;
vcp->vc_seqno = 0;
}
void smb_calcv2mackey(struct smb_vc *vcp, void *ntlmv2Hash, void *ntlmv2,
void *resp, size_t resplen)
{
if (!(vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE))
return;
SMBDEBUG("(fyi) packet signing with ntlmv2\n");
smb_reset_sig(vcp);
vcp->vc_mackeylen = (u_int32_t)(16 + resplen);
vcp->vc_mackey = malloc(vcp->vc_mackeylen, M_SMBTEMP, M_WAITOK);
if (vcp->vc_mackey == NULL) {
SMBDEBUG("malloc of vc_mackey failed?\n");
return;
}
HMACT64(ntlmv2Hash, 16, ntlmv2, (size_t)16, vcp->vc_mackey);
if (resp)
bcopy(resp, vcp->vc_mackey + 16, (int)resplen);
#if SSNDEBUG
smb_hexdump(__FUNCTION__, "setting vc_mackey = ", vcp->vc_mackey, vcp->vc_mackeylen);
#endif
}
void smb_calcmackey(struct smb_vc *vcp, void *resp, size_t resplen)
{
MD4_CTX md4;
u_char S16[16];
if (!(vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE))
return;
SMBDEBUG("(fyi) packet signing with ntlm(v1)\n");
smb_reset_sig(vcp);
vcp->vc_mackeylen = (u_int32_t)(16 + resplen);
vcp->vc_mackey = malloc(vcp->vc_mackeylen, M_SMBTEMP, M_WAITOK);
if (vcp->vc_mackey == NULL) {
SMBDEBUG("malloc of vc_mackey failed?\n");
return;
}
smb_ntlmhash((const uint8_t *)smb_vc_getpass(vcp), (uint8_t *)S16, sizeof(S16));
MD4Init(&md4);
MD4Update(&md4, S16, 16);
MD4Final(vcp->vc_mackey, &md4);
if (resp)
bcopy(resp, vcp->vc_mackey+16, resplen);
#if SSNDEBUG
smb_hexdump(__FUNCTION__, "setting vc_mackey = ", vcp->vc_mackey, vcp->vc_mackeylen);
#endif
return;
}
int
smb_rq_sign(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
struct mbchain *mbp;
mbuf_t mb;
MD5_CTX md5;
u_char digest[16];
KASSERT(vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE,
("signatures not enabled"));
if ((vcp->vc_mackey == NULL) || (rqp->sr_cmd == SMB_COM_SESSION_SETUP_ANDX)) {
rqp->sr_seqno = vcp->vc_seqno++;
rqp->sr_rseqno = vcp->vc_seqno++;
if (rqp->sr_rqsig)
bcopy("BSRSPLY ", rqp->sr_rqsig, 8);
return (0);
}
if (rqp->sr_t2 == NULL ||
(rqp->sr_t2->t2_flags & SMBT2_SECONDARY) == 0) {
rqp->sr_seqno = vcp->vc_seqno++;
rqp->sr_rseqno = vcp->vc_seqno++;
} else {
KASSERT(rqp->sr_t2 == NULL ||
(rqp->sr_t2->t2_flags & SMBT2_SECONDARY) == 0 ||
rqp->sr_t2->t2_rq == rqp,
("sec t2 rq not using same smb_rq"));
}
if (rqp->sr_rqsig) {
*(u_int32_t *)rqp->sr_rqsig = htolel(rqp->sr_seqno);
*(u_int32_t *)(rqp->sr_rqsig + 4) = 0;
}
smb_rq_getrequest(rqp, &mbp);
MD5Init(&md5);
MD5Update(&md5, vcp->vc_mackey, vcp->vc_mackeylen);
for (mb = mbp->mb_top; mb != NULL; mb = mbuf_next(mb))
MD5Update(&md5, mbuf_data(mb), (unsigned int)mbuf_len(mb));
MD5Final(digest, &md5);
if (rqp->sr_rqsig)
bcopy(digest, rqp->sr_rqsig, 8);
return (0);
}
static int
smb_verify(struct smb_rq *rqp, u_int32_t seqno)
{
struct smb_vc *vcp = rqp->sr_vc;
struct mdchain *mdp;
mbuf_t mb;
u_char sigbuf[SMBSIGLEN];
MD5_CTX md5;
u_char digest[16];
smb_rq_getreply(rqp, &mdp);
mb = mdp->md_top;
KASSERT(mbuf_len(mb) >= SMB_HDRLEN, ("forgot to mbuf_pullup"));
MD5Init(&md5);
MD5Update(&md5, vcp->vc_mackey, vcp->vc_mackeylen);
MD5Update(&md5, mbuf_data(mb), SMBSIGOFF);
*(u_int32_t *)sigbuf = htolel(seqno);
*(u_int32_t *)(sigbuf + 4) = 0;
MD5Update(&md5, sigbuf, SMBSIGLEN);
MD5Update(&md5, (u_int8_t *)mbuf_data(mb) + SMBPASTSIG,
(unsigned int)(mbuf_len(mb) - SMBPASTSIG));
for (mb = mbuf_next(mb); mb != NULL; mb = mbuf_next(mb))
if (mbuf_len(mb))
MD5Update(&md5, mbuf_data(mb), (unsigned int)mbuf_len(mb));
MD5Final(digest, &md5);
return (bcmp((u_int8_t *)mbuf_data(mdp->md_top) + SMBSIGOFF, digest, SMBSIGLEN));
}
int
smb_rq_verify(struct smb_rq *rqp)
{
struct smb_vc *vcp = rqp->sr_vc;
int32_t fudge;
if (!(vcp->vc_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE)) {
SMBWARNING("signatures not enabled!\n");
return (0);
}
if ((vcp->vc_mackey == NULL) || (rqp->sr_cmd == SMB_COM_SESSION_SETUP_ANDX))
return (0);
if ((vcp->vc_flags & SMBV_ANONYMOUS_ACCESS) == SMBV_ANONYMOUS_ACCESS)
return (0);
if (smb_verify(rqp, rqp->sr_rseqno) == 0)
return (0);
if (smbfs_loglevel) {
for (fudge = -SMBFUDGESIGN; fudge <= SMBFUDGESIGN; fudge++)
if (fudge == 0)
continue;
else if (smb_verify(rqp, rqp->sr_rseqno + fudge) == 0)
break;
if (fudge <= SMBFUDGESIGN)
SMBERROR("sr_rseqno=%d, but %d would have worked\n",
rqp->sr_rseqno, rqp->sr_rseqno + fudge);
else
SMBERROR("sr_rseqno=%d\n", rqp->sr_rseqno);
}
return (EAUTH);
}