aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/auth/factotum/chap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/auth/factotum/chap.c')
-rw-r--r--src/cmd/auth/factotum/chap.c426
1 files changed, 426 insertions, 0 deletions
diff --git a/src/cmd/auth/factotum/chap.c b/src/cmd/auth/factotum/chap.c
new file mode 100644
index 00000000..2b258902
--- /dev/null
+++ b/src/cmd/auth/factotum/chap.c
@@ -0,0 +1,426 @@
+/*
+ * CHAP, MSCHAP
+ *
+ * The client does not authenticate the server, hence no CAI
+ *
+ * Protocol:
+ *
+ * S -> C: random 8-byte challenge
+ * C -> S: user in UTF-8
+ * C -> S: Chapreply or MSchapreply structure
+ * S -> C: ok or 'bad why'
+ *
+ * The chap protocol requires the client to give it id=%d, the id of
+ * the PPP message containing the challenge, which is used
+ * as part of the response. Because the client protocol is message-id
+ * specific, there is no point in looping to try multiple keys.
+ *
+ * The MS chap protocol actually uses two different hashes, an
+ * older insecure one called the LM (Lan Manager) hash, and a newer
+ * more secure one called the NT hash. By default we send back only
+ * the NT hash, because the LM hash can help an eavesdropper run
+ * a brute force attack. If the key has an lm attribute, then we send only the
+ * LM hash.
+ */
+
+#include "std.h"
+#include "dat.h"
+
+extern Proto chap, mschap;
+
+enum {
+ ChapChallen = 8,
+
+ MShashlen = 16,
+ MSchallen = 8,
+ MSresplen = 24,
+};
+
+static int
+chapcheck(Key *k)
+{
+ if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
+ werrstr("need user and !password attributes");
+ return -1;
+ }
+ return 0;
+}
+
+static void
+nthash(uchar hash[MShashlen], char *passwd)
+{
+ uchar buf[512];
+ int i;
+
+ for(i=0; *passwd && i<sizeof(buf); passwd++) {
+ buf[i++] = *passwd;
+ buf[i++] = 0;
+ }
+
+ memset(hash, 0, 16);
+
+ md4(buf, i, hash, 0);
+}
+
+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;
+
+ strncpy((char*)buf, passwd, sizeof(buf));
+ for(i=0; i<sizeof(buf); i++)
+ if(buf[i] >= 'a' && buf[i] <= 'z')
+ buf[i] += 'A' - 'a';
+
+ memset(hash, 0, 16);
+ 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;
+ uchar buf[21];
+
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, hash, MShashlen);
+
+ for(i=0; i<3; i++) {
+ memmove(resp+i*MSchallen, chal, MSchallen);
+ desencrypt(resp+i*MSchallen, buf+i*7);
+ }
+}
+
+static int
+chapclient(Conv *c)
+{
+ int id, astype, nchal, npw, ret;
+ uchar *chal;
+ char *s, *pw, *user, *res;
+ Attr *attr;
+ Key *k;
+ Chapreply cr;
+ MSchapreply mscr;
+ DigestState *ds;
+
+ ret = -1;
+ chal = nil;
+ k = nil;
+ attr = c->attr;
+
+ if(c->proto == &chap){
+ astype = AuthChap;
+ s = strfindattr(attr, "id");
+ if(s == nil || *s == 0){
+ werrstr("need id=n attr in start message");
+ goto out;
+ }
+ id = strtol(s, &s, 10);
+ if(*s != 0 || id < 0 || id >= 256){
+ werrstr("bad id=n attr in start message");
+ goto out;
+ }
+ cr.id = id;
+ }else if(c->proto == &mschap)
+ astype = AuthMSchap;
+ else{
+ werrstr("bad proto");
+ goto out;
+ }
+
+ c->state = "find key";
+ k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
+ if(k == nil)
+ goto out;
+
+ c->attr = addattrs(copyattr(attr), k->attr);
+
+ c->state = "read challenge";
+ if((nchal = convreadm(c, (char**)(void*)&chal)) < 0)
+ goto out;
+ if(astype == AuthMSchap && nchal != MSchallen)
+ c->state = "write user";
+ if((user = strfindattr(k->attr, "user")) == nil){
+ werrstr("key has no user (cannot happen?)");
+ goto out;
+ }
+ if(convprint(c, "%s", user) < 0)
+ goto out;
+
+ c->state = "write response";
+ if((pw = strfindattr(k->privattr, "!password")) == nil){
+ werrstr("key has no password (cannot happen?)");
+ goto out;
+ }
+ npw = strlen(pw);
+
+ if(astype == AuthChap){
+ ds = md5(&cr.id, 1, 0, 0);
+ md5((uchar*)pw, npw, 0, ds);
+ md5(chal, nchal, (uchar*)cr.resp, ds);
+ if(convwrite(c, &cr, sizeof cr) < 0)
+ goto out;
+ }else{
+ uchar hash[MShashlen];
+
+ memset(&mscr, 0, sizeof mscr);
+ if(strfindattr(k->attr, "lm")){
+ lmhash(hash, pw);
+ mschalresp((uchar*)mscr.LMresp, hash, chal);
+ }else{
+ nthash(hash, pw);
+ mschalresp((uchar*)mscr.NTresp, hash, chal);
+ }
+ if(convwrite(c, &mscr, sizeof mscr) < 0)
+ goto out;
+ }
+
+ c->state = "read result";
+ if(convreadm(c, &res) < 0)
+ goto out;
+ if(strcmp(res, "ok") == 0){
+ ret = 0;
+ werrstr("succeeded");
+ goto out;
+ }
+ if(strncmp(res, "bad ", 4) != 0){
+ werrstr("bad result: %s", res);
+ goto out;
+ }
+
+ c->state = "replace key";
+ keyevict(c, k, "%s", res+4);
+ werrstr("%s", res+4);
+
+out:
+ free(res);
+ keyclose(k);
+ free(chal);
+ if(c->attr != attr)
+ freeattr(attr);
+ return ret;
+}
+
+/* shared with auth dialing routines */
+typedef struct ServerState ServerState;
+struct ServerState
+{
+ int asfd;
+ Key *k;
+ Ticketreq tr;
+ Ticket t;
+ char *dom;
+ char *hostid;
+};
+
+static int chapchal(ServerState*, int, char[ChapChallen]);
+static int chapresp(ServerState*, char*, char*);
+
+static int
+chapserver(Conv *c)
+{
+ char chal[ChapChallen], *user, *resp;
+ ServerState s;
+ int astype, ret;
+ Attr *a;
+
+ ret = -1;
+ user = nil;
+ resp = nil;
+ memset(&s, 0, sizeof s);
+ s.asfd = -1;
+
+ if(c->proto == &chap)
+ astype = AuthChap;
+ else if(c->proto == &mschap)
+ astype = AuthMSchap;
+ else{
+ werrstr("bad proto");
+ goto out;
+ }
+
+ c->state = "find key";
+ if((s.k = plan9authkey(c->attr)) == nil)
+ goto out;
+
+ a = copyattr(s.k->attr);
+ a = delattr(a, "proto");
+ c->attr = addattrs(c->attr, a);
+ freeattr(a);
+
+ c->state = "authdial";
+ s.hostid = strfindattr(s.k->attr, "user");
+ s.dom = strfindattr(s.k->attr, "dom");
+ if((s.asfd = xioauthdial(nil, s.dom)) < 0){
+ werrstr("authdial %s: %r", s.dom);
+ goto out;
+ }
+
+ c->state = "authchal";
+ if(chapchal(&s, astype, chal) < 0)
+ goto out;
+
+ c->state = "write challenge";
+ if(convprint(c, "%s", chal) < 0)
+ goto out;
+
+ c->state = "read user";
+ if(convreadm(c, &user) < 0)
+ goto out;
+
+ c->state = "read response";
+ if(convreadm(c, &resp) < 0)
+ goto out;
+
+ c->state = "authwrite";
+ switch(chapresp(&s, user, resp)){
+ default:
+ fprint(2, "factotum: bad result from chapresp\n");
+ goto out;
+ case -1:
+ goto out;
+ case 0:
+ c->state = "write status";
+ if(convprint(c, "bad authentication failed") < 0)
+ goto out;
+ goto out;
+
+ case 1:
+ c->state = "write status";
+ if(convprint(c, "ok") < 0)
+ goto out;
+ goto ok;
+ }
+
+ok:
+ ret = 0;
+ c->attr = addcap(c->attr, c->sysuser, &s.t);
+
+out:
+ keyclose(s.k);
+ free(user);
+ free(resp);
+// xioclose(s.asfd);
+ return ret;
+}
+
+static int
+chapchal(ServerState *s, int astype, char chal[ChapChallen])
+{
+ char trbuf[TICKREQLEN];
+ Ticketreq tr;
+
+ memset(&tr, 0, sizeof tr);
+
+ tr.type = astype;
+
+ if(strlen(s->hostid) >= sizeof tr.hostid){
+ werrstr("hostid too long");
+ return -1;
+ }
+ strcpy(tr.hostid, s->hostid);
+
+ if(strlen(s->dom) >= sizeof tr.authdom){
+ werrstr("domain too long");
+ return -1;
+ }
+ strcpy(tr.authdom, s->dom);
+
+ convTR2M(&tr, trbuf);
+ if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+ return -1;
+
+ if(xioasrdresp(s->asfd, chal, ChapChallen) <= 5)
+ return -1;
+
+ s->tr = tr;
+ return 0;
+}
+
+static int
+chapresp(ServerState *s, char *user, char *resp)
+{
+ char tabuf[TICKETLEN+AUTHENTLEN];
+ char trbuf[TICKREQLEN];
+ int len;
+ Authenticator a;
+ Ticket t;
+ Ticketreq tr;
+
+ tr = s->tr;
+ if(memrandom(tr.chal, CHALLEN) < 0)
+ return -1;
+
+ if(strlen(user) >= sizeof tr.uid){
+ werrstr("uid too long");
+ return -1;
+ }
+ strcpy(tr.uid, user);
+
+ convTR2M(&tr, trbuf);
+ if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
+ return -1;
+
+ len = strlen(resp);
+ if(xiowrite(s->asfd, resp, len) != len)
+ return -1;
+
+ if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
+ return 0;
+
+ convM2T(tabuf, &t, s->k->priv);
+ if(t.num != AuthTs
+ || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
+ werrstr("key mismatch with auth server");
+ return -1;
+ }
+
+ convM2A(tabuf+TICKETLEN, &a, t.key);
+ if(a.num != AuthAc
+ || memcmp(a.chal, tr.chal, sizeof a.chal) != 0
+ || a.id != 0){
+ werrstr("key2 mismatch with auth server");
+ return -1;
+ }
+
+ s->t = t;
+ return 1;
+}
+
+static Role
+chaproles[] =
+{
+ "client", chapclient,
+ "server", chapserver,
+ 0
+};
+
+Proto chap = {
+.name= "chap",
+.roles= chaproles,
+.checkkey= chapcheck,
+.keyprompt= "user? !password?",
+};
+
+Proto mschap = {
+.name= "mschap",
+.roles= chaproles,
+.checkkey= chapcheck,
+.keyprompt= "user? !password?",
+};
+