Added chap client support (factotum had server only). I include chaptest.c which exercises the code. I use it in the latest cifs client daily, but havent ported ip/ppp as I no longer use that. -Steve Reference: /n/sources/patch/applied/chap-client Date: Sun Oct 31 00:48:04 CES 2004 --- /sys/src/cmd/auth/factotum/chap.c Sun Oct 31 00:48:04 2004 +++ /sys/src/cmd/auth/factotum/chap.c Sun Oct 31 00:48:03 2004 @@ -4,7 +4,8 @@ * The client does not authenticate the server, hence no CAI * * Client protocol: - * unimplemented + * write Chapchal + * read response Chapreply or MSchaprely structure * * Server protocol: * read challenge: 8 bytes binary @@ -12,14 +13,21 @@ * write response: Chapreply or MSchapreply structure */ +#include #include "dat.h" enum { ChapChallen = 8, + ChapResplen = 16, + MSchapResplen = 24, }; static int dochal(State*); static int doreply(State*, void*, int); +static void doLMchap(char *, uchar [ChapChallen], uchar [MSchapResplen]); +static void doNTchap(char *, uchar [ChapChallen], uchar [MSchapResplen]); +static void dochap(char *, int, char [ChapChallen], uchar [ChapResplen]); + struct State { @@ -30,7 +38,9 @@ Ticket t; Ticketreq tr; char chal[ChapChallen]; - char err[ERRMAX]; + MSchapreply mcr; + char cr[ChapResplen]; + char err[ERRMAX]; char user[64]; uchar secret[16]; /* for mschap */ int nsecret; @@ -38,6 +48,9 @@ enum { + CNeedChal, + CHaveResp, + SHaveChal, SNeedUser, SNeedResp, @@ -49,6 +62,9 @@ static char *phasenames[Maxphase] = { +[CNeedChal] "CNeedChal", +[CHaveResp] "CHaveResp", + [SHaveChal] "SHaveChal", [SNeedUser] "SNeedUser", [SNeedResp] "SNeedResp", @@ -64,8 +80,6 @@ if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0) return failure(fss, nil); - if(iscli) - return failure(fss, "%s client not supported", p->name); s = emalloc(sizeof *s); fss->phasename = phasenames; @@ -78,15 +92,21 @@ s->astype = AuthMSchap; s->protoname = "mschap"; } - if((ret = findp9authkey(&s->key, fss)) != RpcOk){ - free(s); - return ret; - } - if(dochal(s) < 0){ - free(s); - return failure(fss, nil); + + if(iscli) + fss->phase = CNeedChal; + else{ + if((ret = findp9authkey(&s->key, fss)) != RpcOk){ + free(s); + return ret; + } + if(dochal(s) < 0){ + free(s); + return failure(fss, nil); + } + fss->phase = SHaveChal; } - fss->phase = SHaveChal; + fss->ps = s; return RpcOk; } @@ -104,11 +124,26 @@ free(s); } +void +dmpkey(char *s, void *v, int n) +{ + int i; + char *p = v; + + print("%s", s); + for (i = 0; i < n; i++) + print("%02x ", *p++); + print("\n"); +} + + static int chapwrite(Fsstate *fss, void *va, uint n) { - int nreply; + int ret, nreply; + char *a, *v; void *reply; + Key *k; State *s; Chapreply cr; MSchapreply mcr; @@ -116,10 +151,34 @@ OMSchapreply omcr; s = fss->ps; + a = va; switch(fss->phase){ default: return phaseerror(fss, "write"); + case CNeedChal: + ret = findkey(&k, fss, fss->sysuser, 0, 0, fss->attr, "%s", fss->proto->keyprompt); + if(ret != RpcOk) + return ret; + v = _strfindattr(k->privattr, "!password"); + if(v == nil) + return failure(fss, "key has no password"); + setattrs(fss->attr, k->attr); + switch(s->astype){ + default: + abort(); + case AuthMSchap: + doLMchap(v, (uchar *)a, (uchar *)s->mcr.LMresp); + doNTchap(v, (uchar *)a, (uchar *)s->mcr.NTresp); + break; + case AuthChap: + dochap(v, *a, a+1, (uchar *)s->cr); + break; + } + closekey(k); + fss->phase = CHaveResp; + return RpcOk; + case SNeedUser: if(n >= sizeof s->user) return failure(fss, "user name too long"); @@ -151,6 +210,9 @@ strecpy(omcr.uid, omcr.uid+sizeof omcr.uid, s->user); reply = &omcr; nreply = sizeof omcr; +print("%s\n", omcr.uid); +dmpkey("LM ", omcr.LMresp, 24); +dmpkey("NT ", omcr.NTresp, 24); break; } if(doreply(s, reply, nreply) < 0) @@ -175,6 +237,25 @@ default: return phaseerror(fss, "read"); + case CHaveResp: + switch(s->astype){ + default: + abort(); + 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->haveai = 0; + return RpcOk; + case SHaveChal: if(*n > sizeof s->chal) *n = sizeof s->chal; @@ -273,6 +354,8 @@ .write= chapwrite, .read= chapread, .close= chapclose, +.addkey= replacekey, +.keyprompt= "!password?" }; Proto mschap = { @@ -281,5 +364,86 @@ .write= chapwrite, .read= chapread, .close= chapclose, +.addkey= replacekey, +.keyprompt= "!password?" }; + +static void +hash(uchar pass[16], uchar c8[ChapChallen], uchar p24[MSchapResplen]) +{ + int i; + uchar p21[21]; + ulong schedule[32]; + + memset(p21, 0, sizeof p21 ); + memmove(p21, pass, 16); + + for(i=0; i<3; i++) { + key_setup(p21+i*7, schedule); + memmove(p24+i*8, c8, 8); + block_cipher(schedule, p24+i*8, 0); + } +} + +static void +doNTchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen]) +{ + int i, n; + uchar *w, unipass[256]; + uchar digest[MD4dlen]; + + Rune r; + + // Standard says unlimited length, experience says 128 max + if ((n = strlen(pass)) > 128) + n = 128; + + memset(unipass, 0, sizeof unipass); + for(i=0, w=unipass; i < n; i++) { + pass += chartorune(&r, pass); + *w++ = r & 0xff; + *w++ = r >> 8; + } + + memset(digest, 0, sizeof digest); + md4(unipass, w - unipass, digest, nil); + hash(digest, chal, reply); +} + +static void +doLMchap(char *pass, uchar chal[ChapChallen], uchar reply[MSchapResplen]) +{ + int i; + ulong schedule[32]; + uchar p14[15], p16[16]; + uchar s8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; + + // Spec says space padded, experience says otherwise + memset(p14, 0, sizeof p14 -1); + p14[sizeof p14 - 1] = '\0'; + + // NT4 requires uppercase, Win XP doesn't care + for (i = 0; pass[i]; i++) + p14[i] = islower(pass[i])? toupper(pass[i]): pass[i]; + + for(i=0; i<2; i++) { + key_setup(p14+i*7, schedule); + memmove(p16+i*8, s8, 8); + block_cipher(schedule, p16+i*8, 0); + } + + hash(p16, chal, reply); +} + +static void +dochap(char *pass, int id, char chal[ChapChallen], uchar resp[ChapResplen]) +{ + char buf[1+ChapChallen+MAXNAMELEN+1]; + int n = strlen(pass); + + *buf = id; + strcpy(buf+1, pass); + memmove(buf+1+n, chal, ChapChallen); + md5((uchar*)buf, 1+n+ChapChallen, resp, nil); +} --- /sys/src/cmd/auth/factotum/chaptest.c Thu Jan 1 00:00:00 1970 +++ /sys/src/cmd/auth/factotum/chaptest.c Sun Oct 31 00:48:04 2004 @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include + +char * +strupr(char *s) +{ + char *p; + for (p = s; *p; p++) + if (islower(*p)) + *p = toupper(*p); + return s; +} + + +/* NT LM 0.12 challenge/response from cifs2.txt, ยง2.10 -- belongs in Factotum */ +static void +hash(uchar pass[16], uchar c8[8], uchar p24[24]) +{ + int i; + uchar p21[21]; + ulong schedule[32]; + + memset(p21, 0, sizeof p21 ); + memmove(p21, pass, 16); + + for(i=0; i<3; i++) { + key_setup(p21+i*7, schedule); + memmove(p24+i*8, c8, 8); + block_cipher(schedule, p24+i*8, 0); + } +} + +void +doNTreply(char *pass, uchar chal[8], uchar reply[24]) +{ + int i, n; + uchar *w, unipass[256]; + uchar digest[MD4dlen]; + + Rune r; + + // Standard says unlimited length, experience says 128 max + if ((n = strlen(pass)) > 128) + n = 128; + + memset(unipass, 0, sizeof unipass); + for(i=0, w=unipass; i < n; i++) { + pass += chartorune(&r, pass); + *w++ = r & 0xff; + *w++ = r >> 8; + } + + memset(digest, 0, sizeof digest); + md4(unipass, w - unipass, digest, nil); + hash(digest, chal, reply); +} + +void +doLMreply(char *pass, uchar chal[8], uchar reply[24]) +{ + int i; + ulong schedule[32]; + uchar p14[15], p16[16]; + uchar s8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; + + memset(p14, 0, sizeof p14 -1); // Spec says space padded, experience says otherwise + p14[sizeof p14 - 1] = '\0'; + memmove(p14, pass, strlen(pass)); + strupr((char *)p14); // NT4 requires uppercase, Win XP doesn't care + + for(i=0; i<2; i++) { + key_setup(p14+i*7, schedule); + memmove(p16+i*8, s8, 8); + block_cipher(schedule, p16+i*8, 0); + } + + hash(p16, chal, reply); +} + +dmpkey(char *s, void *v, int n) +{ + int i; + char *p = v; + + print("%s", s); + for (i = 0; i < n; i++) + print("%02x ", *p++); + print("\n"); +} + +void +dochap(char *pass, int id, char chal[8], uchar resp[16]) +{ + char buf[1+8+255+1]; + int n = strlen(pass); + + *buf = id; + strcpy(buf+1, pass); + memmove(buf+1+n, chal, 8); + md5((uchar*)buf, 1+n+8, resp, nil); +} + + + +void +main(int argc, char *argv[]) +{ + int n, i; + UserPasswd *up; + Chapreply chapchal; + MSchapreply mcr; + char *user; + uchar lm[64], nt[64], chal[64], chap[64], resp[64]; + + if (argc != 3){ + fprint(2, "usage: %s user challange\n", argv[0]); + exits("usage"); + } + + user = argv[1]; + strncpy((char *)chal, argv[2], sizeof(chal)); + + if ((up = auth_getuserpasswd(auth_getkey, "proto=pass service=cifs user=%s", user)) == nil) + sysfatal("auth_getuserpasswd failed %r"); + + doLMreply(up->passwd, chal, lm); + doNTreply(up->passwd, chal, nt); + + if((n = auth_respond(chal, sizeof(chal), user, strlen(user), &mcr, sizeof(mcr), auth_getkey, + "proto=mschap role=client service=cifs user=%s", up->user)) == -1) + sysfatal("auth_respond failed %r"); + + + if (memcmp(mcr.NTresp, nt, 24)){ + fprint(2, "FAILED\n"); + dmpkey("my nt:", nt, 24); + dmpkey("ft nt:", mcr.NTresp, 24); + } + else + fprint(2, "MSCHAP NT ok\n"); + + if (memcmp(mcr.LMresp, lm, 24)){ + fprint(2, "FAILED\n"); + dmpkey("my nt:", lm, 24); + dmpkey("ft nt:", mcr.LMresp, 24); + } + else + fprint(2, "MSCHAP LM ok\n"); + + /*********************/ + + chapchal.id = 123; + strncpy(chapchal.resp, (char *)chal, 8); + + if((n = auth_respond(&chapchal, sizeof(chapchal), user, strlen(user), &resp, sizeof(resp), auth_getkey, + "proto=chap role=client service=cifs user=%s", up->user)) == -1) + sysfatal("auth_respond failed %r"); + + dochap(up->passwd, chapchal.id, chapchal.resp, chap); + + if (memcmp(chap, resp, 16)){ + fprint(2, "FAILED\n"); + dmpkey("my chap:", chap, 16); + dmpkey("ft chap:", resp, 16); + } + else + fprint(2, "CHAP ok\n"); +}