factotum: add ntlmv2 (mschap2) client auth for cifs
This commit is contained in:
parent
71dbddef16
commit
3c8db40def
4 changed files with 280 additions and 324 deletions
|
@ -19,27 +19,29 @@
|
||||||
enum {
|
enum {
|
||||||
ChapChallen = 8,
|
ChapChallen = 8,
|
||||||
ChapResplen = 16,
|
ChapResplen = 16,
|
||||||
MSchapResplen = 24,
|
|
||||||
|
/* Microsoft auth constants */
|
||||||
|
MShashlen = 16,
|
||||||
|
MSchallen = 8,
|
||||||
|
MSresplen = 24,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int dochal(State*);
|
static int dochal(State *s);
|
||||||
static int doreply(State*, void*, int);
|
static int doreply(State *s, uchar *reply, int nreply);
|
||||||
static void doLMchap(char *, uchar [ChapChallen], uchar [MSchapResplen]);
|
static int dochap(char *passwd, int id, char chal[ChapChallen], uchar *resp, int resplen);
|
||||||
static void doNTchap(char *, uchar [ChapChallen], uchar [MSchapResplen]);
|
static int domschap(char *passwd, uchar chal[MSchallen], uchar *resp, int resplen);
|
||||||
static void dochap(char *, int, char [ChapChallen], uchar [ChapResplen]);
|
static int domschap2(char *passwd, char *user, char *dom, uchar chal[MSchallen], uchar *resp, int resplen);
|
||||||
|
|
||||||
|
|
||||||
struct State
|
struct State
|
||||||
{
|
{
|
||||||
char *protoname;
|
|
||||||
int astype;
|
int astype;
|
||||||
int asfd;
|
int asfd;
|
||||||
Key *key;
|
Key *key;
|
||||||
Ticket t;
|
Ticket t;
|
||||||
Ticketreq tr;
|
Ticketreq tr;
|
||||||
char chal[ChapChallen];
|
char chal[ChapChallen];
|
||||||
MSchapreply mcr;
|
int nresp;
|
||||||
char cr[ChapResplen];
|
uchar resp[4096];
|
||||||
char err[ERRMAX];
|
char err[ERRMAX];
|
||||||
char user[64];
|
char user[64];
|
||||||
uchar secret[16]; /* for mschap */
|
uchar secret[16]; /* for mschap */
|
||||||
|
@ -82,17 +84,16 @@ chapinit(Proto *p, Fsstate *fss)
|
||||||
return failure(fss, nil);
|
return failure(fss, nil);
|
||||||
|
|
||||||
s = emalloc(sizeof *s);
|
s = emalloc(sizeof *s);
|
||||||
|
s->nresp = 0;
|
||||||
|
s->nsecret = 0;
|
||||||
fss->phasename = phasenames;
|
fss->phasename = phasenames;
|
||||||
fss->maxphase = Maxphase;
|
fss->maxphase = Maxphase;
|
||||||
s->asfd = -1;
|
s->asfd = -1;
|
||||||
if(p == &chap){
|
if(p == &mschap || p == &mschap2){
|
||||||
s->astype = AuthChap;
|
|
||||||
s->protoname = "chap";
|
|
||||||
}else{
|
|
||||||
s->astype = AuthMSchap;
|
s->astype = AuthMSchap;
|
||||||
s->protoname = "mschap";
|
}else {
|
||||||
|
s->astype = AuthChap;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(iscli)
|
if(iscli)
|
||||||
fss->phase = CNeedChal;
|
fss->phase = CNeedChal;
|
||||||
else{
|
else{
|
||||||
|
@ -124,7 +125,6 @@ chapclose(Fsstate *fss)
|
||||||
free(s);
|
free(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
chapwrite(Fsstate *fss, void *va, uint n)
|
chapwrite(Fsstate *fss, void *va, uint n)
|
||||||
{
|
{
|
||||||
|
@ -137,7 +137,8 @@ chapwrite(Fsstate *fss, void *va, uint n)
|
||||||
MSchapreply *mcr;
|
MSchapreply *mcr;
|
||||||
OChapreply *ocr;
|
OChapreply *ocr;
|
||||||
OMSchapreply *omcr;
|
OMSchapreply *omcr;
|
||||||
uchar reply[4*1024];
|
uchar reply[4096];
|
||||||
|
char *user, *dom;
|
||||||
|
|
||||||
s = fss->ps;
|
s = fss->ps;
|
||||||
a = va;
|
a = va;
|
||||||
|
@ -154,28 +155,33 @@ chapwrite(Fsstate *fss, void *va, uint n)
|
||||||
closekey(k);
|
closekey(k);
|
||||||
return failure(fss, "key has no password");
|
return failure(fss, "key has no password");
|
||||||
}
|
}
|
||||||
|
s->nresp = 0;
|
||||||
|
memset(s->resp, 0, sizeof(s->resp));
|
||||||
setattrs(fss->attr, k->attr);
|
setattrs(fss->attr, k->attr);
|
||||||
switch(s->astype){
|
switch(s->astype){
|
||||||
default:
|
|
||||||
closekey(k);
|
|
||||||
return failure(fss, "chap internal botch");
|
|
||||||
case AuthMSchap:
|
case AuthMSchap:
|
||||||
if(n < ChapChallen){
|
if(n < MSchallen)
|
||||||
closekey(k);
|
break;
|
||||||
return failure(fss, "challenge too short");
|
if(fss->proto == &mschap2){
|
||||||
}
|
user = _strfindattr(fss->attr, "user");
|
||||||
doLMchap(v, (uchar *)a, (uchar *)s->mcr.LMresp);
|
if(user == nil)
|
||||||
doNTchap(v, (uchar *)a, (uchar *)s->mcr.NTresp);
|
break;
|
||||||
|
dom = _strfindattr(fss->attr, "windom");
|
||||||
|
if(dom == nil)
|
||||||
|
dom = "";
|
||||||
|
s->nresp = domschap2(v, user, dom, (uchar*)a, s->resp, sizeof(s->resp));
|
||||||
|
} else
|
||||||
|
s->nresp = domschap(v, (uchar*)a, s->resp, sizeof(s->resp));
|
||||||
break;
|
break;
|
||||||
case AuthChap:
|
case AuthChap:
|
||||||
if(n < ChapChallen+1){
|
if(n < ChapChallen+1)
|
||||||
closekey(k);
|
break;
|
||||||
return failure(fss, "challenge too short");
|
s->nresp = dochap(v, *a, a+1, s->resp, sizeof(s->resp));
|
||||||
}
|
|
||||||
dochap(v, *a, a+1, (uchar *)s->cr);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
closekey(k);
|
closekey(k);
|
||||||
|
if(s->nresp <= 0)
|
||||||
|
return failure(fss, "chap botch");
|
||||||
fss->phase = CHaveResp;
|
fss->phase = CHaveResp;
|
||||||
return RpcOk;
|
return RpcOk;
|
||||||
|
|
||||||
|
@ -239,21 +245,9 @@ chapread(Fsstate *fss, void *va, uint *n)
|
||||||
return phaseerror(fss, "read");
|
return phaseerror(fss, "read");
|
||||||
|
|
||||||
case CHaveResp:
|
case CHaveResp:
|
||||||
switch(s->astype){
|
if(*n > s->nresp)
|
||||||
default:
|
*n = s->nresp;
|
||||||
phaseerror(fss, "write");
|
memmove(va, s->resp, *n);
|
||||||
break;
|
|
||||||
case AuthMSchap:
|
|
||||||
if(*n > sizeof(MSchapreply))
|
|
||||||
*n = sizeof(MSchapreply);
|
|
||||||
memmove(va, &s->mcr, *n);
|
|
||||||
break;
|
|
||||||
case AuthChap:
|
|
||||||
if(*n > ChapResplen)
|
|
||||||
*n = ChapResplen;
|
|
||||||
memmove(va, s->cr, ChapResplen);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fss->phase = Established;
|
fss->phase = Established;
|
||||||
fss->haveai = 0;
|
fss->haveai = 0;
|
||||||
return RpcOk;
|
return RpcOk;
|
||||||
|
@ -313,7 +307,7 @@ err:
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
doreply(State *s, void *reply, int nreply)
|
doreply(State *s, uchar *reply, int nreply)
|
||||||
{
|
{
|
||||||
char ticket[TICKETLEN+AUTHENTLEN];
|
char ticket[TICKETLEN+AUTHENTLEN];
|
||||||
int n;
|
int n;
|
||||||
|
@ -382,90 +376,240 @@ Proto mschap = {
|
||||||
.keyprompt= "!password?"
|
.keyprompt= "!password?"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Proto mschap2 = {
|
||||||
|
.name= "mschap2",
|
||||||
|
.init= chapinit,
|
||||||
|
.write= chapwrite,
|
||||||
|
.read= chapread,
|
||||||
|
.close= chapclose,
|
||||||
|
.addkey= replacekey,
|
||||||
|
.keyprompt= "user? windom? !password?"
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
hash(uchar pass[16], uchar c8[ChapChallen], uchar p24[MSchapResplen])
|
nthash(uchar hash[MShashlen], char *passwd)
|
||||||
|
{
|
||||||
|
DigestState *ds;
|
||||||
|
uchar b[2];
|
||||||
|
Rune r;
|
||||||
|
|
||||||
|
ds = md4(nil, 0, nil, nil);
|
||||||
|
while(*passwd){
|
||||||
|
passwd += chartorune(&r, passwd);
|
||||||
|
b[0] = r & 0xff;
|
||||||
|
b[1] = r >> 8;
|
||||||
|
md4(b, 2, nil, ds);
|
||||||
|
}
|
||||||
|
md4(nil, 0, hash, ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
ntv2hash(uchar hash[MShashlen], char *passwd, char *user, char *dom)
|
||||||
|
{
|
||||||
|
uchar v1hash[MShashlen];
|
||||||
|
DigestState *ds;
|
||||||
|
uchar b[2];
|
||||||
|
Rune r;
|
||||||
|
|
||||||
|
nthash(v1hash, passwd);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some documentation insists that the username must be forced to
|
||||||
|
* uppercase, but the domain name should not be. Other shows both
|
||||||
|
* being forced to uppercase. I am pretty sure this is irrevevant as the
|
||||||
|
* domain name passed from the remote server always seems to be in
|
||||||
|
* uppercase already.
|
||||||
|
*/
|
||||||
|
ds = hmac_md5(nil, 0, v1hash, sizeof(v1hash), nil, nil);
|
||||||
|
while(*user){
|
||||||
|
user += chartorune(&r, user);
|
||||||
|
r = toupperrune(r);
|
||||||
|
b[0] = r & 0xff;
|
||||||
|
b[1] = r >> 8;
|
||||||
|
hmac_md5(b, 2, v1hash, sizeof(v1hash), nil, ds);
|
||||||
|
}
|
||||||
|
while(*dom){
|
||||||
|
dom += chartorune(&r, dom);
|
||||||
|
b[0] = r & 0xff;
|
||||||
|
b[1] = r >> 8;
|
||||||
|
hmac_md5(b, 2, v1hash, sizeof(v1hash), nil, ds);
|
||||||
|
}
|
||||||
|
hmac_md5(nil, 0, v1hash, sizeof(v1hash), hash, ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
desencrypt(uchar data[8], uchar key[7])
|
||||||
|
{
|
||||||
|
ulong ekey[32];
|
||||||
|
|
||||||
|
key_setup(key, ekey);
|
||||||
|
block_cipher(ekey, data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lmhash(uchar hash[MShashlen], char *passwd)
|
||||||
|
{
|
||||||
|
uchar buf[14];
|
||||||
|
char *stdtext = "KGS!@#$%";
|
||||||
|
int i;
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
strncpy((char*)buf, passwd, sizeof(buf));
|
||||||
|
for(i=0; i<sizeof(buf); i++)
|
||||||
|
if(buf[i] >= 'a' && buf[i] <= 'z')
|
||||||
|
buf[i] += 'A' - 'a';
|
||||||
|
|
||||||
|
memcpy(hash, stdtext, 8);
|
||||||
|
memcpy(hash+8, stdtext, 8);
|
||||||
|
|
||||||
|
desencrypt(hash, buf);
|
||||||
|
desencrypt(hash+8, buf+7);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mschalresp(uchar resp[MSresplen], uchar hash[MShashlen], uchar chal[MSchallen])
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
uchar p21[21];
|
uchar buf[21];
|
||||||
ulong schedule[32];
|
|
||||||
|
|
||||||
memset(p21, 0, sizeof p21 );
|
memset(buf, 0, sizeof(buf));
|
||||||
memmove(p21, pass, 16);
|
memcpy(buf, hash, MShashlen);
|
||||||
|
|
||||||
for(i=0; i<3; i++) {
|
for(i=0; i<3; i++) {
|
||||||
key_setup(p21+i*7, schedule);
|
memmove(resp+i*MSchallen, chal, MSchallen);
|
||||||
memmove(p24+i*8, c8, 8);
|
desencrypt(resp+i*MSchallen, buf+i*7);
|
||||||
block_cipher(schedule, p24+i*8, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
doNTchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen])
|
domschap(char *passwd, uchar chal[MSchallen], uchar *resp, int resplen)
|
||||||
{
|
{
|
||||||
Rune r;
|
uchar hash[MShashlen];
|
||||||
uchar digest[MD4dlen];
|
MSchapreply *r;
|
||||||
uchar *w, unipass[128*2]; // Standard says unlimited length, experience says 128 max
|
|
||||||
|
|
||||||
w=unipass;
|
r = (MSchapreply*)resp;
|
||||||
while(*pass != '\0' && w < &unipass[nelem(unipass)]){
|
if(resplen < sizeof(*r))
|
||||||
pass += chartorune(&r, pass);
|
return 0;
|
||||||
/* BUG: UTF-16 surrogates */
|
|
||||||
*w++ = r & 0xff;
|
|
||||||
*w++ = r >> 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(digest, 0, sizeof digest);
|
lmhash(hash, passwd);
|
||||||
md4(unipass, w-unipass, digest, nil);
|
mschalresp((uchar*)r->LMresp, hash, chal);
|
||||||
memset(unipass, 0, sizeof unipass);
|
|
||||||
hash(digest, chal, reply);
|
nthash(hash, passwd);
|
||||||
|
mschalresp((uchar*)r->NTresp, hash, chal);
|
||||||
|
|
||||||
|
return sizeof(*r);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
doLMchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen])
|
domschap2(char *passwd, char *user, char *dom, uchar chal[MSchallen], uchar *resp, int resplen)
|
||||||
{
|
{
|
||||||
int i;
|
uchar hash[MShashlen], *p, *e;
|
||||||
ulong schedule[32];
|
MSchapreply *r;
|
||||||
uchar p14[15], p16[16];
|
DigestState *s;
|
||||||
uchar s8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
|
uvlong t;
|
||||||
int n = strlen(pass);
|
Rune rr;
|
||||||
|
int nb;
|
||||||
|
|
||||||
if(n > 14){
|
ntv2hash(hash, passwd, user, dom);
|
||||||
// let prudent people avoid the LM vulnerability
|
|
||||||
// and protect the loop below from buffer overflow
|
r = (MSchapreply*)resp;
|
||||||
memset(reply, 0, MSchapResplen);
|
p = (uchar*)r->NTresp+16;
|
||||||
return;
|
e = resp + resplen;
|
||||||
|
|
||||||
|
if(p+2+2+4+8+8+4+4+4+4 > e)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
*p++ = 1; /* 8bit: response type */
|
||||||
|
*p++ = 1; /* 8bit: max response type understood by client */
|
||||||
|
|
||||||
|
*p++ = 0; /* 16bit: reserved */
|
||||||
|
*p++ = 0;
|
||||||
|
|
||||||
|
*p++ = 0; /* 32bit: unknown */
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
|
||||||
|
t = time(nil);
|
||||||
|
t += 11644473600LL;
|
||||||
|
t *= 10000000LL;
|
||||||
|
|
||||||
|
*p++ = t; /* 64bit: time in NT format */
|
||||||
|
*p++ = t >> 8;
|
||||||
|
*p++ = t >> 16;
|
||||||
|
*p++ = t >> 24;
|
||||||
|
*p++ = t >> 32;
|
||||||
|
*p++ = t >> 40;
|
||||||
|
*p++ = t >> 48;
|
||||||
|
*p++ = t >> 56;
|
||||||
|
|
||||||
|
memrandom(p, 8);
|
||||||
|
p += 8; /* 64bit: client nonce */
|
||||||
|
|
||||||
|
*p++ = 0; /* 32bit: unknown data */
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
|
||||||
|
*p++ = 2; /* AvPair Domain */
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0; /* length */
|
||||||
|
*p++ = 0;
|
||||||
|
nb = 0;
|
||||||
|
while(*dom){
|
||||||
|
dom += chartorune(&rr, dom);
|
||||||
|
if(p+2+4+4 > e)
|
||||||
|
return 0;
|
||||||
|
*p++ = rr & 0xFF;
|
||||||
|
*p++ = rr >> 8;
|
||||||
|
nb += 2;
|
||||||
}
|
}
|
||||||
|
p[-nb - 2] = nb & 0xFF;
|
||||||
|
p[-nb - 1] = nb >> 8;
|
||||||
|
|
||||||
// Spec says space padded, experience says otherwise
|
*p++ = 0; /* AvPair EOF */
|
||||||
memset(p14, 0, sizeof p14 -1);
|
*p++ = 0;
|
||||||
p14[sizeof p14 - 1] = '\0';
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
|
||||||
|
*p++ = 0; /* 32bit: unknown data */
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
*p++ = 0;
|
||||||
|
|
||||||
// NT4 requires uppercase, Win XP doesn't care
|
/*
|
||||||
for (i = 0; pass[i]; i++)
|
* LmResponse = Cat(HMAC_MD5(LmHash, Cat(SC, CC)), CC)
|
||||||
p14[i] = islower(pass[i])? toupper(pass[i]): pass[i];
|
*/
|
||||||
|
s = hmac_md5(chal, 8, hash, MShashlen, nil, nil);
|
||||||
|
memrandom((uchar*)r->LMresp+16, 8);
|
||||||
|
hmac_md5((uchar*)r->LMresp+16, 8, hash, MShashlen, (uchar*)r->LMresp, s);
|
||||||
|
|
||||||
for(i=0; i<2; i++) {
|
/*
|
||||||
key_setup(p14+i*7, schedule);
|
* NtResponse = Cat(HMAC_MD5(NtHash, Cat(SC, NtBlob)), NtBlob)
|
||||||
memmove(p16+i*8, s8, 8);
|
*/
|
||||||
block_cipher(schedule, p16+i*8, 0);
|
s = hmac_md5(chal, 8, hash, MShashlen, nil, nil);
|
||||||
}
|
hmac_md5((uchar*)r->NTresp+16, p - ((uchar*)r->NTresp+16), hash, MShashlen, (uchar*)r->NTresp, s);
|
||||||
|
|
||||||
memset(p14, 0, sizeof p14);
|
return p - resp;
|
||||||
hash(p16, chal, reply);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
dochap(char *pass, int id, char chal[ChapChallen], uchar resp[ChapResplen])
|
dochap(char *passwd, int id, char chal[ChapChallen], uchar *resp, int resplen)
|
||||||
{
|
{
|
||||||
char buf[1+ChapChallen+MAXNAMELEN+1];
|
char buf[1+ChapChallen+MAXNAMELEN+1];
|
||||||
int n = strlen(pass);
|
int n;
|
||||||
|
|
||||||
|
if(resplen < ChapResplen)
|
||||||
|
return 0;
|
||||||
|
|
||||||
*buf = id;
|
|
||||||
if (n > MAXNAMELEN)
|
|
||||||
n = MAXNAMELEN-1;
|
|
||||||
memset(buf, 0, sizeof buf);
|
memset(buf, 0, sizeof buf);
|
||||||
strncpy(buf+1, pass, n);
|
*buf = id;
|
||||||
|
n = strlen(passwd);
|
||||||
|
if(n > MAXNAMELEN)
|
||||||
|
n = MAXNAMELEN-1;
|
||||||
|
strncpy(buf+1, passwd, n);
|
||||||
memmove(buf+1+n, chal, ChapChallen);
|
memmove(buf+1+n, chal, ChapChallen);
|
||||||
md5((uchar*)buf, 1+n+ChapChallen, resp, nil);
|
md5((uchar*)buf, 1+n+ChapChallen, resp, nil);
|
||||||
}
|
|
||||||
|
|
||||||
|
return ChapResplen;
|
||||||
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ void writehostowner(char*);
|
||||||
/* protocols */
|
/* protocols */
|
||||||
extern Proto apop, cram; /* apop.c */
|
extern Proto apop, cram; /* apop.c */
|
||||||
extern Proto p9any, p9sk1, p9sk2; /* p9sk.c */
|
extern Proto p9any, p9sk1, p9sk2; /* p9sk.c */
|
||||||
extern Proto chap, mschap; /* chap.c */
|
extern Proto chap, mschap, mschap2; /* chap.c */
|
||||||
extern Proto p9cr, vnc; /* p9cr.c */
|
extern Proto p9cr, vnc; /* p9cr.c */
|
||||||
extern Proto pass; /* pass.c */
|
extern Proto pass; /* pass.c */
|
||||||
extern Proto rsa; /* rsa.c */
|
extern Proto rsa; /* rsa.c */
|
||||||
|
|
|
@ -31,6 +31,7 @@ prototab[] =
|
||||||
&cram,
|
&cram,
|
||||||
&httpdigest,
|
&httpdigest,
|
||||||
&mschap,
|
&mschap,
|
||||||
|
&mschap2,
|
||||||
&p9any,
|
&p9any,
|
||||||
&p9cr,
|
&p9cr,
|
||||||
&p9sk1,
|
&p9sk1,
|
||||||
|
|
|
@ -21,7 +21,6 @@ static enum {
|
||||||
MACkeylen = 40, /* MAC key len */
|
MACkeylen = 40, /* MAC key len */
|
||||||
MAClen = 8, /* signature length */
|
MAClen = 8, /* signature length */
|
||||||
MACoff = 14, /* sign. offset from start of SMB (not netbios) pkt */
|
MACoff = 14, /* sign. offset from start of SMB (not netbios) pkt */
|
||||||
Bliplen = 8, /* size of LMv2 client nonce */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -67,18 +66,25 @@ auth_plain(char *windom, char *keyp, uchar *chal, int len)
|
||||||
}
|
}
|
||||||
|
|
||||||
static Auth *
|
static Auth *
|
||||||
auth_lm_and_ntlm(char *windom, char *keyp, uchar *chal, int len)
|
auth_proto(char *proto, char *windom, char *keyp, uchar *chal, int len)
|
||||||
{
|
{
|
||||||
int err;
|
MSchapreply *mcr;
|
||||||
|
uchar resp[4096];
|
||||||
|
int nresp;
|
||||||
char user[64];
|
char user[64];
|
||||||
Auth *ap;
|
Auth *ap;
|
||||||
MSchapreply mcr;
|
|
||||||
|
|
||||||
err = auth_respond(chal, len, user, sizeof user, &mcr, sizeof mcr,
|
mcr = (MSchapreply*)resp;
|
||||||
auth_getkey, "windom=%s proto=mschap role=client service=cifs %s",
|
nresp = sizeof(resp);
|
||||||
windom, keyp);
|
if(strcmp(proto, "mschap") == 0)
|
||||||
if(err == -1)
|
nresp = sizeof(*mcr); /* backwards compatibility with old factotum */
|
||||||
sysfatal("cannot get key - %r");
|
nresp = auth_respond(chal, len, user, sizeof user, resp, nresp,
|
||||||
|
auth_getkey, "proto=%s role=client service=cifs windom=%s %s",
|
||||||
|
proto, windom, keyp);
|
||||||
|
if(nresp < 0)
|
||||||
|
sysfatal("cannot get response - %r");
|
||||||
|
if(nresp < sizeof(*mcr))
|
||||||
|
sysfatal("bad response size");
|
||||||
|
|
||||||
ap = emalloc9p(sizeof(Auth));
|
ap = emalloc9p(sizeof(Auth));
|
||||||
memset(ap, 0, sizeof(ap));
|
memset(ap, 0, sizeof(ap));
|
||||||
|
@ -86,18 +92,24 @@ auth_lm_and_ntlm(char *windom, char *keyp, uchar *chal, int len)
|
||||||
ap->windom = estrdup9p(windom);
|
ap->windom = estrdup9p(windom);
|
||||||
|
|
||||||
/* LM response */
|
/* LM response */
|
||||||
ap->len[0] = sizeof(mcr.LMresp);
|
ap->len[0] = sizeof(mcr->LMresp);
|
||||||
ap->resp[0] = emalloc9p(ap->len[0]);
|
ap->resp[0] = emalloc9p(ap->len[0]);
|
||||||
memcpy(ap->resp[0], mcr.LMresp, ap->len[0]);
|
memcpy(ap->resp[0], mcr->LMresp, ap->len[0]);
|
||||||
|
|
||||||
/* NTLM response */
|
/* NT response */
|
||||||
ap->len[1] = sizeof(mcr.NTresp);
|
ap->len[1] = nresp+sizeof(mcr->NTresp)-sizeof(*mcr);
|
||||||
ap->resp[1] = emalloc9p(ap->len[1]);
|
ap->resp[1] = emalloc9p(ap->len[1]);
|
||||||
memcpy(ap->resp[1], mcr.NTresp, ap->len[1]);
|
memcpy(ap->resp[1], mcr->NTresp, ap->len[1]);
|
||||||
|
|
||||||
return ap;
|
return ap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Auth *
|
||||||
|
auth_lm_and_ntlm(char *windom, char *keyp, uchar *chal, int len)
|
||||||
|
{
|
||||||
|
return auth_proto("mschap", windom, keyp, chal, len);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NTLM response only, the LM response is a just
|
* NTLM response only, the LM response is a just
|
||||||
* copy of the NTLM one. we do this because the lm
|
* copy of the NTLM one. we do this because the lm
|
||||||
|
@ -119,211 +131,10 @@ auth_ntlm(char *windom, char *keyp, uchar *chal, int len)
|
||||||
return ap;
|
return ap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This is not really nescessary as all fields hmac_md5'ed
|
|
||||||
* in the ntlmv2 protocol are less than 64 bytes long, however
|
|
||||||
* I still do this for completeness
|
|
||||||
*/
|
|
||||||
static DigestState *
|
|
||||||
hmac_t64(uchar *data, ulong dlen, uchar *key, ulong klen, uchar *digest,
|
|
||||||
DigestState *state)
|
|
||||||
{
|
|
||||||
if(klen > 64)
|
|
||||||
klen = 64;
|
|
||||||
return hmac_md5(data, dlen, key, klen, digest, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
putname(uchar *buf, int len, char *name, int type)
|
|
||||||
{
|
|
||||||
int n;
|
|
||||||
Rune r;
|
|
||||||
char *d;
|
|
||||||
uchar *p = buf;
|
|
||||||
|
|
||||||
*p++ = type;
|
|
||||||
*p++ = 0; /* 16bit: name type */
|
|
||||||
|
|
||||||
n = utflen(name) * 2;
|
|
||||||
*p++ = n;
|
|
||||||
*p++ = n >> 8; /* 16bit: name length */
|
|
||||||
|
|
||||||
d = name;
|
|
||||||
while(*d != 0 && p-buf < len-8){
|
|
||||||
d += chartorune(&r, d);
|
|
||||||
r = toupperrune(r);
|
|
||||||
*p++ = r;
|
|
||||||
*p++ = r >> 8;
|
|
||||||
} /* var: actual name */
|
|
||||||
|
|
||||||
return p - buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
ntv2_blob(uchar *blob, int len, char *windom)
|
|
||||||
{
|
|
||||||
uvlong t;
|
|
||||||
uchar *p;
|
|
||||||
enum { /* name types */
|
|
||||||
Beof, /* end of name list */
|
|
||||||
Bhost, /* Netbios host name */
|
|
||||||
Bdomain, /* Windows Domain name (NT) */
|
|
||||||
Bdnshost, /* DNS host name */
|
|
||||||
Bdnsdomain, /* DNS domain name */
|
|
||||||
};
|
|
||||||
|
|
||||||
p = blob;
|
|
||||||
*p++ = 1; /* 8bit: response type */
|
|
||||||
*p++ = 1; /* 8bit: max response type understood by client */
|
|
||||||
|
|
||||||
*p++ = 0; /* 16bit: reserved */
|
|
||||||
*p++ = 0;
|
|
||||||
|
|
||||||
*p++ = 0; /* 32bit: unknown */
|
|
||||||
*p++ = 0;
|
|
||||||
*p++ = 0;
|
|
||||||
*p++ = 0;
|
|
||||||
|
|
||||||
t = time(nil);
|
|
||||||
t += 11644473600LL;
|
|
||||||
t *= 10000000LL;
|
|
||||||
|
|
||||||
*p++ = t; /* 64bit: time in NT format */
|
|
||||||
*p++ = t >> 8;
|
|
||||||
*p++ = t >> 16;
|
|
||||||
*p++ = t >> 24;
|
|
||||||
*p++ = t >> 32;
|
|
||||||
*p++ = t >> 40;
|
|
||||||
*p++ = t >> 48;
|
|
||||||
*p++ = t >> 56;
|
|
||||||
|
|
||||||
genrandom(p, 8);
|
|
||||||
p += 8; /* 64bit: client nonce */
|
|
||||||
|
|
||||||
*p++ = 0; /* 32bit: unknown data */
|
|
||||||
*p++ = 0;
|
|
||||||
*p++ = 0;
|
|
||||||
*p++ = 0;
|
|
||||||
|
|
||||||
len -= 4;
|
|
||||||
p += putname(p, len - (p-blob), windom, Bdomain);
|
|
||||||
p += putname(p, len - (p-blob), "", Beof);
|
|
||||||
|
|
||||||
*p++ = 0; /* 32bit: unknown data */
|
|
||||||
*p++ = 0;
|
|
||||||
*p++ = 0;
|
|
||||||
*p++ = 0;
|
|
||||||
|
|
||||||
return p - blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Auth *
|
static Auth *
|
||||||
auth_ntlmv2(char *windom, char *keyp, uchar *chal, int len)
|
auth_ntlmv2(char *windom, char *keyp, uchar *chal, int len)
|
||||||
{
|
{
|
||||||
int i, n;
|
return auth_proto("mschap2", windom, keyp, chal, len);
|
||||||
Rune r;
|
|
||||||
char *p, *u;
|
|
||||||
uchar v1hash[MD5dlen], blip[Bliplen], blob[1024], v2hash[MD5dlen];
|
|
||||||
uchar c, lm_hmac[MD5dlen], nt_hmac[MD5dlen], nt_sesskey[MD5dlen],
|
|
||||||
lm_sesskey[MD5dlen];
|
|
||||||
DigestState *ds;
|
|
||||||
UserPasswd *up;
|
|
||||||
static Auth *ap;
|
|
||||||
|
|
||||||
up = auth_getuserpasswd(auth_getkey, "windom=%s proto=pass service=cifs-ntlmv2 %s",
|
|
||||||
windom, keyp);
|
|
||||||
if(!up)
|
|
||||||
sysfatal("cannot get key - %r");
|
|
||||||
|
|
||||||
ap = emalloc9p(sizeof(Auth));
|
|
||||||
memset(ap, 0, sizeof(ap));
|
|
||||||
|
|
||||||
/* Standard says unlimited length, experience says 128 max */
|
|
||||||
if((n = strlen(up->passwd)) > 128)
|
|
||||||
n = 128;
|
|
||||||
|
|
||||||
ds = md4(nil, 0, nil, nil);
|
|
||||||
for(i=0, p=up->passwd; i < n; i++) {
|
|
||||||
p += chartorune(&r, p);
|
|
||||||
c = r;
|
|
||||||
md4(&c, 1, nil, ds);
|
|
||||||
c = r >> 8;
|
|
||||||
md4(&c, 1, nil, ds);
|
|
||||||
}
|
|
||||||
md4(nil, 0, v1hash, ds);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Some documentation insists that the username must be forced to
|
|
||||||
* uppercase, but the domain name should not be. Other shows both
|
|
||||||
* being forced to uppercase. I am pretty sure this is irrevevant as the
|
|
||||||
* domain name passed from the remote server always seems to be in
|
|
||||||
* uppercase already.
|
|
||||||
*/
|
|
||||||
ds = hmac_t64(nil, 0, v1hash, MD5dlen, nil, nil);
|
|
||||||
u = up->user;
|
|
||||||
while(*u){
|
|
||||||
u += chartorune(&r, u);
|
|
||||||
r = toupperrune(r);
|
|
||||||
c = r;
|
|
||||||
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
|
|
||||||
c = r >> 8;
|
|
||||||
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
|
|
||||||
}
|
|
||||||
u = windom;
|
|
||||||
|
|
||||||
while(*u){
|
|
||||||
u += chartorune(&r, u);
|
|
||||||
c = r;
|
|
||||||
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
|
|
||||||
c = r >> 8;
|
|
||||||
hmac_t64(&c, 1, v1hash, MD5dlen, nil, ds);
|
|
||||||
}
|
|
||||||
hmac_t64(nil, 0, v1hash, MD5dlen, v2hash, ds);
|
|
||||||
ap->user = estrdup9p(up->user);
|
|
||||||
ap->windom = estrdup9p(windom);
|
|
||||||
|
|
||||||
/* LM v2 */
|
|
||||||
|
|
||||||
genrandom(blip, Bliplen);
|
|
||||||
ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
|
|
||||||
hmac_t64(blip, Bliplen, v2hash, MD5dlen, lm_hmac, ds);
|
|
||||||
ap->len[0] = MD5dlen+Bliplen;
|
|
||||||
ap->resp[0] = emalloc9p(ap->len[0]);
|
|
||||||
memcpy(ap->resp[0], lm_hmac, MD5dlen);
|
|
||||||
memcpy(ap->resp[0]+MD5dlen, blip, Bliplen);
|
|
||||||
|
|
||||||
/* LM v2 session key */
|
|
||||||
hmac_t64(lm_hmac, MD5dlen, v2hash, MD5dlen, lm_sesskey, nil);
|
|
||||||
|
|
||||||
/* LM v2 MAC key */
|
|
||||||
ap->mackey[0] = emalloc9p(MACkeylen);
|
|
||||||
memcpy(ap->mackey[0], lm_sesskey, MD5dlen);
|
|
||||||
memcpy(ap->mackey[0]+MD5dlen, ap->resp[0], MACkeylen-MD5dlen);
|
|
||||||
|
|
||||||
/* NTLM v2 */
|
|
||||||
n = ntv2_blob(blob, sizeof blob, windom);
|
|
||||||
ds = hmac_t64(chal, len, v2hash, MD5dlen, nil, nil);
|
|
||||||
hmac_t64(blob, n, v2hash, MD5dlen, nt_hmac, ds);
|
|
||||||
ap->len[1] = MD5dlen+n;
|
|
||||||
ap->resp[1] = emalloc9p(ap->len[1]);
|
|
||||||
memcpy(ap->resp[1], nt_hmac, MD5dlen);
|
|
||||||
memcpy(ap->resp[1]+MD5dlen, blob, n);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* v2hash definitely OK by
|
|
||||||
* the time we get here.
|
|
||||||
*/
|
|
||||||
/* NTLM v2 session key */
|
|
||||||
hmac_t64(nt_hmac, MD5dlen, v2hash, MD5dlen, nt_sesskey, nil);
|
|
||||||
|
|
||||||
/* NTLM v2 MAC key */
|
|
||||||
ap->mackey[1] = emalloc9p(MACkeylen);
|
|
||||||
memcpy(ap->mackey[1], nt_sesskey, MD5dlen);
|
|
||||||
memcpy(ap->mackey[1]+MD5dlen, ap->resp[1], MACkeylen-MD5dlen);
|
|
||||||
free(up);
|
|
||||||
|
|
||||||
return ap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
Loading…
Reference in a new issue