From 6e527fbc4d8f404a7eec934e5c9efaaaa92ffdff Mon Sep 17 00:00:00 2001 From: rsc Date: Sun, 13 Feb 2005 05:59:29 +0000 Subject: new auth --- src/cmd/auth/factotum/apop.c | 350 ++++++++++++ src/cmd/auth/factotum/attr.c | 231 ++++++++ src/cmd/auth/factotum/chap.c | 426 +++++++++++++++ src/cmd/auth/factotum/confirm.c | 139 +++++ src/cmd/auth/factotum/conv.c | 254 +++++++++ src/cmd/auth/factotum/cpu.c | 1117 ++++++++++++++++++++++++++++++++++++++ src/cmd/auth/factotum/ctl.c | 158 ++++++ src/cmd/auth/factotum/dat.h | 226 ++++++++ src/cmd/auth/factotum/dsa.c | 140 +++++ src/cmd/auth/factotum/fs.c | 531 ++++++++++++++++++ src/cmd/auth/factotum/key.c | 217 ++++++++ src/cmd/auth/factotum/log.c | 121 +++++ src/cmd/auth/factotum/main.c | 185 +++++++ src/cmd/auth/factotum/mkfile | 36 ++ src/cmd/auth/factotum/p9any.c | 272 ++++++++++ src/cmd/auth/factotum/p9cr.c | 545 +++++++++++++++++++ src/cmd/auth/factotum/p9sk1.c | 353 ++++++++++++ src/cmd/auth/factotum/pass.c | 100 ++++ src/cmd/auth/factotum/pkcs1.c | 154 ++++++ src/cmd/auth/factotum/plan9.c | 45 ++ src/cmd/auth/factotum/proto.c | 34 ++ src/cmd/auth/factotum/rpc.c | 315 +++++++++++ src/cmd/auth/factotum/rsa.c | 191 +++++++ src/cmd/auth/factotum/secstore.c | 644 ++++++++++++++++++++++ src/cmd/auth/factotum/std.h | 10 + src/cmd/auth/factotum/test.c | 121 +++++ src/cmd/auth/factotum/util.c | 54 ++ src/cmd/auth/factotum/x.c | 15 + src/cmd/auth/factotum/xio.c | 165 ++++++ 29 files changed, 7149 insertions(+) create mode 100644 src/cmd/auth/factotum/apop.c create mode 100644 src/cmd/auth/factotum/attr.c create mode 100644 src/cmd/auth/factotum/chap.c create mode 100644 src/cmd/auth/factotum/confirm.c create mode 100644 src/cmd/auth/factotum/conv.c create mode 100644 src/cmd/auth/factotum/cpu.c create mode 100644 src/cmd/auth/factotum/ctl.c create mode 100644 src/cmd/auth/factotum/dat.h create mode 100644 src/cmd/auth/factotum/dsa.c create mode 100644 src/cmd/auth/factotum/fs.c create mode 100644 src/cmd/auth/factotum/key.c create mode 100644 src/cmd/auth/factotum/log.c create mode 100644 src/cmd/auth/factotum/main.c create mode 100644 src/cmd/auth/factotum/mkfile create mode 100644 src/cmd/auth/factotum/p9any.c create mode 100644 src/cmd/auth/factotum/p9cr.c create mode 100644 src/cmd/auth/factotum/p9sk1.c create mode 100644 src/cmd/auth/factotum/pass.c create mode 100644 src/cmd/auth/factotum/pkcs1.c create mode 100644 src/cmd/auth/factotum/plan9.c create mode 100644 src/cmd/auth/factotum/proto.c create mode 100644 src/cmd/auth/factotum/rpc.c create mode 100644 src/cmd/auth/factotum/rsa.c create mode 100644 src/cmd/auth/factotum/secstore.c create mode 100644 src/cmd/auth/factotum/std.h create mode 100644 src/cmd/auth/factotum/test.c create mode 100644 src/cmd/auth/factotum/util.c create mode 100644 src/cmd/auth/factotum/x.c create mode 100644 src/cmd/auth/factotum/xio.c (limited to 'src/cmd/auth/factotum') diff --git a/src/cmd/auth/factotum/apop.c b/src/cmd/auth/factotum/apop.c new file mode 100644 index 00000000..8e340d47 --- /dev/null +++ b/src/cmd/auth/factotum/apop.c @@ -0,0 +1,350 @@ +/* + * APOP, CRAM - MD5 challenge/response authentication + * + * The client does not authenticate the server, hence no CAI. + * + * Protocol: + * + * S -> C: random@domain + * C -> S: hex-response + * S -> C: ok + * + * Note that this is the protocol between factotum and the local + * program, not between the two factotums. The information + * exchanged here is wrapped in the APOP protocol by the local + * programs. + * + * If S sends "bad [msg]" instead of "ok", that is a hint that the key is bad. + * The protocol goes back to "C -> S: user". + */ + +#include "std.h" +#include "dat.h" + +extern Proto apop, cram; + +static int +apopcheck(Key *k) +{ + if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ + werrstr("need user and !password attributes"); + return -1; + } + return 0; +} + +static int +apopclient(Conv *c) +{ + char *chal, *pw, *res; + int astype, nchal, npw, ntry, ret; + uchar resp[MD5dlen]; + Attr *attr; + DigestState *ds; + Key *k; + + chal = nil; + k = nil; + res = nil; + ret = -1; + attr = c->attr; + + if(c->proto == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + 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->state = "read challenge"; + if((nchal = convreadm(c, &chal)) < 0) + goto out; + + for(ntry=1;; ntry++){ + if(c->attr != attr) + freeattr(c->attr); + c->attr = addattrs(copyattr(attr), k->attr); + if((pw = strfindattr(k->privattr, "!password")) == nil){ + werrstr("key has no password (cannot happen?)"); + goto out; + } + npw = strlen(pw); + + switch(astype){ + case AuthApop: + ds = md5((uchar*)chal, nchal, nil, nil); + md5((uchar*)pw, npw, resp, ds); + break; + case AuthCram: + hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil); + break; + } + + /* C->S: APOP user hex-response\n */ + if(ntry == 1) + c->state = "write user"; + else{ + sprint(c->statebuf, "write user (auth attempt #%d)", ntry); + c->state = c->statebuf; + } + if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0) + goto out; + + c->state = "write response"; + if(convprint(c, "%.*H", sizeof resp, resp) < 0) + goto out; + + c->state = "read result"; + if(convreadm(c, &res) < 0) + goto out; + + if(strcmp(res, "ok") == 0) + break; + + if(strncmp(res, "bad ", 4) != 0){ + werrstr("bad result: %s", res); + goto out; + } + + c->state = "replace key"; + if((k = keyreplace(c, k, "%s", res+4)) == nil){ + c->state = "auth failed"; + werrstr("%s", res+4); + goto out; + } + free(res); + res = nil; + } + + werrstr("succeeded"); + ret = 0; + +out: + 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; +}; + +enum +{ + APOPCHALLEN = 128, +}; + +static int apopchal(ServerState*, int, char[APOPCHALLEN]); +static int apopresp(ServerState*, char*, char*); + +static int +apopserver(Conv *c) +{ + char chal[APOPCHALLEN], *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 == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + 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(apopchal(&s, astype, chal) < 0) + goto out; + + c->state = "write challenge"; + if(convprint(c, "%s", chal) < 0) + goto out; + + for(;;){ + 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(apopresp(&s, user, resp)){ + case -1: + goto out; + case 0: + c->state = "write status"; + if(convprint(c, "bad authentication failed") < 0) + goto out; + break; + case 1: + c->state = "write status"; + if(convprint(c, "ok") < 0) + goto out; + goto ok; + } + free(user); + free(resp); + user = nil; + resp = nil; + } + +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 +apopchal(ServerState *s, int astype, char chal[APOPCHALLEN]) +{ + 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, APOPCHALLEN) <= 5) + return -1; + + s->tr = tr; + return 0; +} + +static int +apopresp(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 +apoproles[] = +{ + "client", apopclient, + "server", apopserver, + 0 +}; + +Proto apop = { +.name= "apop", +.roles= apoproles, +.checkkey= apopcheck, +.keyprompt= "user? !password?", +}; + +Proto cram = { +.name= "cram", +.roles= apoproles, +.checkkey= apopcheck, +.keyprompt= "user? !password?", +}; diff --git a/src/cmd/auth/factotum/attr.c b/src/cmd/auth/factotum/attr.c new file mode 100644 index 00000000..2f2511b9 --- /dev/null +++ b/src/cmd/auth/factotum/attr.c @@ -0,0 +1,231 @@ +#include "std.h" +#include "dat.h" + +Attr* +addattr(Attr *a, char *fmt, ...) +{ + char buf[8192]; + va_list arg; + Attr *b; + + va_start(arg, fmt); + vseprint(buf, buf+sizeof buf, fmt, arg); + va_end(arg); + b = _parseattr(buf); + a = addattrs(a, b); + setmalloctag(a, getcallerpc(&a)); + _freeattr(b); + return a; +} + +/* + * add attributes in list b to list a. If any attributes are in + * both lists, replace those in a by those in b. + */ +Attr* +addattrs(Attr *a, Attr *b) +{ + int found; + Attr **l, *aa; + + for(; b; b=b->next){ + switch(b->type){ + case AttrNameval: + for(l=&a; *l; ){ + if(strcmp((*l)->name, b->name) != 0){ + l=&(*l)->next; + continue; + } + aa = *l; + *l = aa->next; + aa->next = nil; + freeattr(aa); + } + *l = mkattr(AttrNameval, b->name, b->val, nil); + break; + case AttrQuery: + found = 0; + for(l=&a; *l; l=&(*l)->next) + if((*l)->type==AttrNameval && strcmp((*l)->name, b->name) == 0) + found++; + if(!found) + *l = mkattr(AttrQuery, b->name, b->val, nil); + break; + } + } + return a; +} + +void +setmalloctaghere(void *v) +{ + setmalloctag(v, getcallerpc(&v)); +} + +Attr* +sortattr(Attr *a) +{ + int i; + Attr *anext, *a0, *a1, **l; + + if(a == nil || a->next == nil) + return a; + + /* cut list in halves */ + a0 = nil; + a1 = nil; + i = 0; + for(; a; a=anext){ + anext = a->next; + if(i++%2){ + a->next = a0; + a0 = a; + }else{ + a->next = a1; + a1 = a; + } + } + + /* sort */ + a0 = sortattr(a0); + a1 = sortattr(a1); + + /* merge */ + l = &a; + while(a0 || a1){ + if(a1==nil){ + anext = a0; + a0 = a0->next; + }else if(a0==nil){ + anext = a1; + a1 = a1->next; + }else if(strcmp(a0->name, a1->name) < 0){ + anext = a0; + a0 = a0->next; + }else{ + anext = a1; + a1 = a1->next; + } + *l = anext; + l = &(*l)->next; + } + *l = nil; + return a; +} + +int +attrnamefmt(Fmt *fmt) +{ + char *b, buf[8192], *ebuf; + Attr *a; + + ebuf = buf+sizeof buf; + b = buf; + strcpy(buf, " "); + for(a=va_arg(fmt->args, Attr*); a; a=a->next){ + if(a->name == nil) + continue; + b = seprint(b, ebuf, " %q?", a->name); + } + return fmtstrcpy(fmt, buf+1); +} + +/* +static int +hasqueries(Attr *a) +{ + for(; a; a=a->next) + if(a->type == AttrQuery) + return 1; + return 0; +} +*/ + +char *ignored[] = { + "role", + "disabled", +}; + +static int +ignoreattr(char *s) +{ + int i; + + for(i=0; inext, name)) + if(strcmp(a->val, val) == 0) + return 1; + for(a=_findattr(a1, name); a; a=_findattr(a->next, name)) + if(strcmp(a->val, val) == 0) + return 1; + return 0; +} + +int +matchattr(Attr *pat, Attr *a0, Attr *a1) +{ + int type; + + for(; pat; pat=pat->next){ + type = pat->type; + if(ignoreattr(pat->name)) + type = AttrDefault; + switch(type){ + case AttrQuery: /* name=something be present */ + if(!hasname(a0, a1, pat->name)) + return 0; + break; + case AttrNameval: /* name=val must be present */ + if(!hasnameval(a0, a1, pat->name, pat->val)) + return 0; + break; + case AttrDefault: /* name=val must be present if name=anything is present */ + if(hasname(a0, a1, pat->name) && !hasnameval(a0, a1, pat->name, pat->val)) + return 0; + break; + } + } + return 1; +} + +Attr* +parseattrfmtv(char *fmt, va_list arg) +{ + char *s; + Attr *a; + + s = vsmprint(fmt, arg); + if(s == nil) + sysfatal("vsmprint: out of memory"); + a = parseattr(s); + free(s); + return a; +} + +Attr* +parseattrfmt(char *fmt, ...) +{ + va_list arg; + Attr *a; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + return a; +} 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= '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?", +}; + diff --git a/src/cmd/auth/factotum/confirm.c b/src/cmd/auth/factotum/confirm.c new file mode 100644 index 00000000..8f492450 --- /dev/null +++ b/src/cmd/auth/factotum/confirm.c @@ -0,0 +1,139 @@ +#include "std.h" +#include "dat.h" + +Logbuf confbuf; + +void +confirmread(Req *r) +{ + lbread(&confbuf, r); +} + +void +confirmflush(Req *r) +{ + lbflush(&confbuf, r); +} + +int +confirmwrite(char *s) +{ + char *t, *ans; + int allow; + ulong tag; + Attr *a; + Conv *c; + + a = _parseattr(s); + if(a == nil){ + werrstr("bad attr"); + return -1; + } + if((t = _strfindattr(a, "tag")) == nil){ + werrstr("no tag"); + return -1; + } + tag = strtoul(t, 0, 0); + if((ans = _strfindattr(a, "answer")) == nil){ + werrstr("no answer"); + return -1; + } + if(strcmp(ans, "yes") == 0) + allow = 1; + else if(strcmp(ans, "no") == 0) + allow = 0; + else{ + werrstr("bad answer"); + return -1; + } + for(c=conv; c; c=c->next){ + if(tag == c->tag){ + nbsendul(c->keywait, allow); + break; + } + } + if(c == nil){ + werrstr("tag not found"); + return -1; + } + return 0; +} + +int +confirmkey(Conv *c, Key *k) +{ + if(*confirminuse == 0) + return -1; + + lbappend(&confbuf, "confirm tag=%lud %A %N", c->tag, k->attr, k->privattr); + c->state = "keyconfirm"; + return recvul(c->keywait); +} + +Logbuf needkeybuf; + +void +needkeyread(Req *r) +{ + lbread(&needkeybuf, r); +} + +void +needkeyflush(Req *r) +{ + lbflush(&needkeybuf, r); +} + +int +needkeywrite(char *s) +{ + char *t; + ulong tag; + Attr *a; + Conv *c; + + a = _parseattr(s); + if(a == nil){ + werrstr("empty write"); + return -1; + } + if((t = _strfindattr(a, "tag")) == nil){ + werrstr("no tag"); + freeattr(a); + return -1; + } + tag = strtoul(t, 0, 0); + for(c=conv; c; c=c->next) + if(c->tag == tag){ + nbsendul(c->keywait, 0); + break; + } + if(c == nil){ + werrstr("tag not found"); + freeattr(a); + return -1; + } + freeattr(a); + return 0; +} + +int +needkey(Conv *c, Attr *a) +{ + if(c == nil || *needkeyinuse == 0) + return -1; + + lbappend(&needkeybuf, "needkey tag=%lud %A", c->tag, a); + return nbrecvul(c->keywait); +} + +int +badkey(Conv *c, Key *k, char *msg, Attr *a) +{ + if(c == nil || *needkeyinuse == 0) + return -1; + + lbappend(&needkeybuf, "badkey tag=%lud %A %N\n%s\n%A", + c->tag, k->attr, k->privattr, msg, a); + return nbrecvul(c->keywait); +} diff --git a/src/cmd/auth/factotum/conv.c b/src/cmd/auth/factotum/conv.c new file mode 100644 index 00000000..862993f9 --- /dev/null +++ b/src/cmd/auth/factotum/conv.c @@ -0,0 +1,254 @@ +#include "std.h" +#include "dat.h" + +Conv *conv; + +ulong taggen = 1; + +Conv* +convalloc(char *sysuser) +{ + Conv *c; + + c = mallocz(sizeof(Conv), 1); + if(c == nil) + return nil; + c->ref = 1; + c->tag = taggen++; + c->next = conv; + c->sysuser = estrdup(sysuser); + c->state = "nascent"; + c->rpcwait = chancreate(sizeof(void*), 0); + c->keywait = chancreate(sizeof(void*), 0); + strcpy(c->err, "protocol has not started"); + conv = c; + convreset(c); + return c; +} + +void +convreset(Conv *c) +{ + if(c->ref != 1){ + c->hangup = 1; + nbsendp(c->rpcwait, 0); + while(c->ref > 1) + yield(); + c->hangup = 0; + } + c->state = "nascent"; + c->err[0] = '\0'; + freeattr(c->attr); + c->attr = nil; + c->proto = nil; + c->rpc.op = 0; + c->active = 0; + c->done = 0; + c->hangup = 0; +} + +void +convhangup(Conv *c) +{ + c->hangup = 1; + c->rpc.op = 0; + (*c->kickreply)(c); + nbsendp(c->rpcwait, 0); +} + +void +convclose(Conv *c) +{ + Conv *p; + + if(c == nil) + return; + + if(--c->ref > 0) + return; + + if(c == conv){ + conv = c->next; + goto free; + } + for(p=conv; p && p->next!=c; p=p->next) + ; + if(p == nil){ + print("cannot find conv in list\n"); + return; + } + p->next = c->next; + +free: + c->next = nil; + free(c); +} + +static Rpc* +convgetrpc(Conv *c, int want) +{ + for(;;){ + if(c->hangup){ + werrstr("hangup"); + return nil; + } + if(c->rpc.op == RpcUnknown){ + recvp(c->rpcwait); + if(c->hangup){ + werrstr("hangup"); + return nil; + } + if(c->rpc.op == RpcUnknown) + continue; + } + if(want < 0 || c->rpc.op == want) + return &c->rpc; + rpcrespond(c, "phase in state '%s' want '%s'", c->state, rpcname[want]); + } + return nil; /* not reached */ +} + +/* read until the done function tells us that's enough */ +int +convreadfn(Conv *c, int (*done)(void*, int), char **ps) +{ + int n; + Rpc *r; + char *s; + + for(;;){ + r = convgetrpc(c, RpcWrite); + if(r == nil) + return -1; + n = (*done)(r->data, r->count); + if(n == r->count) + break; + rpcrespond(c, "toosmall %d", n); + } + + s = emalloc(r->count+1); + memmove(s, r->data, r->count); + s[r->count] = 0; + *ps = s; + rpcrespond(c, "ok"); + return r->count; +} + +/* + * read until we get a non-zero write. assumes remote side + * knows something about the protocol (is not auth_proxy). + * the remote side typically won't bother with the zero-length + * write to find out the length -- the loop is there only so the + * test program can call auth_proxy on both sides of a pipe + * to play a conversation. + */ +int +convreadm(Conv *c, char **ps) +{ + char *s; + Rpc *r; + + for(;;){ + r = convgetrpc(c, RpcWrite); + if(r == nil) + return -1; + if(r->count > 0) + break; + rpcrespond(c, "toosmall %d", AuthRpcMax); + } + s = emalloc(r->count+1); + memmove(s, r->data, r->count); + s[r->count] = 0; + *ps = s; + rpcrespond(c, "ok"); + return r->count; +} + +/* read exactly count bytes */ +int +convread(Conv *c, void *data, int count) +{ + Rpc *r; + + for(;;){ + r = convgetrpc(c, RpcWrite); + if(r == nil) + return -1; + if(r->count == count) + break; + if(r->count < count) + rpcrespond(c, "toosmall %d", count); + else + rpcrespond(c, "error too much data; want %d got %d", count, r->count); + } + memmove(data, r->data, count); + rpcrespond(c, "ok"); + return 0; +} + +/* write exactly count bytes */ +int +convwrite(Conv *c, void *data, int count) +{ + Rpc *r; + + for(;;){ + r = convgetrpc(c, RpcRead); + if(r == nil) + return -1; + break; + } + rpcrespondn(c, "ok", data, count); + return 0; +} + +/* print to the conversation */ +int +convprint(Conv *c, char *fmt, ...) +{ + char *s; + va_list arg; + int ret; + + va_start(arg, fmt); + s = vsmprint(fmt, arg); + va_end(arg); + if(s == nil) + return -1; + ret = convwrite(c, s, strlen(s)); + free(s); + return ret; +} + +/* ask for a key */ +int +convneedkey(Conv *c, Attr *a) +{ + /* + * Piggyback key requests in the usual RPC channel. + * Wait for the next RPC and then send a key request + * in response. The keys get added out-of-band (via the + * ctl file), so assume the key has been added when the + * next request comes in. + */ + if(convgetrpc(c, -1) == nil) + return -1; + rpcrespond(c, "needkey %A", a); + if(convgetrpc(c, -1) == nil) + return -1; + return 0; +} + +/* ask for a replacement for a bad key*/ +int +convbadkey(Conv *c, Key *k, char *msg, Attr *a) +{ + if(convgetrpc(c, -1) == nil) + return -1; + rpcrespond(c, "badkey %A %N\n%s\n%A", + k->attr, k->privattr, msg, a); + if(convgetrpc(c, -1) == nil) + return -1; + return 0; +} + diff --git a/src/cmd/auth/factotum/cpu.c b/src/cmd/auth/factotum/cpu.c new file mode 100644 index 00000000..da8280ad --- /dev/null +++ b/src/cmd/auth/factotum/cpu.c @@ -0,0 +1,1117 @@ +/* + * cpu.c - Make a connection to a cpu server + * + * Invoked by listen as 'cpu -R | -N service net netdir' + * by users as 'cpu [-h system] [-c cmd args ...]' + */ + +#include +#include +#include +#include +#include +#include + +#define Maxfdata 8192 + +void remoteside(int); +void fatal(int, char*, ...); +void lclnoteproc(int); +void rmtnoteproc(void); +void catcher(void*, char*); +void usage(void); +void writestr(int, char*, char*, int); +int readstr(int, char*, int); +char *rexcall(int*, char*, char*); +int setamalg(char*); + +int notechan; +char system[32]; +int cflag; +int hflag; +int dbg; +char *user; + +char *srvname = "ncpu"; +char *exportfs = "/bin/exportfs"; +char *ealgs = "rc4_256 sha1"; + +/* message size for exportfs; may be larger so we can do big graphics in CPU window */ +int msgsize = 8192+IOHDRSZ; + +/* authentication mechanisms */ +static int netkeyauth(int); +static int netkeysrvauth(int, char*); +static int p9auth(int); +static int srvp9auth(int, char*); +static int noauth(int); +static int srvnoauth(int, char*); + +typedef struct AuthMethod AuthMethod; +struct AuthMethod { + char *name; /* name of method */ + int (*cf)(int); /* client side authentication */ + int (*sf)(int, char*); /* server side authentication */ +} authmethod[] = +{ + { "p9", p9auth, srvp9auth,}, + { "netkey", netkeyauth, netkeysrvauth,}, +// { "none", noauth, srvnoauth,}, + { nil, nil} +}; +AuthMethod *am = authmethod; /* default is p9 */ + +char *p9authproto = "p9any"; + +int setam(char*); + +void +usage(void) +{ + fprint(2, "usage: cpu [-h system] [-a authmethod] [-e 'crypt hash'] [-c cmd args ...]\n"); + exits("usage"); +} +int fdd; + +void +main(int argc, char **argv) +{ + char dat[128], buf[128], cmd[128], *p, *err; + int fd, ms, kms, data; + + /* see if we should use a larger message size */ + fd = open("/dev/draw", OREAD); + if(fd > 0){ + ms = iounit(fd); + if(ms != 0 && ms < ms+IOHDRSZ) + msgsize = ms+IOHDRSZ; + close(fd); + } + kms = kiounit(); + if(msgsize > kms-IOHDRSZ-100) /* 100 for network packets, etc. */ + msgsize = kms-IOHDRSZ-100; + + user = getuser(); + if(user == nil) + fatal(1, "can't read user name"); + ARGBEGIN{ + case 'a': + p = EARGF(usage()); + if(setam(p) < 0) + fatal(0, "unknown auth method %s", p); + break; + case 'e': + ealgs = EARGF(usage()); + if(*ealgs == 0 || strcmp(ealgs, "clear") == 0) + ealgs = nil; + break; + case 'd': + dbg++; + break; + case 'f': + /* ignored but accepted for compatibility */ + break; + case 'O': + p9authproto = "p9sk2"; + remoteside(1); /* From listen */ + break; + case 'R': /* From listen */ + remoteside(0); + break; + case 'h': + hflag++; + p = EARGF(usage()); + strcpy(system, p); + break; + case 'c': + cflag++; + cmd[0] = '!'; + cmd[1] = '\0'; + while(p = ARGF()) { + strcat(cmd, " "); + strcat(cmd, p); + } + break; + case 'o': + p9authproto = "p9sk2"; + srvname = "cpu"; + break; + case 'u': + user = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + + if(argc != 0) + usage(); + + if(hflag == 0) { + p = getenv("cpu"); + if(p == 0) + fatal(0, "set $cpu"); + strcpy(system, p); + } + + if(err = rexcall(&data, system, srvname)) + fatal(1, "%s: %s", err, system); + + /* Tell the remote side the command to execute and where our working directory is */ + if(cflag) + writestr(data, cmd, "command", 0); + if(getwd(dat, sizeof(dat)) == 0) + writestr(data, "NO", "dir", 0); + else + writestr(data, dat, "dir", 0); + + /* start up a process to pass along notes */ + lclnoteproc(data); + + /* + * Wait for the other end to execute and start our file service + * of /mnt/term + */ + if(readstr(data, buf, sizeof(buf)) < 0) + fatal(1, "waiting for FS"); + if(strncmp("FS", buf, 2) != 0) { + print("remote cpu: %s", buf); + exits(buf); + } + + /* Begin serving the gnot namespace */ + close(0); + dup(data, 0); + close(data); + sprint(buf, "%d", msgsize); + if(dbg) + execl(exportfs, exportfs, "-dm", buf, 0); + else + execl(exportfs, exportfs, "-m", buf, 0); + fatal(1, "starting exportfs"); +} + +void +fatal(int syserr, char *fmt, ...) +{ + char buf[ERRMAX]; + va_list arg; + + va_start(arg, fmt); + doprint(buf, buf+sizeof(buf), fmt, arg); + va_end(arg); + if(syserr) + fprint(2, "cpu: %s: %r\n", buf); + else + fprint(2, "cpu: %s\n", buf); + exits(buf); +} + +char *negstr = "negotiating authentication method"; + +char bug[256]; + +int +old9p(int fd) +{ + int p[2]; + + if(pipe(p) < 0) + fatal(1, "pipe"); + + switch(rfork(RFPROC|RFFDG|RFNAMEG)) { + case -1: + fatal(1, "rfork srvold9p"); + case 0: + if(fd != 1){ + dup(fd, 1); + close(fd); + } + if(p[0] != 0){ + dup(p[0], 0); + close(p[0]); + } + close(p[1]); + if(0){ + fd = open("/sys/log/cpu", OWRITE); + if(fd != 2){ + dup(fd, 2); + close(fd); + } + execl("/bin/srvold9p", "srvold9p", "-ds", 0); + } else + execl("/bin/srvold9p", "srvold9p", "-s", 0); + fatal(1, "exec srvold9p"); + default: + close(fd); + close(p[0]); + } + return p[1]; +} + +/* Invoked with stdin, stdout and stderr connected to the network connection */ +void +remoteside(int old) +{ + char user[128], home[128], buf[128], xdir[128], cmd[128]; + int i, n, fd, badchdir, gotcmd; + + fd = 0; + + /* negotiate authentication mechanism */ + n = readstr(fd, cmd, sizeof(cmd)); + if(n < 0) + fatal(1, "authenticating"); + if(setamalg(cmd) < 0){ + writestr(fd, "unsupported auth method", nil, 0); + fatal(1, "bad auth method %s", cmd); + } else + writestr(fd, "", "", 1); + + fd = (*am->sf)(fd, user); + if(fd < 0) + fatal(1, "srvauth"); + + /* Set environment values for the user */ + putenv("user", user); + sprint(home, "/usr/%s", user); + putenv("home", home); + + /* Now collect invoking cpu's current directory or possibly a command */ + gotcmd = 0; + if(readstr(fd, xdir, sizeof(xdir)) < 0) + fatal(1, "dir/cmd"); + if(xdir[0] == '!') { + strcpy(cmd, &xdir[1]); + gotcmd = 1; + if(readstr(fd, xdir, sizeof(xdir)) < 0) + fatal(1, "dir"); + } + + /* Establish the new process at the current working directory of the + * gnot */ + badchdir = 0; + if(strcmp(xdir, "NO") == 0) + chdir(home); + else if(chdir(xdir) < 0) { + badchdir = 1; + chdir(home); + } + + /* Start the gnot serving its namespace */ + writestr(fd, "FS", "FS", 0); + writestr(fd, "/", "exportfs dir", 0); + + n = read(fd, buf, sizeof(buf)); + if(n != 2 || buf[0] != 'O' || buf[1] != 'K') + exits("remote tree"); + + if(old) + fd = old9p(fd); + + /* make sure buffers are big by doing fversion explicitly; pick a huge number; other side will trim */ + strcpy(buf, VERSION9P); + if(fversion(fd, 64*1024, buf, sizeof buf) < 0) + exits("fversion failed"); + if(mount(fd, -1, "/mnt/term", MCREATE|MREPL, "") < 0) + exits("mount failed"); + + close(fd); + + /* the remote noteproc uses the mount so it must follow it */ + rmtnoteproc(); + + for(i = 0; i < 3; i++) + close(i); + + if(open("/mnt/term/dev/cons", OREAD) != 0) + exits("open stdin"); + if(open("/mnt/term/dev/cons", OWRITE) != 1) + exits("open stdout"); + dup(1, 2); + + if(badchdir) + print("cpu: failed to chdir to '%s'\n", xdir); + + if(gotcmd) + execl("/bin/rc", "rc", "-lc", cmd, 0); + else + execl("/bin/rc", "rc", "-li", 0); + fatal(1, "exec shell"); +} + +char* +rexcall(int *fd, char *host, char *service) +{ + char *na; + char dir[128]; + char err[ERRMAX]; + char msg[128]; + int n; + + na = netmkaddr(host, 0, service); + if((*fd = dial(na, 0, dir, 0)) < 0) + return "can't dial"; + + /* negotiate authentication mechanism */ + if(ealgs != nil) + snprint(msg, sizeof(msg), "%s %s", am->name, ealgs); + else + snprint(msg, sizeof(msg), "%s", am->name); + writestr(*fd, msg, negstr, 0); + n = readstr(*fd, err, sizeof err); + if(n < 0) + return negstr; + if(*err){ + werrstr(err); + return negstr; + } + + /* authenticate */ + *fd = (*am->cf)(*fd); + if(*fd < 0) + return "can't authenticate"; + return 0; +} + +void +writestr(int fd, char *str, char *thing, int ignore) +{ + int l, n; + + l = strlen(str); + n = write(fd, str, l+1); + if(!ignore && n < 0) + fatal(1, "writing network: %s", thing); +} + +int +readstr(int fd, char *str, int len) +{ + int n; + + while(len) { + n = read(fd, str, 1); + if(n < 0) + return -1; + if(*str == '\0') + return 0; + str++; + len--; + } + return -1; +} + +static int +readln(char *buf, int n) +{ + char *p = buf; + + n--; + while(n > 0){ + if(read(0, p, 1) != 1) + break; + if(*p == '\n' || *p == '\r'){ + *p = 0; + return p-buf; + } + p++; + } + *p = 0; + return p-buf; +} + +/* + * user level challenge/response + */ +static int +netkeyauth(int fd) +{ + char chall[32]; + char resp[32]; + + strcpy(chall, getuser()); + print("user[%s]: ", chall); + if(readln(resp, sizeof(resp)) < 0) + return -1; + if(*resp != 0) + strcpy(chall, resp); + writestr(fd, chall, "challenge/response", 1); + + for(;;){ + if(readstr(fd, chall, sizeof chall) < 0) + break; + if(*chall == 0) + return fd; + print("challenge: %s\nresponse: ", chall); + if(readln(resp, sizeof(resp)) < 0) + break; + writestr(fd, resp, "challenge/response", 1); + } + return -1; +} + +static int +netkeysrvauth(int fd, char *user) +{ + char response[32]; + Chalstate *ch; + int tries; + AuthInfo *ai; + + if(readstr(fd, user, 32) < 0) + return -1; + + ai = nil; + ch = nil; + for(tries = 0; tries < 10; tries++){ + if((ch = auth_challenge("p9cr", user, nil)) == nil) + return -1; + writestr(fd, ch->chal, "challenge", 1); + if(readstr(fd, response, sizeof response) < 0) + return -1; + ch->resp = response; + ch->nresp = strlen(response); + if((ai = auth_response(ch)) != nil) + break; + } + auth_freechal(ch); + if(ai == nil) + return -1; + writestr(fd, "", "challenge", 1); + if(auth_chuid(ai, 0) < 0) + fatal(1, "newns"); + auth_freeAI(ai); + return fd; +} + +static void +mksecret(char *t, uchar *f) +{ + sprint(t, "%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux", + f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9]); +} + +/* + * plan9 authentication followed by rc4 encryption + */ +static int +p9auth(int fd) +{ + uchar key[16]; + uchar digest[SHA1dlen]; + char fromclientsecret[21]; + char fromserversecret[21]; + int i; + AuthInfo *ai; + + ai = auth_proxy(fd, auth_getkey, "proto=%q user=%q role=client", p9authproto, user); + if(ai == nil) + return -1; + memmove(key+4, ai->secret, ai->nsecret); + if(ealgs == nil) + return fd; + + /* exchange random numbers */ + srand(truerand()); + for(i = 0; i < 4; i++) + key[i] = rand(); + if(write(fd, key, 4) != 4) + return -1; + if(readn(fd, key+12, 4) != 4) + return -1; + + /* scramble into two secrets */ + sha1(key, sizeof(key), digest, nil); + mksecret(fromclientsecret, digest); + mksecret(fromserversecret, digest+10); + + /* set up encryption */ + i = pushssl(fd, ealgs, fromclientsecret, fromserversecret, nil); + if(i < 0) + werrstr("can't establish ssl connection: %r"); + return i; +} + +static int +noauth(int fd) +{ + ealgs = nil; + return fd; +} + +static int +srvnoauth(int fd, char *user) +{ + strcpy(user, getuser()); + ealgs = nil; + return fd; +} + +void +loghex(uchar *p, int n) +{ + char buf[100]; + int i; + + for(i = 0; i < n; i++) + sprint(buf+2*i, "%2.2ux", p[i]); + syslog(0, "cpu", buf); +} + +static int +srvp9auth(int fd, char *user) +{ + uchar key[16]; + uchar digest[SHA1dlen]; + char fromclientsecret[21]; + char fromserversecret[21]; + int i; + AuthInfo *ai; + + ai = auth_proxy(0, nil, "proto=%q role=server", p9authproto); + if(ai == nil) + return -1; + if(auth_chuid(ai, nil) < 0) + return -1; + strcpy(user, ai->cuid); + memmove(key+4, ai->secret, ai->nsecret); + + if(ealgs == nil) + return fd; + + /* exchange random numbers */ + srand(truerand()); + for(i = 0; i < 4; i++) + key[i+12] = rand(); + if(readn(fd, key, 4) != 4) + return -1; + if(write(fd, key+12, 4) != 4) + return -1; + + /* scramble into two secrets */ + sha1(key, sizeof(key), digest, nil); + mksecret(fromclientsecret, digest); + mksecret(fromserversecret, digest+10); + + /* set up encryption */ + i = pushssl(fd, ealgs, fromserversecret, fromclientsecret, nil); + if(i < 0) + werrstr("can't establish ssl connection: %r"); + return i; +} + +/* + * set authentication mechanism + */ +int +setam(char *name) +{ + for(am = authmethod; am->name != nil; am++) + if(strcmp(am->name, name) == 0) + return 0; + am = authmethod; + return -1; +} + +/* + * set authentication mechanism and encryption/hash algs + */ +int +setamalg(char *s) +{ + ealgs = strchr(s, ' '); + if(ealgs != nil) + *ealgs++ = 0; + return setam(s); +} + +char *rmtnotefile = "/mnt/term/dev/cpunote"; + +/* + * loop reading /mnt/term/dev/note looking for notes. + * The child returns to start the shell. + */ +void +rmtnoteproc(void) +{ + int n, fd, pid, notepid; + char buf[256]; + + /* new proc returns to start shell */ + pid = rfork(RFPROC|RFFDG|RFNOTEG|RFNAMEG|RFMEM); + switch(pid){ + case -1: + syslog(0, "cpu", "cpu -R: can't start noteproc: %r"); + return; + case 0: + return; + } + + /* new proc reads notes from other side and posts them to shell */ + switch(notepid = rfork(RFPROC|RFFDG|RFMEM)){ + case -1: + syslog(0, "cpu", "cpu -R: can't start wait proc: %r"); + _exits(0); + case 0: + fd = open(rmtnotefile, OREAD); + if(fd < 0){ + syslog(0, "cpu", "cpu -R: can't open %s", rmtnotefile); + _exits(0); + } + + for(;;){ + n = read(fd, buf, sizeof(buf)-1); + if(n <= 0){ + postnote(PNGROUP, pid, "hangup"); + _exits(0); + } + buf[n] = 0; + postnote(PNGROUP, pid, buf); + } + break; + } + + /* original proc waits for shell proc to die and kills note proc */ + for(;;){ + n = waitpid(); + if(n < 0 || n == pid) + break; + } + postnote(PNPROC, notepid, "kill"); + _exits(0); +} + +enum +{ + Qdir, + Qcpunote, + + Nfid = 32, +}; + +struct { + char *name; + Qid qid; + ulong perm; +} fstab[] = +{ + [Qdir] { ".", {Qdir, 0, QTDIR}, DMDIR|0555 }, + [Qcpunote] { "cpunote", {Qcpunote, 0}, 0444 }, +}; + +typedef struct Note Note; +struct Note +{ + Note *next; + char msg[ERRMAX]; +}; + +typedef struct Request Request; +struct Request +{ + Request *next; + Fcall f; +}; + +typedef struct Fid Fid; +struct Fid +{ + int fid; + int file; +}; +Fid fids[Nfid]; + +struct { + Lock; + Note *nfirst, *nlast; + Request *rfirst, *rlast; +} nfs; + +int +fsreply(int fd, Fcall *f) +{ + uchar buf[IOHDRSZ+Maxfdata]; + int n; + + if(dbg) + fprint(2, "<-%F\n", f); + n = convS2M(f, buf, sizeof buf); + if(n > 0){ + if(write(fd, buf, n) != n){ + close(fd); + return -1; + } + } + return 0; +} + +/* match a note read request with a note, reply to the request */ +int +kick(int fd) +{ + Request *rp; + Note *np; + int rv; + + for(;;){ + lock(&nfs); + rp = nfs.rfirst; + np = nfs.nfirst; + if(rp == nil || np == nil){ + unlock(&nfs); + break; + } + nfs.rfirst = rp->next; + nfs.nfirst = np->next; + unlock(&nfs); + + rp->f.type = Rread; + rp->f.count = strlen(np->msg); + rp->f.data = np->msg; + rv = fsreply(fd, &rp->f); + free(rp); + free(np); + if(rv < 0) + return -1; + } + return 0; +} + +void +flushreq(int tag) +{ + Request **l, *rp; + + lock(&nfs); + for(l = &nfs.rfirst; *l != nil; l = &(*l)->next){ + rp = *l; + if(rp->f.tag == tag){ + *l = rp->next; + unlock(&nfs); + free(rp); + return; + } + } + unlock(&nfs); +} + +Fid* +getfid(int fid) +{ + int i, freefid; + + freefid = -1; + for(i = 0; i < Nfid; i++){ + if(freefid < 0 && fids[i].file < 0) + freefid = i; + if(fids[i].fid == fid) + return &fids[i]; + } + if(freefid >= 0){ + fids[freefid].fid = fid; + return &fids[freefid]; + } + return nil; +} + +int +fsstat(int fd, Fid *fid, Fcall *f) +{ + Dir d; + uchar statbuf[256]; + + memset(&d, 0, sizeof(d)); + d.name = fstab[fid->file].name; + d.uid = user; + d.gid = user; + d.muid = user; + d.qid = fstab[fid->file].qid; + d.mode = fstab[fid->file].perm; + d.atime = d.mtime = time(0); + f->stat = statbuf; + f->nstat = convD2M(&d, statbuf, sizeof statbuf); + return fsreply(fd, f); +} + +int +fsread(int fd, Fid *fid, Fcall *f) +{ + Dir d; + uchar buf[256]; + Request *rp; + + switch(fid->file){ + default: + return -1; + case Qdir: + if(f->offset == 0 && f->count >0){ + memset(&d, 0, sizeof(d)); + d.name = fstab[Qcpunote].name; + d.uid = user; + d.gid = user; + d.muid = user; + d.qid = fstab[Qcpunote].qid; + d.mode = fstab[Qcpunote].perm; + d.atime = d.mtime = time(0); + f->count = convD2M(&d, buf, sizeof buf); + f->data = (char*)buf; + } else + f->count = 0; + return fsreply(fd, f); + case Qcpunote: + rp = mallocz(sizeof(*rp), 1); + if(rp == nil) + return -1; + rp->f = *f; + lock(&nfs); + if(nfs.rfirst == nil) + nfs.rfirst = rp; + else + nfs.rlast->next = rp; + nfs.rlast = rp; + unlock(&nfs); + return kick(fd);; + } +} + +char Eperm[] = "permission denied"; +char Enofile[] = "out of files"; +char Enotdir[] = "not a directory"; + +void +notefs(int fd) +{ + uchar buf[IOHDRSZ+Maxfdata]; + int i, j, n; + char err[ERRMAX]; + Fcall f; + Fid *fid, *nfid; + int doreply; + + rfork(RFNOTEG); + fmtinstall('F', fcallconv); + + for(n = 0; n < Nfid; n++) + fids[n].file = -1; + + for(;;){ + n = read9pmsg(fd, buf, sizeof(buf)); + if(n <= 0){ + if(dbg) + fprint(2, "read9pmsg(%d) returns %d: %r\n", fd, n); + break; + } + if(convM2S(buf, n, &f) < 0) + break; + if(dbg) + fprint(2, "->%F\n", &f); + doreply = 1; + fid = getfid(f.fid); + if(fid == nil){ +nofids: + f.type = Rerror; + f.ename = Enofile; + fsreply(fd, &f); + continue; + } + switch(f.type++){ + default: + f.type = Rerror; + f.ename = "unknown type"; + break; + case Tflush: + flushreq(f.oldtag); + break; + case Tversion: + if(f.msize > IOHDRSZ+Maxfdata) + f.msize = IOHDRSZ+Maxfdata; + break; + case Tauth: + f.type = Rerror; + f.ename = "cpu: authentication not required"; + break; + case Tattach: + f.qid = fstab[Qdir].qid; + fid->file = Qdir; + break; + case Twalk: + nfid = nil; + if(f.newfid != f.fid){ + nfid = getfid(f.newfid); + if(nfid == nil) + goto nofids; + nfid->file = fid->file; + fid = nfid; + } + + f.ename = nil; + for(i=0; i MAXWELEM){ + f.type = Rerror; + f.ename = "too many name elements"; + break; + } + if(fid->file != Qdir){ + f.type = Rerror; + f.ename = Enotdir; + break; + } + if(strcmp(f.wname[i], "cpunote") == 0){ + fid->file = Qcpunote; + f.wqid[i] = fstab[Qcpunote].qid; + continue; + } + f.type = Rerror; + f.ename = err; + strcpy(err, "cpu: file \""); + for(j=0; j<=i; j++){ + if(strlen(err)+1+strlen(f.wname[j])+32 > sizeof err) + break; + if(j != 0) + strcat(err, "/"); + strcat(err, f.wname[j]); + } + strcat(err, "\" does not exist"); + break; + } + if(nfid != nil && (f.ename != nil || i < f.nwname)) + nfid ->file = -1; + if(f.type != Rerror) + f.nwqid = i; + break; + case Topen: + if(f.mode != OREAD){ + f.type = Rerror; + f.ename = Eperm; + } + f.qid = fstab[fid->file].qid; + break; + case Tcreate: + f.type = Rerror; + f.ename = Eperm; + break; + case Tread: + if(fsread(fd, fid, &f) < 0) + goto err; + doreply = 0; + break; + case Twrite: + f.type = Rerror; + f.ename = Eperm; + break; + case Tclunk: + fid->file = -1; + break; + case Tremove: + f.type = Rerror; + f.ename = Eperm; + break; + case Tstat: + if(fsstat(fd, fid, &f) < 0) + goto err; + doreply = 0; + break; + case Twstat: + f.type = Rerror; + f.ename = Eperm; + break; + } + if(doreply) + if(fsreply(fd, &f) < 0) + break; + } +err: + if(dbg) + fprint(2, "notefs exiting: %r\n"); + close(fd); +} + +char notebuf[ERRMAX]; + +void +catcher(void*, char *text) +{ + int n; + + n = strlen(text); + if(n >= sizeof(notebuf)) + n = sizeof(notebuf)-1; + memmove(notebuf, text, n); + notebuf[n] = '\0'; + noted(NCONT); +} + +/* + * mount in /dev a note file for the remote side to read. + */ +void +lclnoteproc(int netfd) +{ + int exportfspid; + Waitmsg *w; + Note *np; + int pfd[2]; + + if(pipe(pfd) < 0){ + fprint(2, "cpu: can't start note proc: pipe: %r\n"); + return; + } + + /* new proc mounts and returns to start exportfs */ + switch(exportfspid = rfork(RFPROC|RFNAMEG|RFFDG|RFMEM)){ + case -1: + fprint(2, "cpu: can't start note proc: rfork: %r\n"); + return; + case 0: + close(pfd[0]); + if(mount(pfd[1], -1, "/dev", MBEFORE, "") < 0) + fprint(2, "cpu: can't mount note proc: %r\n"); + close(pfd[1]); + return; + } + + close(netfd); + close(pfd[1]); + + /* new proc listens for note file system rpc's */ + switch(rfork(RFPROC|RFNAMEG|RFMEM)){ + case -1: + fprint(2, "cpu: can't start note proc: rfork1: %r\n"); + _exits(0); + case 0: + notefs(pfd[0]); + _exits(0); + } + + /* original proc waits for notes */ + notify(catcher); + w = nil; + for(;;) { + *notebuf = 0; + free(w); + w = wait(); + if(w == nil) { + if(*notebuf == 0) + break; + np = mallocz(sizeof(Note), 1); + if(np != nil){ + strcpy(np->msg, notebuf); + lock(&nfs); + if(nfs.nfirst == nil) + nfs.nfirst = np; + else + nfs.nlast->next = np; + nfs.nlast = np; + unlock(&nfs); + kick(pfd[0]); + } + unlock(&nfs); + } else if(w->pid == exportfspid) + break; + } + + if(w == nil) + exits(nil); + exits(w->msg); +} diff --git a/src/cmd/auth/factotum/ctl.c b/src/cmd/auth/factotum/ctl.c new file mode 100644 index 00000000..df44b97d --- /dev/null +++ b/src/cmd/auth/factotum/ctl.c @@ -0,0 +1,158 @@ +#include "std.h" +#include "dat.h" + +/* + * key attr=val... - add a key + * the attr=val pairs are protocol-specific. + * for example, both of these are valid: + * key p9sk1 gre cs.bell-labs.com mysecret + * key p9sk1 gre cs.bell-labs.com 11223344556677 fmt=des7hex + * delkey ... - delete a key + * if given, the attr=val pairs are used to narrow the search + * [maybe should require a password?] + * + * debug - toggle debugging + */ + +static char *msg[] = { + "key", + "delkey", + "debug", +}; + +static int +classify(char *s) +{ + int i; + + for(i=0; i/mnt/factotum/ctl" + * and writes that (incorrectly) contain multiple key lines. + */ + if(p = strchr(a, '\n')){ + if(p[1] != '\0'){ + werrstr("multiline write not allowed"); + return -1; + } + *p = '\0'; + } + + if((p = strchr(a, ' ')) == nil) + p = ""; + else + *p++ = '\0'; + switch(classify(a)){ + default: + werrstr("unknown verb"); + return -1; + case 0: /* key */ + attr = parseattr(p); + /* separate out proto= attributes */ + lprotos = &protos; + for(l=&attr; (*l); ){ + if(strcmp((*l)->name, "proto") == 0){ + *lprotos = *l; + lprotos = &(*l)->next; + *l = (*l)->next; + }else + l = &(*l)->next; + } + *lprotos = nil; + if(protos == nil){ + werrstr("key without protos"); + freeattr(attr); + return -1; + } + + /* separate out private attributes */ + lpriv = &priv; + for(l=&attr; (*l); ){ + if((*l)->name[0] == '!'){ + *lpriv = *l; + lpriv = &(*l)->next; + *l = (*l)->next; + }else + l = &(*l)->next; + } + *lpriv = nil; + + /* add keys */ + ret = 0; + for(pa=protos; pa; pa=pa->next){ + if((proto = protolookup(pa->val)) == nil){ + werrstr("unknown proto %s", pa->val); + ret = -1; + continue; + } + if(proto->checkkey == nil){ + werrstr("proto %s does not accept keys", proto->name); + ret = -1; + continue; + } + k = emalloc(sizeof(Key)); + k->attr = mkattr(AttrNameval, "proto", proto->name, copyattr(attr)); + k->privattr = copyattr(priv); + k->ref = 1; + k->proto = proto; + if((*proto->checkkey)(k) < 0){ + ret = -1; + keyclose(k); + continue; + } + keyadd(k); + keyclose(k); + } + freeattr(attr); + freeattr(priv); + freeattr(protos); + return ret; + case 1: /* delkey */ + nmatch = 0; + attr = parseattr(p); + for(pa=attr; pa; pa=pa->next){ + if(pa->type != AttrQuery && pa->name[0]=='!'){ + werrstr("only !private? patterns are allowed for private fields"); + freeattr(attr); + return -1; + } + } + for(i=0; iattr, ring.key[i]->privattr)){ + nmatch++; + keyclose(ring.key[i]); + ring.nkey--; + memmove(&ring.key[i], &ring.key[i+1], (ring.nkey-i)*sizeof(ring.key[0])); + }else + i++; + } + freeattr(attr); + if(nmatch == 0){ + werrstr("found no keys to delete"); + return -1; + } + return 0; + case 2: /* debug */ + debug ^= 1; + return 0; + } +} diff --git a/src/cmd/auth/factotum/dat.h b/src/cmd/auth/factotum/dat.h new file mode 100644 index 00000000..11648328 --- /dev/null +++ b/src/cmd/auth/factotum/dat.h @@ -0,0 +1,226 @@ +enum +{ + MaxRpc = 2048, /* max size of any protocol message */ + + /* keep in sync with rpc.c:/rpcname */ + RpcUnknown = 0, /* Rpc.op */ + RpcAuthinfo, + RpcAttr, + RpcRead, + RpcStart, + RpcWrite, + + /* thread stack size - big buffers for printing */ + STACK = 65536, +}; + +typedef struct Conv Conv; +typedef struct Key Key; +typedef struct Logbuf Logbuf; +typedef struct Proto Proto; +typedef struct Ring Ring; +typedef struct Role Role; +typedef struct Rpc Rpc; + +struct Rpc +{ + int op; + void *data; + int count; +}; + +struct Conv +{ + int ref; /* ref count */ + int hangup; /* flag: please hang up */ + int active; /* flag: there is an active thread */ + int done; /* flag: conversation finished successfully */ + ulong tag; /* identifying tag */ + Conv *next; /* in linked list */ + char *sysuser; /* system name for user speaking to us */ + char *state; /* for debugging */ + char statebuf[128]; /* for formatted states */ + char err[ERRMAX]; /* last error */ + + Attr *attr; /* current attributes */ + Proto *proto; /* protocol */ + + Channel *rpcwait; /* wait here for an rpc */ + Rpc rpc; /* current rpc. op==RpcUnknown means none */ + char rpcbuf[MaxRpc]; /* buffer for rpc */ + char reply[MaxRpc]; /* buffer for response */ + int nreply; /* count of response */ + void (*kickreply)(Conv*); /* call to send response */ + Req *req; /* 9P call to read response */ + + Channel *keywait; /* wait here for key confirmation */ + +}; + +struct Key +{ + int ref; /* ref count */ + ulong tag; /* identifying tag: sequence number */ + Attr *attr; /* public attributes */ + Attr *privattr; /* private attributes, like !password */ + Proto *proto; /* protocol owner of key */ + void *priv; /* protocol-specific storage */ +}; + +struct Logbuf +{ + Req *wait; + Req **waitlast; + int rp; + int wp; + char *msg[128]; +}; + +struct Ring +{ + Key **key; + int nkey; +}; + +struct Proto +{ + char *name; /* name of protocol */ + Role *roles; /* list of roles and service functions */ + char *keyprompt; /* required attributes for key proto=name */ + int (*checkkey)(Key*); /* initialize k->priv or reject key */ + void (*closekey)(Key*); /* free k->priv */ +}; + +struct Role +{ + char *name; /* name of role */ + int (*fn)(Conv*); /* service function */ +}; + +extern char *authaddr; /* plan9.c */ +extern int *confirminuse; /* fs.c */ +extern Conv* conv; /* conv.c */ +extern int debug; /* main.c */ +extern char *factname; /* main.c */ +extern Srv fs; /* fs.c */ +extern int *needkeyinuse; /* fs.c */ +extern char *owner; /* main.c */ +extern Proto *prototab[]; /* main.c */ +extern Ring ring; /* key.c */ +extern char *rpcname[]; /* rpc.c */ + +extern char Easproto[]; /* err.c */ + +/* provided by lib9p */ +#define emalloc emalloc9p +#define erealloc erealloc9p +#define estrdup estrdup9p + +/* hidden in libauth */ +#define attrfmt _attrfmt +#define copyattr _copyattr +#define delattr _delattr +#define findattr _findattr +#define freeattr _freeattr +#define mkattr _mkattr +#define parseattr _parseattr +#define strfindattr _strfindattr + +extern Attr* addattr(Attr*, char*, ...); +/* #pragma varargck argpos addattr 2 */ +extern Attr* addattrs(Attr*, Attr*); +extern Attr* sortattr(Attr*); +extern int attrnamefmt(Fmt*); +/* #pragma varargck type "N" Attr* */ +extern int matchattr(Attr*, Attr*, Attr*); +extern Attr* parseattrfmt(char*, ...); +/* #pragma varargck argpos parseattrfmt 1 */ +extern Attr* parseattrfmtv(char*, va_list); + +extern void confirmflush(Req*); +extern void confirmread(Req*); +extern int confirmwrite(char*); +extern int needkey(Conv*, Attr*); +extern int badkey(Conv*, Key*, char*, Attr*); +extern int confirmkey(Conv*, Key*); + +extern Conv* convalloc(char*); +extern void convclose(Conv*); +extern void convhangup(Conv*); +extern int convneedkey(Conv*, Attr*); +extern int convbadkey(Conv*, Key*, char*, Attr*); +extern int convread(Conv*, void*, int); +extern int convreadm(Conv*, char**); +extern int convprint(Conv*, char*, ...); +/* #pragma varargck argpos convprint 2 */ +extern int convreadfn(Conv*, int(*)(void*, int), char**); +extern void convreset(Conv*); +extern int convwrite(Conv*, void*, int); + +extern int ctlwrite(char*); + +extern char* estrappend(char*, char*, ...); +/* #pragma varargck argpos estrappend 2 */ +extern int hexparse(char*, uchar*, int); + +extern void keyadd(Key*); +extern Key* keylookup(char*, ...); +extern Key* keyiterate(int, char*, ...); +/* #pragma varargck argpos keylookup 1 */ +extern Key* keyfetch(Conv*, char*, ...); +/* #pragma varargck argpos keyfetch 2 */ +extern void keyclose(Key*); +extern void keyevict(Conv*, Key*, char*, ...); +/* #pragma varargck argpos keyevict 3 */ +extern Key* keyreplace(Conv*, Key*, char*, ...); +/* #pragma varargck argpos keyreplace 3 */ + +extern void lbkick(Logbuf*); +extern void lbappend(Logbuf*, char*, ...); +extern void lbvappend(Logbuf*, char*, va_list); +/* #pragma varargck argpos lbappend 2 */ +extern void lbread(Logbuf*, Req*); +extern void lbflush(Logbuf*, Req*); +extern void flog(char*, ...); +/* #pragma varargck argpos flog 1 */ + +extern void logflush(Req*); +extern void logread(Req*); +extern void logwrite(Req*); + +extern void needkeyread(Req*); +extern void needkeyflush(Req*); +extern int needkeywrite(char*); +extern int needkeyqueue(void); + +extern Attr* addcap(Attr*, char*, Ticket*); +extern Key* plan9authkey(Attr*); +extern int _authdial(char*, char*); + +extern int memrandom(void*, int); + +extern Proto* protolookup(char*); + +extern int rpcwrite(Conv*, void*, int); +extern void rpcrespond(Conv*, char*, ...); +/* #pragma varargck argpos rpcrespond 2 */ +extern void rpcrespondn(Conv*, char*, void*, int); +extern void rpcexec(Conv*); + +extern int xioauthdial(char*, char*); +extern void xioclose(int); +extern int xiodial(char*, char*, char*, int*); +extern int xiowrite(int, void*, int); +extern int xioasrdresp(int, void*, int); +extern int xioasgetticket(int, char*, char*); + +/* pkcs1.c */ +typedef DigestState *DigestAlg(uchar*, ulong, uchar*, DigestState*); +int rsasign(RSApriv*, DigestAlg*, uchar*, uint, uchar*, uint); +void mptoberjust(mpint*, uchar*, uint); + + +extern int extrafactotumdir; + +int havesecstore(void); +int secstorefetch(void); diff --git a/src/cmd/auth/factotum/dsa.c b/src/cmd/auth/factotum/dsa.c new file mode 100644 index 00000000..73f8d296 --- /dev/null +++ b/src/cmd/auth/factotum/dsa.c @@ -0,0 +1,140 @@ +#include "std.h" +#include "dat.h" + +/* + * DSA signing and verification + * + * Sign: + * start p=xxx q=xxx alpha=xxx key=xxx + * write msg + * read signature(msg) + * + * Verify: (not implemented) + * start p=xxx q=xxx alpha=xxx key=xxx + * write msg + * write signature(msg) + * read ok or fail + * + * all numbers are hexadecimal bigints parsable with strtomp. + */ + +static int +xdsasign(Conv *c) +{ + int n; + mpint *m; + uchar digest[SHA1dlen]; + DSAsig *sig; + Key *k; + + k = keylookup("%A", c->attr); + if(k == nil) + return -1; + + c->state = "read data"; + if((n=convread(c, digest, SHA1dlen)) < 0){ + keyclose(k); + return -1; + } + m = betomp(digest, SHA1dlen, nil); + if(m == nil){ + keyclose(k); + return -1; + } + sig = dsasign(k->priv, m); + keyclose(k); + mpfree(m); + if(sig == nil) + return -1; + convprint(c, "%B %B", sig->r, sig->s); + dsasigfree(sig); + return 0; +} + +/* + * convert to canonical form (lower case) + * for use in attribute matches. + */ +static void +strlwr(char *a) +{ + for(; *a; a++){ + if('A' <= *a && *a <= 'Z') + *a += 'a' - 'A'; + } +} + +static DSApriv* +readdsapriv(Key *k) +{ + char *a; + DSApriv *priv; + + priv = dsaprivalloc(); + + if((a=strfindattr(k->attr, "p"))==nil + || (priv->pub.p=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->attr, "q"))==nil + || (priv->pub.q=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "alpha"))==nil + || (priv->pub.alpha=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "key"))==nil + || (priv->pub.key=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!secret"))==nil + || (priv->secret=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + return priv; + +Error: + dsaprivfree(priv); + return nil; +} + +static int +dsacheck(Key *k) +{ + static int first = 1; + + if(first){ + fmtinstall('B', mpfmt); + first = 0; + } + + if((k->priv = readdsapriv(k)) == nil){ + werrstr("malformed key data"); + return -1; + } + return 0; +} + +static void +dsaclose(Key *k) +{ + dsaprivfree(k->priv); + k->priv = nil; +} + +static Role +dsaroles[] = +{ + "sign", xdsasign, + 0 +}; + +Proto dsa = { + "dsa", + dsaroles, + nil, + dsacheck, + dsaclose +}; + diff --git a/src/cmd/auth/factotum/fs.c b/src/cmd/auth/factotum/fs.c new file mode 100644 index 00000000..f9ad785b --- /dev/null +++ b/src/cmd/auth/factotum/fs.c @@ -0,0 +1,531 @@ +#include "std.h" +#include "dat.h" + +enum +{ + Qroot, + Qfactotum, + Qrpc, + Qkeylist, + Qprotolist, + Qconfirm, + Qlog, + Qctl, + Qneedkey, + Qconv, +}; + +static int qtop; + +Qid +mkqid(int type, int path) +{ + Qid q; + + q.type = type; + q.path = path; + q.vers = 0; + return q; +} + +static struct +{ + char *name; + int qidpath; + ulong perm; +} dirtab[] = { + /* positions of confirm and needkey known below */ + "confirm", Qconfirm, 0600|DMEXCL, + "needkey", Qneedkey, 0600|DMEXCL, + "ctl", Qctl, 0600, + "rpc", Qrpc, 0666, + "proto", Qprotolist, 0444, + "log", Qlog, 0600|DMEXCL, + "conv", Qconv, 0400, +}; + +static void +fillstat(Dir *dir, char *name, int type, int path, ulong perm) +{ + dir->name = estrdup(name); + dir->uid = estrdup(owner); + dir->gid = estrdup(owner); + dir->mode = perm; + dir->length = 0; + dir->qid = mkqid(type, path); + dir->atime = time(0); + dir->mtime = time(0); + dir->muid = estrdup(""); +} + +static int +rootdirgen(int n, Dir *dir, void *v) +{ + USED(v); + + if(n > 0) + return -1; + + fillstat(dir, factname, QTDIR, Qfactotum, DMDIR|0555); + return 0; +} + +static int +fsdirgen(int n, Dir *dir, void *v) +{ + USED(v); + + if(n >= nelem(dirtab)) + return -1; + fillstat(dir, dirtab[n].name, 0, dirtab[n].qidpath, dirtab[n].perm); + return 0; +} + +static char* +fswalk1(Fid *fid, char *name, Qid *qid) +{ + int i; + + switch((int)fid->qid.path){ + default: + return "fswalk1: cannot happen"; + case Qroot: + if(strcmp(name, factname) == 0){ + *qid = mkqid(QTDIR, Qfactotum); + fid->qid = *qid; + return nil; + } + if(strcmp(name, "..") == 0){ + *qid = fid->qid; + return nil; + } + return "not found"; + case Qfactotum: + for(i=0; iqid = *qid; + return nil; + } + if(strcmp(name, "..") == 0){ + *qid = mkqid(QTDIR, qtop); + fid->qid = *qid; + return nil; + } + return "not found"; + } +} + +static void +fsstat(Req *r) +{ + int i, path; + + path = r->fid->qid.path; + switch(path){ + case Qroot: + fillstat(&r->d, "/", QTDIR, Qroot, 0555|DMDIR); + break; + case Qfactotum: + fillstat(&r->d, "factotum", QTDIR, Qfactotum, 0555|DMDIR); + break; + default: + for(i=0; id, dirtab[i].name, 0, dirtab[i].qidpath, dirtab[i].perm); + goto Break2; + } + respond(r, "file not found"); + break; + } + Break2: + respond(r, nil); +} + +static int +readlist(int off, int (*gen)(int, char*, uint), Req *r) +{ + char *a, *ea; + int n; + + a = r->ofcall.data; + ea = a+r->ifcall.count; + for(;;){ + n = (*gen)(off, a, ea-a); + if(n == 0){ + r->ofcall.count = a - (char*)r->ofcall.data; + return off; + } + a += n; + off++; + } + return -1; /* not reached */ +} + +static int +keylist(int i, char *a, uint nn) +{ + int n; + char buf[512]; + Key *k; + + if(i >= ring.nkey) + return 0; + + k = ring.key[i]; + k->attr = sortattr(k->attr); + n = snprint(buf, sizeof buf, "key %A %N\n", k->attr, k->privattr); + if(n >= sizeof(buf)-5) + strcpy(buf+sizeof(buf)-5, "...\n"); + n = strlen(buf); + if(n > nn) + return 0; + memmove(a, buf, n); + return n; +} + +static int +protolist(int i, char *a, uint n) +{ + if(prototab[i] == nil) + return 0; + if(strlen(prototab[i]->name)+1 > n) + return 0; + n = strlen(prototab[i]->name)+1; + memmove(a, prototab[i]->name, n-1); + a[n-1] = '\n'; + return n; +} + +/* BUG this is O(n^2) to fill in the list */ +static int +convlist(int i, char *a, uint nn) +{ + Conv *c; + char buf[512]; + int n; + + for(c=conv; c && i-- > 0; c=c->next) + ; + + if(c == nil) + return 0; + + if(c->state) + n = snprint(buf, sizeof buf, "conv state=%q %A\n", c->state, c->attr); + else + n = snprint(buf, sizeof buf, "conv state=closed err=%q\n", c->err); + + if(n >= sizeof(buf)-5) + strcpy(buf+sizeof(buf)-5, "...\n"); + n = strlen(buf); + if(n > nn) + return 0; + memmove(a, buf, n); + return n; +} + +static void +fskickreply(Conv *c) +{ + Req *r; + + if(c->hangup){ + if(c->req){ + respond(c->req, "hangup"); + c->req = nil; + } + return; + } + + if(!c->req || !c->nreply) + return; + + r = c->req; + r->ofcall.count = c->nreply; + r->ofcall.data = c->reply; + if(r->ofcall.count > r->ifcall.count) + r->ofcall.count = r->ifcall.count; + respond(r, nil); + c->req = nil; + c->nreply = 0; +} + +/* + * Some of the file system work happens in the fs proc, but + * fsopen, fsread, fswrite, fsdestroyfid, and fsflush happen in + * the main proc so that they can access the various shared + * data structures without worrying about locking. + */ +static int inuse[nelem(dirtab)]; +int *confirminuse = &inuse[0]; +int *needkeyinuse = &inuse[1]; +static void +fsopen(Req *r) +{ + int i, *inusep, perm; + static int need[4] = { 4, 2, 6, 1 }; + Conv *c; + + inusep = nil; + perm = 5; /* directory */ + for(i=0; ifid->qid.path){ + if(dirtab[i].perm & DMEXCL) + inusep = &inuse[i]; + if(strcmp(r->fid->uid, owner) == 0) + perm = dirtab[i].perm>>6; + else + perm = dirtab[i].perm; + break; + } + + if((r->ifcall.mode&~(OMASK|OTRUNC)) + || (need[r->ifcall.mode&3] & ~perm)){ + respond(r, "permission denied"); + return; + } + + if(inusep){ + if(*inusep){ + respond(r, "file in use"); + return; + } + *inusep = 1; + } + + if(r->fid->qid.path == Qrpc){ + if((c = convalloc(r->fid->uid)) == nil){ + char e[ERRMAX]; + + rerrstr(e, sizeof e); + respond(r, e); + return; + } + c->kickreply = fskickreply; + r->fid->aux = c; + } + + respond(r, nil); +} + +static void +fsread(Req *r) +{ + Conv *c; + + switch((int)r->fid->qid.path){ + default: + respond(r, "fsread: cannot happen"); + break; + case Qroot: + dirread9p(r, rootdirgen, nil); + respond(r, nil); + break; + case Qfactotum: + dirread9p(r, fsdirgen, nil); + respond(r, nil); + break; + case Qrpc: + c = r->fid->aux; + if(c->rpc.op == RpcUnknown){ + respond(r, "no rpc pending"); + break; + } + if(c->req){ + respond(r, "read already pending"); + break; + } + c->req = r; + if(c->nreply) + (*c->kickreply)(c); + else + rpcexec(c); + break; + case Qconfirm: + confirmread(r); + break; + case Qlog: + logread(r); + break; + case Qctl: + r->fid->aux = (void*)readlist((int)r->fid->aux, keylist, r); + respond(r, nil); + break; + case Qneedkey: + needkeyread(r); + break; + case Qprotolist: + r->fid->aux = (void*)readlist((int)r->fid->aux, protolist, r); + respond(r, nil); + break; + case Qconv: + r->fid->aux = (void*)readlist((int)r->fid->aux, convlist, r); + respond(r, nil); + break; + } +} + +static void +fswrite(Req *r) +{ + int ret; + char err[ERRMAX], *s; + int (*strfn)(char*); + + switch((int)r->fid->qid.path){ + default: + respond(r, "fswrite: cannot happen"); + break; + case Qrpc: + if(rpcwrite(r->fid->aux, r->ifcall.data, r->ifcall.count) < 0){ + rerrstr(err, sizeof err); + respond(r, err); + }else{ + r->ofcall.count = r->ifcall.count; + respond(r, nil); + } + break; + case Qneedkey: + strfn = needkeywrite; + goto string; + case Qctl: + strfn = ctlwrite; + goto string; + case Qconfirm: + strfn = confirmwrite; + string: + s = emalloc(r->ifcall.count+1); + memmove(s, r->ifcall.data, r->ifcall.count); + s[r->ifcall.count] = '\0'; + ret = (*strfn)(s); + free(s); + if(ret < 0){ + rerrstr(err, sizeof err); + respond(r, err); + }else{ + r->ofcall.count = r->ifcall.count; + respond(r, nil); + } + break; + } +} + +static void +fsflush(Req *r) +{ + confirmflush(r); + logflush(r); +} + +static void +fsdestroyfid(Fid *fid) +{ + if(fid->qid.path == Qrpc && fid->aux){ + convhangup(fid->aux); + convclose(fid->aux); + } +} + +static Channel *creq; +static Channel *cfid, *cfidr; + +static void +fsreqthread(void *v) +{ + Req *r; + + USED(v); + + while((r = recvp(creq)) != nil){ + switch(r->ifcall.type){ + default: + respond(r, "bug in fsreqthread"); + break; + case Topen: + fsopen(r); + break; + case Tread: + fsread(r); + break; + case Twrite: + fswrite(r); + break; + case Tflush: + fsflush(r); + break; + } + } +} + +static void +fsclunkthread(void *v) +{ + Fid *f; + + USED(v); + + while((f = recvp(cfid)) != nil){ + fsdestroyfid(f); + sendp(cfidr, 0); + } +} + +static void +fsproc(void *v) +{ + USED(v); + + threadcreate(fsreqthread, nil, STACK); + threadcreate(fsclunkthread, nil, STACK); + threadexits(nil); +} + +static void +fsattach(Req *r) +{ + r->fid->qid = mkqid(QTDIR, qtop); + r->ofcall.qid = r->fid->qid; + respond(r, nil); +} + +static void +fssend(Req *r) +{ + sendp(creq, r); +} + +static void +fssendclunk(Fid *f) +{ + sendp(cfid, f); + recvp(cfidr); +} + +void +fsstart(Srv *s) +{ + USED(s); + + if(extrafactotumdir) + qtop = Qroot; + else + qtop = Qfactotum; + creq = chancreate(sizeof(Req*), 0); + cfid = chancreate(sizeof(Fid*), 0); + cfidr = chancreate(sizeof(Fid*), 0); + proccreate(fsproc, nil, STACK); +} + +Srv fs = { +.attach= fsattach, +.walk1= fswalk1, +.open= fssend, +.read= fssend, +.write= fssend, +.stat= fsstat, +.flush= fssend, +.destroyfid= fssendclunk, +.start= fsstart, +}; + diff --git a/src/cmd/auth/factotum/key.c b/src/cmd/auth/factotum/key.c new file mode 100644 index 00000000..e2299b84 --- /dev/null +++ b/src/cmd/auth/factotum/key.c @@ -0,0 +1,217 @@ +#include "std.h" +#include "dat.h" + +Ring ring; + +Key* +keyiterate(int skip, char *fmt, ...) +{ + int i; + Attr *a; + Key *k; + va_list arg; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + + for(i=0; iattr, k->privattr)){ + if(skip-- > 0) + continue; + k->ref++; + freeattr(a); + return k; + } + } + freeattr(a); + werrstr("no key found"); + return nil; +} + +Key* +keylookup(char *fmt, ...) +{ + int i; + Attr *a; + Key *k; + va_list arg; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + + for(i=0; iattr, k->privattr)){ + k->ref++; + freeattr(a); + return k; + } + } + freeattr(a); + werrstr("no key found"); + return nil; +} + +Key* +keyfetch(Conv *c, char *fmt, ...) +{ + int i, tag; + Attr *a; + Key *k; + va_list arg; + + va_start(arg, fmt); + a = parseattrfmtv(fmt, arg); + va_end(arg); + + tag = 0; + + for(i=0; itag) + tag = k->tag; + if(matchattr(a, k->attr, k->privattr)){ + k->ref++; + if(strfindattr(k->attr, "confirm") && confirmkey(c, k) != 1){ + k->ref--; + continue; + } + freeattr(a); + return k; + } + } + + if(needkey(c, a) < 0) + convneedkey(c, a); + + for(i=0; itag <= tag) + continue; + if(matchattr(a, k->attr, k->privattr)){ + k->ref++; + if(strfindattr(k->attr, "confirm") && confirmkey(c, k) != 1){ + k->ref--; + continue; + } + freeattr(a); + return k; + } + } + freeattr(a); + werrstr("no key found"); + return nil; +} + +static int taggen; + +void +keyadd(Key *k) +{ + int i; + + k->ref++; + k->tag = ++taggen; + for(i=0; iattr, ring.key[i]->attr, nil) + && matchattr(ring.key[i]->attr, k->attr, nil)){ + keyclose(ring.key[i]); + ring.key[i] = k; + return; + } + } + + ring.key = erealloc(ring.key, (ring.nkey+1)*sizeof(ring.key[0])); + ring.key[ring.nkey++] = k; +} + +void +keyclose(Key *k) +{ + if(k == nil) + return; + + if(--k->ref > 0) + return; + + if(k->proto->closekey) + (*k->proto->closekey)(k); + + freeattr(k->attr); + freeattr(k->privattr); + free(k); +} + +Key* +keyreplace(Conv *c, Key *k, char *fmt, ...) +{ + Key *kk; + char *msg; + Attr *a, *b, *bp; + va_list arg; + + va_start(arg, fmt); + msg = vsmprint(fmt, arg); + if(msg == nil) + sysfatal("out of memory"); + va_end(arg); + + /* replace prompted values with prompts */ + a = copyattr(k->attr); + bp = parseattr(k->proto->keyprompt); + for(b=bp; b; b=b->next){ + a = delattr(a, b->name); + a = addattr(a, "%q?", b->name); + } + freeattr(bp); + + if(badkey(c, k, msg, a) < 0) + convbadkey(c, k, msg, a); + kk = keylookup("%A", a); + freeattr(a); + keyclose(k); + if(kk == k){ + keyclose(kk); + werrstr("%s", msg); + return nil; + } + + if(strfindattr(kk->attr, "confirm")){ + if(confirmkey(c, kk) != 1){ + werrstr("key use not confirmed"); + keyclose(kk); + return nil; + } + } + return kk; +} + +void +keyevict(Conv *c, Key *k, char *fmt, ...) +{ + char *msg; + Attr *a, *b, *bp; + va_list arg; + + va_start(arg, fmt); + msg = vsmprint(fmt, arg); + if(msg == nil) + sysfatal("out of memory"); + va_end(arg); + + /* replace prompted values with prompts */ + a = copyattr(k->attr); + bp = parseattr(k->proto->keyprompt); + for(b=bp; b; b=b->next){ + a = delattr(a, b->name); + a = addattr(a, "%q?", b->name); + } + freeattr(bp); + + if(badkey(c, k, msg, nil) < 0) + convbadkey(c, k, msg, nil); + keyclose(k); +} diff --git a/src/cmd/auth/factotum/log.c b/src/cmd/auth/factotum/log.c new file mode 100644 index 00000000..6c2d69dd --- /dev/null +++ b/src/cmd/auth/factotum/log.c @@ -0,0 +1,121 @@ +#include "std.h" +#include "dat.h" + +void +lbkick(Logbuf *lb) +{ + char *s; + int n; + Req *r; + + while(lb->wait && lb->rp != lb->wp){ + r = lb->wait; + lb->wait = r->aux; + if(lb->wait == nil) + lb->waitlast = &lb->wait; + r->aux = nil; + if(r->ifcall.count < 5){ + respond(r, "factotum: read request count too short"); + continue; + } + s = lb->msg[lb->rp]; + lb->msg[lb->rp] = nil; + if(++lb->rp == nelem(lb->msg)) + lb->rp = 0; + n = r->ifcall.count; + if(n < strlen(s)+1+1){ + memmove(r->ofcall.data, s, n-5); + n -= 5; + r->ofcall.data[n] = '\0'; + /* look for first byte of UTF-8 sequence by skipping continuation bytes */ + while(n>0 && (r->ofcall.data[--n]&0xC0)==0x80) + ; + strcpy(r->ofcall.data+n, "...\n"); + }else{ + strcpy(r->ofcall.data, s); + strcat(r->ofcall.data, "\n"); + } + r->ofcall.count = strlen(r->ofcall.data); + free(s); + respond(r, nil); + } +} + +void +lbread(Logbuf *lb, Req *r) +{ + if(lb->waitlast == nil) + lb->waitlast = &lb->wait; + *(lb->waitlast) = r; + lb->waitlast = (Req**)&r->aux; + r->aux = nil; + lbkick(lb); +} + +void +lbflush(Logbuf *lb, Req *r) +{ + Req **l; + + for(l=&lb->wait; *l; l=(Req**)&(*l)->aux){ + if(*l == r){ + *l = r->aux; + r->aux = nil; + if(*l == nil) + lb->waitlast = l; + closereq(r); + break; + } + } +} + +void +lbappend(Logbuf *lb, char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + lbvappend(lb, fmt, arg); + va_end(arg); +} + +void +lbvappend(Logbuf *lb, char *fmt, va_list arg) +{ + char *s; + + s = smprint(fmt, arg); + if(s == nil) + sysfatal("out of memory"); + if(lb->msg[lb->wp]) + free(lb->msg[lb->wp]); + lb->msg[lb->wp] = s; + if(++lb->wp == nelem(lb->msg)) + lb->wp = 0; + lbkick(lb); +} + +Logbuf logbuf; + +void +logread(Req *r) +{ + lbread(&logbuf, r); +} + +void +logflush(Req *r) +{ + lbflush(&logbuf, r); +} + +void +flog(char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + lbvappend(&logbuf, fmt, arg); + va_end(arg); +} + diff --git a/src/cmd/auth/factotum/main.c b/src/cmd/auth/factotum/main.c new file mode 100644 index 00000000..1a8c4ffc --- /dev/null +++ b/src/cmd/auth/factotum/main.c @@ -0,0 +1,185 @@ +#include "std.h" +#include "dat.h" +#include <9pclient.h> + +int extrafactotumdir; +int debug; +int trysecstore = 1; +char *factname = "factotum"; +char *service = "factotum"; +char *owner; +char *authaddr; +void gflag(char*); + +void +usage(void) +{ + fprint(2, "usage: factotum [-Dd] [-a authaddr] [-m mtpt] [-s service]\n"); + fprint(2, " or factotum -g keypattern\n"); + fprint(2, " or factotum -g 'badkeyattr\\nmsg\\nkeypattern'\n"); + threadexitsall("usage"); +} + +void +threadmain(int argc, char *argv[]) +{ + char *mtpt; + char err[ERRMAX]; + +// mtpt = "/mnt"; + mtpt = nil; + owner = getuser(); + quotefmtinstall(); + fmtinstall('A', attrfmt); + fmtinstall('H', encodefmt); + fmtinstall('N', attrnamefmt); + + if(argc == 3 && strcmp(argv[1], "-g") == 0){ + gflag(argv[2]); + threadexitsall(nil); + } + + ARGBEGIN{ + default: + usage(); + case 'D': + chatty9p++; + break; + case 'a': + authaddr = EARGF(usage()); + break; + case 'g': + usage(); + case 'm': + mtpt = EARGF(usage()); + break; + case 's': + service = EARGF(usage()); + break; + case 'n': + trysecstore = 0; + break; + case 'x': + extrafactotumdir = 1; + break; + }ARGEND + + if(argc != 0) + usage(); + + if(trysecstore && havesecstore()){ + while(secstorefetch() < 0){ + rerrstr(err, sizeof err); + if(strcmp(err, "cancel") == 0) + break; + fprint(2, "secstorefetch: %r\n"); + fprint(2, "Enter an empty password to quit.\n"); + } + } + + threadpostmountsrv(&fs, service, mtpt, MBEFORE); + threadexits(nil); +} + +/* + * prompt user for a key. don't care about memory leaks, runs standalone + */ +static Attr* +promptforkey(int fd, char *params) +{ + char *v; + Attr *a, *attr; + char *def; + + attr = _parseattr(params); + fprint(fd, "!adding key:"); + for(a=attr; a; a=a->next) + if(a->type != AttrQuery && a->name[0] != '!') + fprint(fd, " %q=%q", a->name, a->val); + fprint(fd, "\n"); + + for(a=attr; a; a=a->next){ + v = a->name; + if(a->type != AttrQuery || v[0]=='!') + continue; + def = nil; + if(strcmp(v, "user") == 0) + def = getuser(); + a->val = readcons(v, def, 0); + if(a->val == nil) + sysfatal("user terminated key input"); + a->type = AttrNameval; + } + for(a=attr; a; a=a->next){ + v = a->name; + if(a->type != AttrQuery || v[0]!='!') + continue; + def = nil; + if(strcmp(v+1, "user") == 0) + def = getuser(); + a->val = readcons(v+1, def, 1); + if(a->val == nil) + sysfatal("user terminated key input"); + a->type = AttrNameval; + } + fprint(fd, "!\n"); + close(fd); + return attr; +} + +/* + * send a key to the mounted factotum + */ +static int +sendkey(Attr *attr) +{ + int rv; + char buf[8192]; + CFid *fid; + + fid = nsopen("factotum", nil, "ctl", OWRITE); + if(fid == nil) + sysfatal("opening factotum/ctl: %r"); + snprint(buf, sizeof buf, "key %A\n", attr); + rv = fswrite(fid, buf, strlen(buf)); + fsclose(fid); + return rv; +} + +static void +askuser(int fd, char *params) +{ + Attr *attr; + + attr = promptforkey(fd, params); + if(attr == nil) + sysfatal("no key supplied"); + if(sendkey(attr) < 0) + sysfatal("sending key to factotum: %r"); +} + +void +gflag(char *s) +{ + char *f[4]; + int nf; + int fd; + + if((fd = open("/dev/tty", ORDWR)) < 0) + sysfatal("open /dev/tty: %r"); + + nf = getfields(s, f, nelem(f), 0, "\n"); + if(nf == 1){ /* needkey or old badkey */ + fprint(fd, "\n"); + askuser(fd, s); + threadexitsall(nil); + } + if(nf == 3){ /* new badkey */ + fprint(fd, "\n"); + fprint(fd, "!replace: %s\n", f[0]); + fprint(fd, "!because: %s\n", f[1]); + askuser(fd, f[2]); + threadexitsall(nil); + } + usage(); +} diff --git a/src/cmd/auth/factotum/mkfile b/src/cmd/auth/factotum/mkfile new file mode 100644 index 00000000..7c716021 --- /dev/null +++ b/src/cmd/auth/factotum/mkfile @@ -0,0 +1,36 @@ +<$PLAN9/src/mkhdr + +TARG=factotum +PROTO=\ + apop.$O\ + chap.$O\ + p9any.$O\ + p9sk1.$O\ + rsa.$O\ + +OFILES=\ + $PROTO\ + attr.$O\ + confirm.$O\ + conv.$O\ + ctl.$O\ + dsa.$O\ + fs.$O\ + key.$O\ + log.$O\ + main.$O\ + plan9.$O\ + pkcs1.$O\ + proto.$O\ + rpc.$O\ + util.$O\ + xio.$O\ + secstore.$O\ + +HFILES=dat.h + +<$PLAN9/src/mkone + +$O.test: test.$O + $LD -o $target $prereq + diff --git a/src/cmd/auth/factotum/p9any.c b/src/cmd/auth/factotum/p9any.c new file mode 100644 index 00000000..694d4cbc --- /dev/null +++ b/src/cmd/auth/factotum/p9any.c @@ -0,0 +1,272 @@ +#include "std.h" +#include "dat.h" + +/* + * p9any - protocol negotiator + * + * Protocol: + * S->C: v.2 proto@dom proto@dom proto@dom... NUL + * C->S: proto dom NUL + * [negotiated proto continues] + */ + +extern Proto p9sk1, p9sk2, p9cr; + +static Proto* okproto[] = +{ + &p9sk1, + nil, +}; + +static int +rolecall(Role *r, char *name, Conv *c) +{ + for(; r->name; r++) + if(strcmp(r->name, name) == 0) + return (*r->fn)(c); + werrstr("unknown role"); + return -1; +} + +static int +hasnul(void *v, int n) +{ + char *c; + + c = v; + if(n > 0 && c[n-1] == '\0') + return n; + else + return AuthRpcMax; +} + +static int +p9anyserver(Conv *c) +{ + char *s, *dom; + int i, j, n, m, ret; + char *tok[3]; + Attr *attr; + Key *k; + + ret = -1; + s = estrdup("v.2"); + n = 0; + attr = delattr(copyattr(c->attr), "proto"); + + for(i=0; iproto == okproto[j] + && (dom = strfindattr(k->attr, "dom")) != nil + && matchattr(attr, k->attr, k->privattr)){ + s = estrappend(s, " %s@%s", k->proto->name, dom); + n++; + } + } + + if(n == 0){ + werrstr("no valid keys"); + goto out; + } + + c->state = "write offer"; + if(convwrite(c, s, strlen(s)+1) < 0) + goto out; + free(s); + s = nil; + + c->state = "read choice"; + if(convreadfn(c, hasnul, &s) < 0) + goto out; + + m = tokenize(s, tok, nelem(tok)); + if(m != 2){ + werrstr("bad protocol message"); + goto out; + } + + for(i=0; okproto[i]; i++) + if(strcmp(okproto[i]->name, tok[0]) == 0) + break; + if(!okproto[i]){ + werrstr("bad chosen protocol %q", tok[0]); + goto out; + } + + c->state = "write ok"; + if(convwrite(c, "OK\0", 3) < 0) + goto out; + + c->state = "start choice"; + attr = addattr(attr, "proto=%q dom=%q", tok[0], tok[1]); + free(c->attr); + c->attr = attr; + attr = nil; + c->proto = okproto[i]; + + if(rolecall(c->proto->roles, "server", c) < 0){ + werrstr("%s: %r", tok[0]); + goto out; + } + + ret = 0; + +out: + free(s); + freeattr(attr); + return ret; +} + +static int +p9anyclient(Conv *c) +{ + char *s, **f, *tok[20], ok[3], *q, *user, *dom, *choice; + int i, n, ret, version; + Key *k; + Attr *attr; + Proto *p; + + ret = -1; + s = nil; + k = nil; + + user = strfindattr(c->attr, "user"); + dom = strfindattr(c->attr, "dom"); + + /* + * if the user is the factotum owner, any key will do. + * if not, then if we have a speakfor key, + * we will only vouch for the user's local identity. + * + * this logic is duplicated in p9sk1.c + */ + attr = delattr(copyattr(c->attr), "role"); + attr = delattr(attr, "proto"); + if(strcmp(c->sysuser, owner) == 0) + attr = addattr(attr, "role=client"); + else if(user==nil || strcmp(c->sysuser, user)==0){ + attr = delattr(attr, "user"); + attr = addattr(attr, "role=speakfor"); + }else{ + werrstr("will not authenticate for %q as %q", c->sysuser, user); + goto out; + } + + c->state = "read offer"; + if(convreadfn(c, hasnul, &s) < 0) + goto out; + + c->state = "look for keys"; + n = tokenize(s, tok, nelem(tok)); + f = tok; + version = 1; + if(n > 0 && memcmp(f[0], "v.", 2) == 0){ + version = atoi(f[0]+2); + if(version != 2){ + werrstr("unknown p9any version: %s", f[0]); + goto out; + } + f++; + n--; + } + + /* look for keys that don't need confirmation */ + for(i=0; iattr, "confirm") == nil) + goto found; + *--q = '@'; + } + + /* look for any keys at all */ + for(i=0; istate = "ask for keys"; + for(i=0; ikeyprompt == nil){ + *--q = '@'; + continue; + } + if(k = keyfetch(c, "%A proto=%q dom=%q %s", attr, f[i], q, p->keyprompt)) + goto found; + *--q = '@'; + } + + /* nothing worked */ + werrstr("unable to find common key"); + goto out; + +found: + /* f[i] is the chosen protocol, q the chosen domain */ + attr = addattr(attr, "proto=%q dom=%q", f[i], q); + c->state = "write choice"; + + /* have a key: go for it */ + choice = estrappend(nil, "%q %q", f[i], q); + if(convwrite(c, choice, strlen(choice)+1) < 0){ + free(choice); + goto out; + } + free(choice); + + if(version == 2){ + c->state = "read ok"; + if(convread(c, ok, 3) < 0 || memcmp(ok, "OK\0", 3) != 0) + goto out; + } + + c->state = "start choice"; + c->proto = protolookup(f[i]); + freeattr(c->attr); + c->attr = attr; + attr = nil; + + if(rolecall(c->proto->roles, "client", c) < 0){ + werrstr("%s: %r", c->proto->name); + goto out; + } + + ret = 0; + +out: + keyclose(k); + freeattr(attr); + free(s); + return ret; +} + +static Role +p9anyroles[] = +{ + "client", p9anyclient, + "server", p9anyserver, + 0 +}; + +Proto p9any = { +.name= "p9any", +.roles= p9anyroles, +}; + diff --git a/src/cmd/auth/factotum/p9cr.c b/src/cmd/auth/factotum/p9cr.c new file mode 100644 index 00000000..7f53e447 --- /dev/null +++ b/src/cmd/auth/factotum/p9cr.c @@ -0,0 +1,545 @@ +/* + * p9cr, vnc - one-sided challenge/response authentication + * + * Protocol: + * + * C -> S: user + * S -> C: challenge + * C -> S: response + * S -> C: ok or bad + * + * Note that this is the protocol between factotum and the local + * program, not between the two factotums. The information + * exchanged here is wrapped in other protocols by the local + * programs. + */ + +#include "std.h" +#include "dat.h" + +static int +p9crcheck(Key *k) +{ + if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){ + werrstr("need user and !password attributes"); + return -1; + } + return 0; +} + +static int +p9crclient(Conv *c) +{ + char *chal, *pw, *res, *user; + int astype, nchal, npw, ntry, ret; + uchar resp[MD5dlen]; + Attr *attr; + DigestState *ds; + Key *k; + + chal = nil; + k = nil; + res = nil; + ret = -1; + attr = c->attr; + + if(c->proto == &p9cr){ + astype = AuthChal; + challen = NETCHLEN; + }else if(c->proto == &vnc){ + astype = AuthVnc; + challen = MAXCHAL; + }else{ + werrstr("bad proto"); + goto out; + } + + c->state = "find key"; + k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); + if(k == nil) + goto out; + + for(ntry=1;; ntry++){ + if(c->attr != attr) + freeattr(c->attr); + c->attr = addattrs(copyattr(attr), k->attr); + if((pw = strfindattr(k->privattr, "!password")) == nil){ + werrstr("key has no !password (cannot happen)"); + goto out; + } + npw = strlen(pw); + + if((user = strfindattr(k->attr, "user")) == nil){ + werrstr("key has no user (cannot happen)"); + goto out; + } + + if(convprint(c, "%s", user) < 0) + goto out; + + if(convreadm(c, &chal) < 0) + goto out; + + if((nresp = (*response)(chal, resp)) < 0) + goto out; + + if(convwrite(c, resp, nresp) < 0) + goto out; + + if(convreadm(c, &res) < 0) + goto out; + + if(strcmp(res, "ok") == 0) + break; + + if((k = keyreplace(c, k, "%s", res)) == nil){ + c->state = "auth failed"; + werrstr("%s", res); + goto out; + } + } + + werrstr("succeeded"); + ret = 0; + +out: + keyclose(k); + free(chal); + if(c->attr != attr) + freeattr(attr); + return ret; +} + +static int +p9crserver(Conv *c) +{ + char chal[APOPCHALLEN], *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 == &apop) + astype = AuthApop; + else if(c->proto == &cram) + astype = AuthCram; + 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(p9crchal(&s, astype, chal) < 0) + goto out; + + c->state = "write challenge"; + if(convprint(c, "%s", chal) < 0) + goto out; + + for(;;){ + 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(apopresp(&s, user, resp)){ + case -1: + goto out; + case 0: + c->state = "write status"; + if(convprint(c, "bad authentication failed") < 0) + goto out; + break; + case 1: + c->state = "write status"; + if(convprint(c, "ok") < 0) + goto out; + goto ok; + } + free(user); + free(resp); + user = nil; + resp = nil; + } + +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; +} + +enum +{ + MAXCHAL = 64, +}; + +typedef struct State State; +struct State +{ + Key *key; + int astype; + int asfd; + Ticket t; + Ticketreq tr; + char chal[MAXCHAL]; + int challen; + char resp[MAXCHAL]; + int resplen; +}; + +enum +{ + CNeedChal, + CHaveResp, + + SHaveChal, + SNeedResp, + + Maxphase, +}; + +static char *phasenames[Maxphase] = +{ +[CNeedChal] "CNeedChal", +[CHaveResp] "CHaveResp", + +[SHaveChal] "SHaveChal", +[SNeedResp] "SNeedResp", +}; + +static void +p9crclose(Fsstate *fss) +{ + State *s; + + s = fss->ps; + if(s->asfd >= 0){ + close(s->asfd); + s->asfd = -1; + } + free(s); +} + +static int getchal(State*, Fsstate*); + +static int +p9crinit(Proto *p, Fsstate *fss) +{ + int iscli, ret; + char *user; + State *s; + Attr *attr; + + if((iscli = isclient(_str_findattr(fss->attr, "role"))) < 0) + return failure(fss, nil); + + s = emalloc(sizeof(*s)); + s->asfd = -1; + if(p == &p9cr){ + s->astype = AuthChal; + s->challen = NETCHLEN; + }else if(p == &vnc){ + s->astype = AuthVNC; + s->challen = Maxchal; + }else + abort(); + + if(iscli){ + fss->phase = CNeedChal; + if(p == &p9cr) + attr = setattr(_copyattr(fss->attr), "proto=p9sk1"); + else + attr = nil; + ret = findkey(&s->key, fss, Kuser, 0, attr ? attr : fss->attr, + "role=client %s", p->keyprompt); + _freeattr(attr); + if(ret != RpcOk){ + free(s); + return ret; + } + fss->ps = s; + }else{ + if((ret = findp9authkey(&s->key, fss)) != RpcOk){ + free(s); + return ret; + } + if((user = _str_findattr(fss->attr, "user")) == nil){ + free(s); + return failure(fss, "no user name specified in start msg"); + } + if(strlen(user) >= sizeof s->tr.uid){ + free(s); + return failure(fss, "user name too long"); + } + fss->ps = s; + strcpy(s->tr.uid, user); + ret = getchal(s, fss); + if(ret != RpcOk){ + p9crclose(fss); /* frees s */ + fss->ps = nil; + } + } + fss->phasename = phasenames; + fss->maxphase = Maxphase; + return ret; +} + +static int +p9crread(Fsstate *fss, void *va, uint *n) +{ + int m; + State *s; + + s = fss->ps; + switch(fss->phase){ + default: + return phaseerror(fss, "read"); + + case CHaveResp: + if(s->resplen < *n) + *n = s->resplen; + memmove(va, s->resp, *n); + fss->phase = Established; + return RpcOk; + + case SHaveChal: + if(s->astype == AuthChal) + m = strlen(s->chal); /* ascii string */ + else + m = s->challen; /* fixed length binary */ + if(m > *n) + return toosmall(fss, m); + *n = m; + memmove(va, s->chal, m); + fss->phase = SNeedResp; + return RpcOk; + } +} + +static int +p9response(Fsstate *fss, State *s) +{ + char key[DESKEYLEN]; + uchar buf[8]; + ulong chal; + char *pw; + + pw = _str_findattr(s->key->privattr, "!password"); + if(pw == nil) + return failure(fss, "vncresponse cannot happen"); + passtokey(key, pw); + memset(buf, 0, 8); + sprint((char*)buf, "%d", atoi(s->chal)); + if(encrypt(key, buf, 8) < 0) + return failure(fss, "can't encrypt response"); + chal = (buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+buf[3]; + s->resplen = snprint(s->resp, sizeof s->resp, "%.8lux", chal); + return RpcOk; +} + +static uchar tab[256]; + +/* VNC reverses the bits of each byte before using as a des key */ +static void +mktab(void) +{ + int i, j, k; + static int once; + + if(once) + return; + once = 1; + + for(i=0; i<256; i++) { + j=i; + tab[i] = 0; + for(k=0; k<8; k++) { + tab[i] = (tab[i]<<1) | (j&1); + j >>= 1; + } + } +} + +static int +vncaddkey(Key *k) +{ + uchar *p; + char *s; + + k->priv = emalloc(8+1); + if(s = _str_findattr(k->privattr, "!password")){ + mktab(); + memset(k->priv, 0, 8+1); + strncpy((char*)k->priv, s, 8); + for(p=k->priv; *p; p++) + *p = tab[*p]; + }else{ + werrstr("no key data"); + return -1; + } + return replacekey(k); +} + +static void +vncclosekey(Key *k) +{ + free(k->priv); +} + +static int +vncresponse(Fsstate*, State *s) +{ + DESstate des; + + memmove(s->resp, s->chal, sizeof s->chal); + setupDESstate(&des, s->key->priv, nil); + desECBencrypt((uchar*)s->resp, s->challen, &des); + s->resplen = s->challen; + return RpcOk; +} + +static int +p9crwrite(Fsstate *fss, void *va, uint n) +{ + char tbuf[TICKETLEN+AUTHENTLEN]; + State *s; + char *data = va; + Authenticator a; + char resp[Maxchal]; + int ret; + + s = fss->ps; + switch(fss->phase){ + default: + return phaseerror(fss, "write"); + + case CNeedChal: + if(n >= sizeof(s->chal)) + return failure(fss, Ebadarg); + memset(s->chal, 0, sizeof s->chal); + memmove(s->chal, data, n); + s->challen = n; + + if(s->astype == AuthChal) + ret = p9response(fss, s); + else + ret = vncresponse(fss, s); + if(ret != RpcOk) + return ret; + fss->phase = CHaveResp; + return RpcOk; + + case SNeedResp: + /* send response to auth server and get ticket */ + if(n > sizeof(resp)) + return failure(fss, Ebadarg); + memset(resp, 0, sizeof resp); + memmove(resp, data, n); + if(write(s->asfd, resp, s->challen) != s->challen) + return failure(fss, Easproto); + + /* get ticket plus authenticator from auth server */ + if(_asrdresp(s->asfd, tbuf, TICKETLEN+AUTHENTLEN) < 0) + return failure(fss, nil); + + /* check ticket */ + convM2T(tbuf, &s->t, s->key->priv); + if(s->t.num != AuthTs + || memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0) + return failure(fss, Easproto); + convM2A(tbuf+TICKETLEN, &a, s->t.key); + if(a.num != AuthAc + || memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0 + || a.id != 0) + return failure(fss, Easproto); + + fss->haveai = 1; + fss->ai.cuid = s->t.cuid; + fss->ai.suid = s->t.suid; + fss->ai.nsecret = 0; + fss->ai.secret = nil; + fss->phase = Established; + return RpcOk; + } +} + +static int +getchal(State *s, Fsstate *fss) +{ + char trbuf[TICKREQLEN]; + int n; + + safecpy(s->tr.hostid, _str_findattr(s->key->attr, "user"), sizeof(s->tr.hostid)); + safecpy(s->tr.authdom, _str_findattr(s->key->attr, "dom"), sizeof(s->tr.authdom)); + s->tr.type = s->astype; + convTR2M(&s->tr, trbuf); + + /* get challenge from auth server */ + s->asfd = _authdial(nil, _str_findattr(s->key->attr, "dom")); + if(s->asfd < 0) + return failure(fss, Easproto); + if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) + return failure(fss, Easproto); + n = _asrdresp(s->asfd, s->chal, s->challen); + if(n <= 0){ + if(n == 0) + werrstr("_asrdresp short read"); + return failure(fss, nil); + } + s->challen = n; + fss->phase = SHaveChal; + return RpcOk; +} + +Proto p9cr = +{ +.name= "p9cr", +.init= p9crinit, +.write= p9crwrite, +.read= p9crread, +.close= p9crclose, +.keyprompt= "user? !password?", +}; + +Proto vnc = +{ +.name= "vnc", +.init= p9crinit, +.write= p9crwrite, +.read= p9crread, +.close= p9crclose, +.keyprompt= "!password?", +.addkey= vncaddkey, +}; diff --git a/src/cmd/auth/factotum/p9sk1.c b/src/cmd/auth/factotum/p9sk1.c new file mode 100644 index 00000000..92b055d0 --- /dev/null +++ b/src/cmd/auth/factotum/p9sk1.c @@ -0,0 +1,353 @@ +/* + * p9sk1, p9sk2 - Plan 9 secret (private) key authentication. + * p9sk2 is an incomplete flawed variant of p9sk1. + * + * Client protocol: + * write challenge[challen] (p9sk1 only) + * read tickreq[tickreqlen] + * write ticket[ticketlen] + * read authenticator[authentlen] + * + * Server protocol: + * read challenge[challen] (p9sk1 only) + * write tickreq[tickreqlen] + * read ticket[ticketlen] + * write authenticator[authentlen] + */ + +#include "std.h" +#include "dat.h" + +extern Proto p9sk1, p9sk2; +static int gettickets(Ticketreq*, char*, Key*); + +#define max(a, b) ((a) > (b) ? (a) : (b)) +enum +{ + MAXAUTH = max(TICKREQLEN, TICKETLEN+max(TICKETLEN, AUTHENTLEN)) +}; + +static int +p9skclient(Conv *c) +{ + char *user; + char cchal[CHALLEN]; + uchar secret[8]; + char buf[MAXAUTH]; + int speakfor, ret; + Attr *a; + Authenticator au; + Key *k; + Ticket t; + Ticketreq tr; + + ret = -1; + a = nil; + k = nil; + + /* p9sk1: send client challenge */ + if(c->proto == &p9sk1){ + c->state = "write challenge"; + memrandom(cchal, CHALLEN); + if(convwrite(c, cchal, CHALLEN) < 0) + goto out; + } + + /* read ticket request */ + c->state = "read tickreq"; + if(convread(c, buf, TICKREQLEN) < 0) + goto out; + convM2TR(buf, &tr); + + /* p9sk2: use server challenge as client challenge */ + if(c->proto == &p9sk2) + memmove(cchal, tr.chal, CHALLEN); + + /* + * find a key. + * + * if the user is the factotum owner, any key will do. + * if not, then if we have a speakfor key, + * we will only vouch for the user's local identity. + * + * this logic is duplicated in p9any.c + */ + user = strfindattr(c->attr, "user"); + a = delattr(copyattr(c->attr), "role"); + a = addattr(a, "proto=p9sk1"); + + if(strcmp(c->sysuser, owner) == 0){ + speakfor = 0; + a = addattr(a, "proto=p9sk1 user? dom=%q", tr.authdom); + }else if(user==nil || strcmp(c->sysuser, user)==0){ + speakfor = 1; + a = delattr(a, "user"); + a = addattr(a, "proto=p9sk1 user? dom=%q role=speakfor", tr.authdom); + }else{ + werrstr("will not authenticate for %q as %q", c->sysuser, user); + goto out; + } + + for(;;){ + c->state = "find key"; + k = keyfetch(c, "%A", a); + if(k == nil) + goto out; + + /* relay ticket request to auth server, get tickets */ + strcpy(tr.hostid, strfindattr(k->attr, "user")); + if(speakfor) + strcpy(tr.uid, c->sysuser); + else + strcpy(tr.uid, tr.hostid); + + c->state = "get tickets"; + if(gettickets(&tr, buf, k) < 0) + goto out; + + convM2T(buf, &t, k->priv); + if(t.num == AuthTc) + break; + + /* we don't agree with the auth server about the key; try again */ + c->state = "replace key"; + if((k = keyreplace(c, k, "key mismatch with auth server")) == nil){ + werrstr("key mismatch with auth server"); + goto out; + } + } + + /* send second ticket and authenticator to server */ + c->state = "write ticket+auth"; + memmove(buf, buf+TICKETLEN, TICKETLEN); + au.num = AuthAc; + memmove(au.chal, tr.chal, CHALLEN); + au.id = 0; + convA2M(&au, buf+TICKETLEN, t.key); + if(convwrite(c, buf, TICKETLEN+AUTHENTLEN) < 0) + goto out; + + /* read authenticator from server */ + c->state = "read auth"; + if(convread(c, buf, AUTHENTLEN) < 0) + goto out; + convM2A(buf, &au, t.key); + if(au.num != AuthAs || memcmp(au.chal, cchal, CHALLEN) != 0 || au.id != 0){ + werrstr("server lies through his teeth"); + goto out; + } + + /* success */ + c->attr = addcap(c->attr, c->sysuser, &t); + des56to64((uchar*)t.key, secret); + c->attr = addattr(c->attr, "secret=%.8H", secret); + ret = 0; + +out: + freeattr(a); + keyclose(k); + return ret; +} + +static int +p9skserver(Conv *c) +{ + char cchal[CHALLEN], buf[MAXAUTH]; + uchar secret[8]; + int ret; + Attr *a; + Authenticator au; + Key *k; + Ticketreq tr; + Ticket t; + + ret = -1; + + a = addattr(copyattr(c->attr), "user? dom?"); + a = addattr(a, "user? dom? proto=p9sk1"); + if((k = keyfetch(c, "%A", a)) == nil) + goto out; + + /* p9sk1: read client challenge */ + if(c->proto == &p9sk1){ + if(convread(c, cchal, CHALLEN) < 0) + goto out; + } + + /* send ticket request */ + memset(&tr, 0, sizeof tr); + tr.type = AuthTreq; + strcpy(tr.authid, strfindattr(k->attr, "user")); + strcpy(tr.authdom, strfindattr(k->attr, "dom")); + memrandom(tr.chal, sizeof tr.chal); + convTR2M(&tr, buf); + if(convwrite(c, buf, TICKREQLEN) < 0) + goto out; + + /* p9sk2: use server challenge as client challenge */ + if(c->proto == &p9sk2) + memmove(cchal, tr.chal, sizeof tr.chal); + + /* read ticket+authenticator */ + if(convread(c, buf, TICKETLEN+AUTHENTLEN) < 0) + goto out; + + convM2T(buf, &t, k->priv); + if(t.num != AuthTs || memcmp(t.chal, tr.chal, CHALLEN) != 0){ + /* BUG badkey */ + werrstr("key mismatch with auth server"); + goto out; + } + + convM2A(buf+TICKETLEN, &au, t.key); + if(au.num != AuthAc || memcmp(au.chal, tr.chal, CHALLEN) != 0 || au.id != 0){ + werrstr("client lies through his teeth"); + goto out; + } + + /* send authenticator */ + au.num = AuthAs; + memmove(au.chal, cchal, CHALLEN); + convA2M(&au, buf, t.key); + if(convwrite(c, buf, AUTHENTLEN) < 0) + goto out; + + /* success */ + c->attr = addcap(c->attr, c->sysuser, &t); + des56to64((uchar*)t.key, secret); + c->attr = addattr(c->attr, "secret=%.8H", secret); + ret = 0; + +out: + freeattr(a); + keyclose(k); + return ret; +} + +int +_asgetticket(int fd, char *trbuf, char *tbuf) +{ + if(write(fd, trbuf, TICKREQLEN) < 0){ + close(fd); + return -1; + } + return _asrdresp(fd, tbuf, 2*TICKETLEN); +} +static int +getastickets(Ticketreq *tr, char *buf) +{ + int asfd; + int ret; + + if((asfd = xioauthdial(nil, tr->authdom)) < 0) + return -1; + convTR2M(tr, buf); + ret = xioasgetticket(asfd, buf, buf); + xioclose(asfd); + return ret; +} + +static int +mktickets(Ticketreq *tr, char *buf, Key *k) +{ + Ticket t; + + if(strcmp(tr->authid, tr->hostid) != 0) + return -1; + + memset(&t, 0, sizeof t); + memmove(t.chal, tr->chal, CHALLEN); + strcpy(t.cuid, tr->uid); + strcpy(t.suid, tr->uid); + memrandom(t.key, DESKEYLEN); + t.num = AuthTc; + convT2M(&t, buf, k->priv); + t.num = AuthTs; + convT2M(&t, buf+TICKETLEN, k->priv); + return 0; +} + +static int +gettickets(Ticketreq *tr, char *buf, Key *k) +{ + if(getastickets(tr, buf) == 0) + return 0; + if(mktickets(tr, buf, k) == 0) + return 0; + werrstr("gettickets: %r"); + return -1; +} + +static int +p9sk1check(Key *k) +{ + char *user, *dom, *pass; + Ticketreq tr; + + user = strfindattr(k->attr, "user"); + dom = strfindattr(k->attr, "dom"); + if(user==nil || dom==nil){ + werrstr("need user and dom attributes"); + return -1; + } + if(strlen(user) >= sizeof tr.authid){ + werrstr("user name too long"); + return -1; + } + if(strlen(dom) >= sizeof tr.authdom){ + werrstr("auth dom name too long"); + return -1; + } + + k->priv = emalloc(DESKEYLEN); + if(pass = strfindattr(k->privattr, "!password")) + passtokey(k->priv, pass); + else if(pass = strfindattr(k->privattr, "!hex")){ + if(hexparse(pass, k->priv, 7) < 0){ + werrstr("malformed !hex key data"); + return -1; + } + }else{ + werrstr("need !password or !hex attribute"); + return -1; + } + + return 0; +} + +static void +p9sk1close(Key *k) +{ + free(k->priv); + k->priv = nil; +} + +static Role +p9sk1roles[] = +{ + "client", p9skclient, + "server", p9skserver, + 0 +}; + +static Role +p9sk2roles[] = +{ + "client", p9skclient, + "server", p9skserver, + 0 +}; + +Proto p9sk1 = { +.name= "p9sk1", +.roles= p9sk1roles, +.checkkey= p9sk1check, +.closekey= p9sk1close, +.keyprompt= "user? dom? !password?", +}; + +Proto p9sk2 = { +.name= "p9sk2", +.roles= p9sk2roles, +}; + diff --git a/src/cmd/auth/factotum/pass.c b/src/cmd/auth/factotum/pass.c new file mode 100644 index 00000000..b3d4cb6a --- /dev/null +++ b/src/cmd/auth/factotum/pass.c @@ -0,0 +1,100 @@ +/* + * This is just a repository for a password. + * We don't want to encourage this, there's + * no server side. + */ + +#include "dat.h" + +typedef struct State State; +struct State +{ + Key *key; +}; + +enum +{ + HavePass, + Maxphase, +}; + +static char *phasenames[Maxphase] = +{ +[HavePass] "HavePass", +}; + +static int +passinit(Proto *p, Fsstate *fss) +{ + int ask; + Key *k; + State *s; + + k = findkey(fss, Kuser, &ask, 0, fss->attr, "%s", p->keyprompt); + if(k == nil){ + if(ask) + return RpcNeedkey; + return failure(fss, nil); + } + setattrs(fss->attr, k->attr); + s = emalloc(sizeof(*s)); + s->key = k; + fss->ps = s; + return RpcOk; +} + +static void +passclose(Fsstate *fss) +{ + State *s; + + s = fss->ps; + if(s->key) + closekey(s->key); + free(s); +} + +static int +passread(Fsstate *fss, void *va, uint *n) +{ + int m; + char buf[500]; + char *pass, *user; + State *s; + + s = fss->ps; + switch(fss->phase){ + default: + return phaseerror(fss, "read"); + + case HavePass: + user = strfindattr(s->key->attr, "user"); + pass = strfindattr(s->key->privattr, "!password"); + if(user==nil || pass==nil) + return failure(fss, "passread cannot happen"); + snprint(buf, sizeof buf, "%q %q", user, pass); + m = strlen(buf); + if(m > *n) + return toosmall(fss, m); + *n = m; + memmove(va, buf, m); + return RpcOk; + } +} + +static int +passwrite(Fsstate *fss, void*, uint) +{ + return phaseerror(fss, "write"); +} + +Proto pass = +{ +.name= "pass", +.init= passinit, +.write= passwrite, +.read= passread, +.close= passclose, +.addkey= replacekey, +.keyprompt= "user? !password?", +}; diff --git a/src/cmd/auth/factotum/pkcs1.c b/src/cmd/auth/factotum/pkcs1.c new file mode 100644 index 00000000..fb35ce83 --- /dev/null +++ b/src/cmd/auth/factotum/pkcs1.c @@ -0,0 +1,154 @@ +#include "std.h" +#include "dat.h" + +/* + * PKCS #1 v2.0 signatures (aka RSASSA-PKCS1-V1_5) + * + * You don't want to read the spec. + * Here is what you need to know. + * + * RSA sign (aka RSASP1) is just an RSA encryption. + * RSA verify (aka RSAVP1) is just an RSA decryption. + * + * We sign hashes of messages instead of the messages + * themselves. + * + * The hashes are encoded in ASN.1 DER to identify + * the signature type, and then prefixed with 0x01 PAD 0x00 + * where PAD is as many 0xFF bytes as desired. + */ + +static int mkasn1(uchar *asn1, DigestAlg *alg, uchar *d, uint dlen); + +int +rsasign(RSApriv *key, DigestAlg *hash, uchar *digest, uint dlen, + uchar *sig, uint siglen) +{ + uchar asn1[64], *buf; + int n, len, pad; + mpint *m, *s; + + /* + * Create ASN.1 + */ + n = mkasn1(asn1, hash, digest, dlen); + + /* + * Create number to sign. + */ + len = (mpsignif(key->pub.n)+7)/8; + if(len < n+2){ + werrstr("rsa key too short"); + return -1; + } + pad = len - (n+2); + if(siglen < len){ + werrstr("signature buffer too short"); + return -1; + } + buf = malloc(len); + if(buf == nil) + return -1; + buf[0] = 0x01; + memset(buf+1, 0xFF, pad); + buf[1+pad] = 0x00; + memmove(buf+1+pad+1, asn1, n); + m = betomp(buf, len, nil); + free(buf); + if(m == nil) + return -1; + + /* + * Sign it. + */ + s = rsadecrypt(key, m, nil); + mpfree(m); + if(s == nil) + return -1; + mptoberjust(s, sig, len); + mpfree(s); + return len; +} + +/* + * Mptobe but shift right to fill buffer. + */ +void +mptoberjust(mpint *b, uchar *buf, uint len) +{ + int n; + + n = mptobe(b, buf, len, nil); + assert(n >= 0); + if(n < len){ + len -= n; + memmove(buf+len, buf, n); + memset(buf, 0, len); + } +} + +/* + * Simple ASN.1 encodings. + * Lengths < 128 are encoded as 1-bytes constants, + * making our life easy. + */ + +/* + * Hash OIDs + * + * SHA1 = 1.3.14.3.2.26 + * MDx = 1.2.840.113549.2.x + */ +#define O0(a,b) ((a)*40+(b)) +#define O2(x) \ + (((x)>>7)&0x7F)|0x80, \ + ((x)&0x7F) +#define O3(x) \ + (((x)>>14)&0x7F)|0x80, \ + (((x)>>7)&0x7F)|0x80, \ + ((x)&0x7F) +uchar oidsha1[] = { O0(1, 3), 14, 3, 2, 26 }; +uchar oidmd2[] = { O0(1, 2), O2(840), O3(113549), 2, 2 }; +uchar oidmd5[] = { O0(1, 2), O2(840), O3(113549), 2, 5 }; + +/* + * DigestInfo ::= SEQUENCE { + * digestAlgorithm AlgorithmIdentifier, + * digest OCTET STRING + * } + */ +static int +mkasn1(uchar *asn1, DigestAlg *alg, uchar *d, uint dlen) +{ + uchar *obj, *p; + uint olen; + + if(alg == sha1){ + obj = oidsha1; + olen = sizeof(oidsha1); + }else if(alg == md5){ + obj = oidmd5; + olen = sizeof(oidmd5); + }else{ + sysfatal("bad alg in mkasn1"); + return -1; + } + + p = asn1; + *p++ = 0x30; /* sequence */ + p++; + + *p++ = 0x06; /* object id */ + *p++ = olen; + memmove(p, obj, olen); + p += olen; + + *p++ = 0x04; /* octet string */ + *p++ = dlen; + memmove(p, d, dlen); + p += dlen; + + asn1[1] = p - (asn1+2); + return p-asn1; +} + diff --git a/src/cmd/auth/factotum/plan9.c b/src/cmd/auth/factotum/plan9.c new file mode 100644 index 00000000..0b6bb601 --- /dev/null +++ b/src/cmd/auth/factotum/plan9.c @@ -0,0 +1,45 @@ +#include "std.h" +#include "dat.h" +#include + +int +memrandom(void *p, int n) +{ + uchar *cp; + + for(cp = (uchar*)p; n > 0; n--) + *cp++ = fastrand(); + return 0; +} + +Attr* +addcap(Attr *a, char *from, Ticket *t) +{ + return addattr(a, "cuid=%q suid=%q cap=''", t->cuid, t->suid); +} + +int +_authdial(char *net, char *authdom) +{ + return authdial(net, authdom); +} + +Key* +plan9authkey(Attr *a) +{ + char *dom; + Key *k; + + /* + * The only important part of a is dom. + * We don't care, for example, about user name. + */ + dom = strfindattr(a, "dom"); + if(dom) + k = keylookup("proto=p9sk1 role=server user? dom=%q", dom); + else + k = keylookup("proto=p9sk1 role=server user? dom?"); + if(k == nil) + werrstr("could not find plan 9 auth key dom %q", dom); + return k; +} diff --git a/src/cmd/auth/factotum/proto.c b/src/cmd/auth/factotum/proto.c new file mode 100644 index 00000000..a455bd0a --- /dev/null +++ b/src/cmd/auth/factotum/proto.c @@ -0,0 +1,34 @@ +#include "std.h" +#include "dat.h" + +extern Proto apop; /* apop.c */ +extern Proto chap; /* chap.c */ +extern Proto cram; /* apop.c */ +extern Proto dsa; /* dsa.c */ +extern Proto mschap; /* chap.c */ +extern Proto p9any; /* p9any.c */ +extern Proto p9sk1; /* p9sk1.c */ +extern Proto p9sk2; /* p9sk2.c */ +extern Proto rsa; /* rsa.c */ + +Proto *prototab[] = { + &apop, + &cram, + &dsa, + &p9any, + &p9sk1, + &p9sk2, + &rsa, + nil, +}; + +Proto* +protolookup(char *name) +{ + int i; + + for(i=0; prototab[i]; i++) + if(strcmp(prototab[i]->name, name) == 0) + return prototab[i]; + return nil; +} diff --git a/src/cmd/auth/factotum/rpc.c b/src/cmd/auth/factotum/rpc.c new file mode 100644 index 00000000..e9c163aa --- /dev/null +++ b/src/cmd/auth/factotum/rpc.c @@ -0,0 +1,315 @@ +#include "std.h" +#include "dat.h" + +/* + * Factotum RPC + * + * Must be paired write/read cycles on /mnt/factotum/rpc. + * The format of a request is verb, single space, data. + * Data format is verb-dependent; in particular, it can be binary. + * The format of a response is the same. The write only sets up + * the RPC. The read tries to execute it. If the /mnt/factotum/key + * file is open, we ask for new keys using that instead of returning + * an error in the RPC. This means the read blocks. + * Textual arguments are parsed with tokenize, so rc-style quoting + * rules apply. + * + * Only authentication protocol messages go here. Configuration + * is still via ctl (below). + * + * Request RPCs are: + * start attrs - initializes protocol for authentication, can fail. + * returns "ok read" or "ok write" on success. + * read - execute protocol read + * write - execute protocol write + * authinfo - if the protocol is finished, return the AI if any + * attr - return protocol information + * Return values are: + * error message - an error happened. + * ok [data] - success, possible data is request dependent. + * needkey attrs - request aborted, get me this key and try again + * badkey attrs - request aborted, this key might be bad + * done [haveai] - authentication is done [haveai: you can get an ai with authinfo] + */ + +char *rpcname[] = +{ + "unknown", + "authinfo", + "attr", + "read", + "start", + "write", +}; + +static int +classify(char *s) +{ + int i; + + for(i=1; i= MaxRpc){ + werrstr("rpc too large"); + return -1; + } + + /* cancel any current rpc */ + c->rpc.op = RpcUnknown; + c->nreply = 0; + + /* parse new rpc */ + memmove(c->rpcbuf, data, count); + c->rpcbuf[count] = 0; + if(p = (uchar*)strchr((char*)c->rpcbuf, ' ')){ + *p++ = '\0'; + c->rpc.data = p; + c->rpc.count = count - (p - (uchar*)c->rpcbuf); + }else{ + c->rpc.data = ""; + c->rpc.count = 0; + } + op = classify(c->rpcbuf); + if(op == RpcUnknown){ + werrstr("bad rpc verb: %s", c->rpcbuf); + return -1; + } + + c->rpc.op = op; + return 0; +} + +void +convthread(void *v) +{ + Conv *c; + Attr *a; + char *role, *proto; + Proto *p; + Role *r; + + c = v; + a = parseattr(c->rpc.data); + if(a == nil){ + werrstr("empty attr"); + goto out; + } + c->attr = a; + proto = strfindattr(a, "proto"); + role = strfindattr(a, "role"); + + if(proto == nil){ + werrstr("no proto in attrs"); + goto out; + } + if(role == nil){ + werrstr("no role in attrs"); + goto out; + } + + p = protolookup(proto); + if(p == nil){ + werrstr("unknown proto %s", proto); + goto out; + } + + c->proto = p; + for(r=p->roles; r->name; r++){ + if(strcmp(r->name, role) != 0) + continue; + rpcrespond(c, "ok"); + c->active = 1; + if((*r->fn)(c) == 0){ + c->done = 1; + werrstr("protocol finished"); + }else + werrstr("%s %s %s: %r", p->name, r->name, c->state); + goto out; + } + werrstr("unknown role"); + +out: + c->active = 0; + c->state = 0; + rerrstr(c->err, sizeof c->err); + rpcrespond(c, "error %r"); + convclose(c); +} + +static uchar* convAI2M(uchar *p, int n, char *cuid, char *suid, char *cap, char *hex); + +void +rpcexec(Conv *c) +{ + uchar *p; + + switch(c->rpc.op){ + case RpcRead: + if(c->rpc.count > 0){ + rpcrespond(c, "error read takes no parameters"); + break; + } + /* fall through */ + default: + if(!c->active){ + if(c->done) + rpcrespond(c, "done"); + else + rpcrespond(c, "error %s", c->err); + break; + } + nbsendp(c->rpcwait, 0); + break; + case RpcUnknown: + break; + case RpcAuthinfo: + /* deprecated */ + if(c->active) + rpcrespond(c, "error conversation still active"); + else if(!c->done) + rpcrespond(c, "error conversation not successful"); + else{ + /* make up an auth info using the attr */ + p = convAI2M((uchar*)c->reply+3, sizeof c->reply-3, + strfindattr(c->attr, "cuid"), + strfindattr(c->attr, "suid"), + strfindattr(c->attr, "cap"), + strfindattr(c->attr, "secret")); + if(p == nil) + rpcrespond(c, "error %r"); + else + rpcrespondn(c, "ok", c->reply+3, p-(uchar*)(c->reply+3)); + } + break; + case RpcAttr: + rpcrespond(c, "ok %A", c->attr); + break; + case RpcStart: + convreset(c); + c->ref++; + threadcreate(convthread, c, STACK); + break; + } +} + +void +rpcrespond(Conv *c, char *fmt, ...) +{ + va_list arg; + + if(c->hangup) + return; + + if(fmt == nil) + fmt = ""; + + va_start(arg, fmt); + c->nreply = vsnprint(c->reply, sizeof c->reply, fmt, arg); + va_end(arg); + (*c->kickreply)(c); + c->rpc.op = RpcUnknown; +} + +void +rpcrespondn(Conv *c, char *verb, void *data, int count) +{ + char *p; + + if(c->hangup) + return; + + if(strlen(verb)+1+count > sizeof c->reply){ + print("RPC response too large; caller %#lux", getcallerpc(&c)); + return; + } + + strcpy(c->reply, verb); + p = c->reply + strlen(c->reply); + *p++ = ' '; + memmove(p, data, count); + c->nreply = count + (p - c->reply); + (*c->kickreply)(c); + c->rpc.op = RpcUnknown; +} + +/* deprecated */ +static uchar* +pstring(uchar *p, uchar *e, char *s) +{ + uint n; + + if(p == nil) + return nil; + if(s == nil) + s = ""; + n = strlen(s); + if(p+n+BIT16SZ >= e) + return nil; + PBIT16(p, n); + p += BIT16SZ; + memmove(p, s, n); + p += n; + return p; +} + +static uchar* +pcarray(uchar *p, uchar *e, uchar *s, uint n) +{ + if(p == nil) + return nil; + if(s == nil){ + if(n > 0) + sysfatal("pcarray"); + s = (uchar*)""; + } + if(p+n+BIT16SZ >= e) + return nil; + PBIT16(p, n); + p += BIT16SZ; + memmove(p, s, n); + p += n; + return p; +} + +static uchar* +convAI2M(uchar *p, int n, char *cuid, char *suid, char *cap, char *hex) +{ + uchar *e = p+n; + uchar *secret; + int nsecret; + + if(cuid == nil) + cuid = ""; + if(suid == nil) + suid = ""; + if(cap == nil) + cap = ""; + if(hex == nil) + hex = ""; + nsecret = strlen(hex)/2; + secret = emalloc(nsecret); + if(hexparse(hex, secret, nsecret) < 0){ + werrstr("hexparse %s failed", hex); /* can't happen */ + free(secret); + return nil; + } + p = pstring(p, e, cuid); + p = pstring(p, e, suid); + p = pstring(p, e, cap); + p = pcarray(p, e, secret, nsecret); + free(secret); + if(p == nil) + werrstr("authinfo too big"); + return p; +} + diff --git a/src/cmd/auth/factotum/rsa.c b/src/cmd/auth/factotum/rsa.c new file mode 100644 index 00000000..327dbc5b --- /dev/null +++ b/src/cmd/auth/factotum/rsa.c @@ -0,0 +1,191 @@ +#include "std.h" +#include "dat.h" + +/* + * RSA authentication. + * + * Client: + * start n=xxx ek=xxx + * write msg + * read decrypt(msg) + * + * Sign (PKCS #1 using hash=sha1 or hash=md5) + * start n=xxx ek=xxx + * write hash(msg) + * read signature(hash(msg)) + * + * all numbers are hexadecimal biginits parsable with strtomp. + * must be lower case for attribute matching in start. + */ + +static int +rsaclient(Conv *c) +{ + char *chal; + mpint *m; + Key *k; + + k = keylookup("%A", c->attr); + if(k == nil) + return -1; + c->state = "read challenge"; + if(convreadm(c, &chal) < 0){ + keyclose(k); + return -1; + } + if(strlen(chal) < 32){ + badchal: + free(chal); + convprint(c, "bad challenge"); + keyclose(k); + return -1; + } + m = strtomp(chal, nil, 16, nil); + if(m == nil) + goto badchal; + free(chal); + m = rsadecrypt(k->priv, m, m); + convprint(c, "%B", m); + mpfree(m); + keyclose(k); + return 0; +} + +static int +xrsasign(Conv *c) +{ + char *hash; + int dlen, n; + DigestAlg *hashfn; + Key *k; + uchar sig[1024], digest[64]; + + k = keylookup("%A", c->attr); + if(k == nil) + return -1; + hash = strfindattr(k->attr, "hash"); + if(hash == nil) + hash = "sha1"; + if(strcmp(hash, "sha1") == 0){ + hashfn = sha1; + dlen = SHA1dlen; + }else if(strcmp(hash, "md5") == 0){ + hashfn = md5; + dlen = MD5dlen; + }else{ + werrstr("unknown hash function %s", hash); + return -1; + } + c->state = "read data"; + if((n=convread(c, digest, dlen)) < 0){ + keyclose(k); + return -1; + } + memset(sig, 0xAA, sizeof sig); + n = rsasign(k->priv, hashfn, digest, dlen, sig, sizeof sig); + keyclose(k); + if(n < 0) + return -1; + convwrite(c, sig, n); + return 0; +} + +/* + * convert to canonical form (lower case) + * for use in attribute matches. + */ +static void +strlwr(char *a) +{ + for(; *a; a++){ + if('A' <= *a && *a <= 'Z') + *a += 'a' - 'A'; + } +} + +static RSApriv* +readrsapriv(Key *k) +{ + char *a; + RSApriv *priv; + + priv = rsaprivalloc(); + + if((a=strfindattr(k->attr, "ek"))==nil + || (priv->pub.ek=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->attr, "n"))==nil + || (priv->pub.n=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!p"))==nil + || (priv->p=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!q"))==nil + || (priv->q=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!kp"))==nil + || (priv->kp=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!kq"))==nil + || (priv->kq=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!c2"))==nil + || (priv->c2=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + if((a=strfindattr(k->privattr, "!dk"))==nil + || (priv->dk=strtomp(a, nil, 16, nil))==nil) + goto Error; + strlwr(a); + return priv; + +Error: + rsaprivfree(priv); + return nil; +} + +static int +rsacheck(Key *k) +{ + static int first = 1; + + if(first){ + fmtinstall('B', mpfmt); + first = 0; + } + + if((k->priv = readrsapriv(k)) == nil){ + werrstr("malformed key data"); + return -1; + } + return 0; +} + +static void +rsaclose(Key *k) +{ + rsaprivfree(k->priv); + k->priv = nil; +} + +static Role +rsaroles[] = +{ + "client", rsaclient, + "sign", xrsasign, + 0 +}; + +Proto rsa = { + "rsa", + rsaroles, + nil, + rsacheck, + rsaclose +}; diff --git a/src/cmd/auth/factotum/secstore.c b/src/cmd/auth/factotum/secstore.c new file mode 100644 index 00000000..d82d7862 --- /dev/null +++ b/src/cmd/auth/factotum/secstore.c @@ -0,0 +1,644 @@ +/* + * Various files from /sys/src/cmd/auth/secstore, just enough + * to download a file at boot time. + */ + +#include "std.h" +#include "dat.h" +#include + +enum{ CHK = 16}; +enum{ MAXFILESIZE = 10*1024*1024 }; + +enum{// PW status bits + Enabled = (1<<0), + STA = (1<<1), // extra SecurID step +}; + +static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n"; +char *secstore; + +int +secdial(void) +{ + char *p; + + p = secstore; + if(p == nil) /* else use the authserver */ + p = getenv("secstore"); + if(p == nil) + p = getenv("auth"); + if(p == nil) + p = "secstore"; + + return dial(netmkaddr(p, "net", "secstore"), 0, 0, 0); +} + + +int +havesecstore(void) +{ + int m, n, fd; + uchar buf[500]; + + n = snprint((char*)buf, sizeof buf, testmess, owner); + hnputs(buf, 0x8000+n-2); + + fd = secdial(); + if(fd < 0) + return 0; + if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2){ + close(fd); + return 0; + } + n = ((buf[0]&0x7f)<<8) + buf[1]; + if(n+1 > sizeof buf){ + werrstr("implausibly large count %d", n); + close(fd); + return 0; + } + m = readn(fd, buf, n); + close(fd); + if(m != n){ + if(m >= 0) + werrstr("short read from secstore"); + return 0; + } + buf[n] = 0; + if(strcmp((char*)buf, "!account expired") == 0){ + werrstr("account expired"); + return 0; + } + return strcmp((char*)buf, "!account exists") == 0; +} + +// delimited, authenticated, encrypted connection +enum{ Maxmsg=4096 }; // messages > Maxmsg bytes are truncated +typedef struct SConn SConn; + +extern SConn* newSConn(int); // arg is open file descriptor +struct SConn{ + void *chan; + int secretlen; + int (*secret)(SConn*, uchar*, int);// + int (*read)(SConn*, uchar*, int); // <0 if error; errmess in buffer + int (*write)(SConn*, uchar*, int); + void (*free)(SConn*); // also closes file descriptor +}; +// secret(s,b,dir) sets secret for digest, encrypt, using the secretlen +// bytes in b to form keys for the two directions; +// set dir=0 in client, dir=1 in server + +// error convention: write !message in-band +#define readstr secstore_readstr +static void writerr(SConn*, char*); +static int readstr(SConn*, char*); // call with buf of size Maxmsg+1 + // returns -1 upon error, with error message in buf + +typedef struct ConnState { + uchar secret[SHA1dlen]; + ulong seqno; + RC4state rc4; +} ConnState; + +typedef struct SS{ + int fd; // file descriptor for read/write of encrypted data + int alg; // if nonzero, "alg sha rc4_128" + ConnState in, out; +} SS; + +static int +SC_secret(SConn *conn, uchar *sigma, int direction) +{ + SS *ss = (SS*)(conn->chan); + int nsigma = conn->secretlen; + + if(direction != 0){ + hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil); + hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil); + }else{ + hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil); + hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil); + } + setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits + setupRC4state(&ss->out.rc4, ss->out.secret, 16); + ss->alg = 1; + return 0; +} + +static void +hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen]) +{ + DigestState sha; + uchar seq[4]; + + seq[0] = seqno>>24; + seq[1] = seqno>>16; + seq[2] = seqno>>8; + seq[3] = seqno; + memset(&sha, 0, sizeof sha); + sha1(secret, SHA1dlen, nil, &sha); + sha1(data, len, nil, &sha); + sha1(seq, 4, d, &sha); +} + +static int +verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen]) +{ + DigestState sha; + uchar seq[4]; + uchar digest[SHA1dlen]; + + seq[0] = seqno>>24; + seq[1] = seqno>>16; + seq[2] = seqno>>8; + seq[3] = seqno; + memset(&sha, 0, sizeof sha); + sha1(secret, SHA1dlen, nil, &sha); + sha1(data, len, nil, &sha); + sha1(seq, 4, digest, &sha); + return memcmp(d, digest, SHA1dlen); +} + +static int +SC_read(SConn *conn, uchar *buf, int n) +{ + SS *ss = (SS*)(conn->chan); + uchar count[2], digest[SHA1dlen]; + int len, nr; + + if(read(ss->fd, count, 2) != 2 || count[0]&0x80 == 0){ + werrstr("!SC_read invalid count"); + return -1; + } + len = (count[0]&0x7f)<<8 | count[1]; // SSL-style count; no pad + if(ss->alg){ + len -= SHA1dlen; + if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){ + werrstr("!SC_read missing sha1"); + return -1; + } + if(len > n || readn(ss->fd, buf, len) != len){ + werrstr("!SC_read missing data"); + return -1; + } + rc4(&ss->in.rc4, digest, SHA1dlen); + rc4(&ss->in.rc4, buf, len); + if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){ + werrstr("!SC_read integrity check failed"); + return -1; + } + }else{ + if(len <= 0 || len > n){ + werrstr("!SC_read implausible record length"); + return -1; + } + if( (nr = readn(ss->fd, buf, len)) != len){ + werrstr("!SC_read expected %d bytes, but got %d", len, nr); + return -1; + } + } + ss->in.seqno++; + return len; +} + +static int +SC_write(SConn *conn, uchar *buf, int n) +{ + SS *ss = (SS*)(conn->chan); + uchar count[2], digest[SHA1dlen], enc[Maxmsg+1]; + int len; + + if(n <= 0 || n > Maxmsg+1){ + werrstr("!SC_write invalid n %d", n); + return -1; + } + len = n; + if(ss->alg) + len += SHA1dlen; + count[0] = 0x80 | len>>8; + count[1] = len; + if(write(ss->fd, count, 2) != 2){ + werrstr("!SC_write invalid count"); + return -1; + } + if(ss->alg){ + hash(ss->out.secret, buf, n, ss->out.seqno, digest); + rc4(&ss->out.rc4, digest, SHA1dlen); + memcpy(enc, buf, n); + rc4(&ss->out.rc4, enc, n); + if(write(ss->fd, digest, SHA1dlen) != SHA1dlen || + write(ss->fd, enc, n) != n){ + werrstr("!SC_write error on send"); + return -1; + } + }else{ + if(write(ss->fd, buf, n) != n){ + werrstr("!SC_write error on send"); + return -1; + } + } + ss->out.seqno++; + return n; +} + +static void +SC_free(SConn *conn) +{ + SS *ss = (SS*)(conn->chan); + + close(ss->fd); + free(ss); + free(conn); +} + +SConn* +newSConn(int fd) +{ + SS *ss; + SConn *conn; + + if(fd < 0) + return nil; + ss = (SS*)emalloc(sizeof(*ss)); + conn = (SConn*)emalloc(sizeof(*conn)); + ss->fd = fd; + ss->alg = 0; + conn->chan = (void*)ss; + conn->secretlen = SHA1dlen; + conn->free = SC_free; + conn->secret = SC_secret; + conn->read = SC_read; + conn->write = SC_write; + return conn; +} + +static void +writerr(SConn *conn, char *s) +{ + char buf[Maxmsg]; + + snprint(buf, Maxmsg, "!%s", s); + conn->write(conn, (uchar*)buf, strlen(buf)); +} + +static int +readstr(SConn *conn, char *s) +{ + int n; + + n = conn->read(conn, (uchar*)s, Maxmsg); + if(n >= 0){ + s[n] = 0; + if(s[0] == '!'){ + memmove(s, s+1, n); + n = -1; + } + }else{ + strcpy(s, "read error"); + } + return n; +} + +static int +getfile(SConn *conn, uchar *key, int nkey) +{ + char *buf; + int nbuf, n, nr, len; + char s[Maxmsg+1], *gf, *p, *q; + uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw; + AESstate aes; + DigestState *sha; + + gf = "factotum"; + memset(&aes, 0, sizeof aes); + + snprint(s, Maxmsg, "GET %s\n", gf); + conn->write(conn, (uchar*)s, strlen(s)); + + /* get file size */ + s[0] = '\0'; + if(readstr(conn, s) < 0){ + werrstr("secstore: %r"); + return -1; + } + if((len = atoi(s)) < 0){ + werrstr("secstore: remote file %s does not exist", gf); + return -1; + }else if(len > MAXFILESIZE){//assert + werrstr("secstore: implausible file size %d for %s", len, gf); + return -1; + } + + ibr = ibw = ib; + buf = nil; + nbuf = 0; + for(nr=0; nr < len;){ + if((n = conn->read(conn, ibw, Maxmsg)) <= 0){ + werrstr("secstore: empty file chunk n=%d nr=%d len=%d: %r", n, nr, len); + return -1; + } + nr += n; + ibw += n; + if(!aes.setup){ /* first time, read 16 byte IV */ + if(n < 16){ + werrstr("secstore: no IV in file"); + return -1; + } + sha = sha1((uchar*)"aescbc file", 11, nil, nil); + sha1(key, nkey, skey, sha); + setupAESstate(&aes, skey, AESbsize, ibr); + memset(skey, 0, sizeof skey); + ibr += AESbsize; + n -= AESbsize; + } + aesCBCdecrypt(ibw-n, n, &aes); + n = ibw-ibr-CHK; + if(n > 0){ + buf = erealloc(buf, nbuf+n+1); + memmove(buf+nbuf, ibr, n); + nbuf += n; + ibr += n; + } + memmove(ib, ibr, ibw-ibr); + ibw = ib + (ibw-ibr); + ibr = ib; + } + n = ibw-ibr; + if((n != CHK) || (memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0)){ + werrstr("secstore: decrypted file failed to authenticate!"); + free(buf); + return -1; + } + if(nbuf == 0){ + werrstr("secstore got empty file"); + return -1; + } + buf[nbuf] = '\0'; + p = buf; + n = 0; + while(p){ + if(q = strchr(p, '\n')) + *q++ = '\0'; + n++; + if(ctlwrite(p) < 0) + fprint(2, "secstore(%s) line %d: %r\n", gf, n); + p = q; + } + free(buf); + return 0; +} + +static char VERSION[] = "secstore"; + +typedef struct PAKparams{ + mpint *q, *p, *r, *g; +} PAKparams; + +static PAKparams *pak; + +// This group was generated by the seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E. +static void +initPAKparams(void) +{ + if(pak) + return; + pak = (PAKparams*)emalloc(sizeof(*pak)); + pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil); + pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBBD" + "B12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86" + "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9" + "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", nil, 16, nil); + pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241CEF" + "2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E887" + "D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D21" + "C4656848614D888A4", nil, 16, nil); + pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D2327173444" + "ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD41" + "0E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734E3E" + "2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", nil, 16, nil); +} + +// H = (sha(ver,C,sha(passphrase)))^r mod p, +// a hash function expensive to attack by brute force. +static void +longhash(char *ver, char *C, uchar *passwd, mpint *H) +{ + uchar *Cp; + int i, n, nver, nC; + uchar buf[140], key[1]; + + nver = strlen(ver); + nC = strlen(C); + n = nver + nC + SHA1dlen; + Cp = (uchar*)emalloc(n); + memmove(Cp, ver, nver); + memmove(Cp+nver, C, nC); + memmove(Cp+nver+nC, passwd, SHA1dlen); + for(i = 0; i < 7; i++){ + key[0] = 'A'+i; + hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil); + } + memset(Cp, 0, n); + free(Cp); + betomp(buf, sizeof buf, H); + mpmod(H, pak->p, H); + mpexp(H, pak->r, pak->p, H); +} + +// Hi = H^-1 mod p +static char * +PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi) +{ + uchar passhash[SHA1dlen]; + + sha1((uchar *)passphrase, strlen(passphrase), passhash, nil); + initPAKparams(); + longhash(VERSION, C, passhash, H); + mpinvert(H, pak->p, Hi); + return mptoa(Hi, 64, nil, 0); +} + +// another, faster, hash function for each party to +// confirm that the other has the right secrets. +static void +shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest) +{ + SHA1state *state; + + state = sha1((uchar*)mess, strlen(mess), 0, 0); + state = sha1((uchar*)C, strlen(C), 0, state); + state = sha1((uchar*)S, strlen(S), 0, state); + state = sha1((uchar*)m, strlen(m), 0, state); + state = sha1((uchar*)mu, strlen(mu), 0, state); + state = sha1((uchar*)sigma, strlen(sigma), 0, state); + state = sha1((uchar*)Hi, strlen(Hi), 0, state); + state = sha1((uchar*)mess, strlen(mess), 0, state); + state = sha1((uchar*)C, strlen(C), 0, state); + state = sha1((uchar*)S, strlen(S), 0, state); + state = sha1((uchar*)m, strlen(m), 0, state); + state = sha1((uchar*)mu, strlen(mu), 0, state); + state = sha1((uchar*)sigma, strlen(sigma), 0, state); + sha1((uchar*)Hi, strlen(Hi), digest, state); +} + +// On input, conn provides an open channel to the server; +// C is the name this client calls itself; +// pass is the user's passphrase +// On output, session secret has been set in conn +// (unless return code is negative, which means failure). +// If pS is not nil, it is set to the (alloc'd) name the server calls itself. +static int +PAKclient(SConn *conn, char *C, char *pass, char **pS) +{ + char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi; + char kc[2*SHA1dlen+1]; + uchar digest[SHA1dlen]; + int rc = -1, n; + mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0); + mpint *H = mpnew(0), *Hi = mpnew(0); + + hexHi = PAK_Hi(C, pass, H, Hi); + + // random 1<=x<=q-1; send C, m=g**x H + x = mprand(164, genrandom, nil); + mpmod(x, pak->q, x); + if(mpcmp(x, mpzero) == 0) + mpassign(mpone, x); + mpexp(pak->g, x, pak->p, m); + mpmul(m, H, m); + mpmod(m, pak->p, m); + hexm = mptoa(m, 64, nil, 0); + mess = (char*)emalloc(2*Maxmsg+2); + mess2 = mess+Maxmsg+1; + snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm); + conn->write(conn, (uchar*)mess, strlen(mess)); + + // recv g**y, S, check hash1(g**xy) + if(readstr(conn, mess) < 0){ + fprint(2, "error: %s\n", mess); + writerr(conn, "couldn't read g**y"); + goto done; + } + eol = strchr(mess, '\n'); + if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){ + writerr(conn, "verifier syntax error"); + goto done; + } + hexmu = mess+3; + *eol = 0; + ks = eol+3; + eol = strchr(ks, '\n'); + if(!eol || strncmp("\nS=", eol, 3) != 0){ + writerr(conn, "verifier syntax error for secstore 1.0"); + goto done; + } + *eol = 0; + S = eol+3; + eol = strchr(S, '\n'); + if(!eol){ + writerr(conn, "verifier syntax error for secstore 1.0"); + goto done; + } + *eol = 0; + if(pS) + *pS = estrdup(S); + strtomp(hexmu, nil, 64, mu); + mpexp(mu, x, pak->p, sigma); + hexsigma = mptoa(sigma, 64, nil, 0); + shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(kc, sizeof kc, digest, SHA1dlen); + if(strcmp(ks, kc) != 0){ + writerr(conn, "verifier didn't match"); + goto done; + } + + // send hash2(g**xy) + shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(kc, sizeof kc, digest, SHA1dlen); + snprint(mess2, Maxmsg, "k'=%s\n", kc); + conn->write(conn, (uchar*)mess2, strlen(mess2)); + + // set session key + shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest); + memset(hexsigma, 0, strlen(hexsigma)); + n = conn->secret(conn, digest, 0); + memset(digest, 0, SHA1dlen); + if(n < 0){//assert + writerr(conn, "can't set secret"); + goto done; + } + + rc = 0; +done: + mpfree(x); + mpfree(sigma); + mpfree(mu); + mpfree(m); + mpfree(Hi); + mpfree(H); + free(hexsigma); + free(hexHi); + free(hexm); + free(mess); + return rc; +} + +int +secstorefetch(void) +{ + int rv = -1, fd; + char s[Maxmsg+1]; + SConn *conn; + char *pass, *sta; + + sta = nil; + conn = nil; + pass = readcons("secstore password", nil, 1); + if(pass==nil || strlen(pass)==0){ + werrstr("cancel"); + goto Out; + } + if((fd = secdial()) < 0) + goto Out; + if((conn = newSConn(fd)) == nil) + goto Out; + if(PAKclient(conn, owner, pass, nil) < 0){ + werrstr("password mistyped?"); + goto Out; + } + if(readstr(conn, s) < 0) + goto Out; + if(strcmp(s, "STA") == 0){ + sta = readcons("STA PIN+SecureID", nil, 1); + if(sta==nil || strlen(sta)==0){ + werrstr("cancel"); + goto Out; + } + if(strlen(sta) >= sizeof s - 3){ + werrstr("STA response too long"); + goto Out; + } + strcpy(s+3, sta); + conn->write(conn, (uchar*)s, strlen(s)); + readstr(conn, s); + } + if(strcmp(s, "OK") !=0){ + werrstr("%s", s); + goto Out; + } + if(getfile(conn, (uchar*)pass, strlen(pass)) < 0) + goto Out; + conn->write(conn, (uchar*)"BYE", 3); + rv = 0; + +Out: + if(conn) + conn->free(conn); + if(pass) + free(pass); + if(sta) + free(sta); + return rv; +} + diff --git a/src/cmd/auth/factotum/std.h b/src/cmd/auth/factotum/std.h new file mode 100644 index 00000000..814664e0 --- /dev/null +++ b/src/cmd/auth/factotum/std.h @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include <9p.h> + diff --git a/src/cmd/auth/factotum/test.c b/src/cmd/auth/factotum/test.c new file mode 100644 index 00000000..b4104898 --- /dev/null +++ b/src/cmd/auth/factotum/test.c @@ -0,0 +1,121 @@ +#include +#include +#include + +typedef struct Test Test; + +struct Test +{ + char *name; + int (*server)(Test*, AuthRpc*, int); + int (*client)(Test*, int); +}; + +int +ai2status(AuthInfo *ai) +{ + if(ai == nil) + return -1; + auth_freeAI(ai); + return 0; +} + +int +proxyserver(Test *t, AuthRpc *rpc, int fd) +{ + char buf[1024]; + + sprint(buf, "proto=%q role=server", t->name); + return ai2status(fauth_proxy(fd, rpc, nil, buf)); +} + +int +proxyclient(Test *t, int fd) +{ + return ai2status(auth_proxy(fd, auth_getkey, "proto=%q role=client", t->name)); +} + +Test test[] = +{ + "apop", proxyserver, proxyclient, + "cram", proxyserver, proxyclient, + "p9sk1", proxyserver, proxyclient, + "p9sk2", proxyserver, proxyclient, + "p9any", proxyserver, proxyclient, +}; + +void +usage(void) +{ + fprint(2, "usage: test [name]...\n"); + exits("usage"); +} + +void +runtest(AuthRpc *srpc, Test *t) +{ + int p[2], bad; + Waitmsg *w; + + if(pipe(p) < 0) + sysfatal("pipe: %r"); + + print("%s...", t->name); + + switch(fork()){ + case -1: + sysfatal("fork: %r"); + + case 0: + close(p[0]); + if((*t->server)(t, srpc, p[1]) < 0){ + print("\n\tserver: %r"); + _exits("oops"); + } + close(p[1]); + _exits(nil); + default: + close(p[1]); + if((*t->client)(t, p[0]) < 0){ + print("\n\tclient: %r"); + bad = 1; + } + close(p[0]); + break; + } + w = wait(); + if(w->msg[0]) + bad = 1; + print("\n"); +} + +void +main(int argc, char **argv) +{ + int i, j; + int afd; + AuthRpc *srpc; + + ARGBEGIN{ + default: + usage(); + }ARGEND + + quotefmtinstall(); + afd = open("/n/kremvax/factotum/rpc", ORDWR); + if(afd < 0) + sysfatal("open /n/kremvax/factotum/rpc: %r"); + srpc = auth_allocrpc(afd); + if(srpc == nil) + sysfatal("auth_allocrpc: %r"); + + if(argc == 0) + for(i=0; i ndat) + return -1; + if(hex[strspn(hex, "0123456789abcdefABCDEF")] != '\0') + return -1; + for(i=0; i +#include +#include + +void +f(void*) +{ +} + +void +main(void) +{ + f(auth_challenge); + f(auth_response); +} diff --git a/src/cmd/auth/factotum/xio.c b/src/cmd/auth/factotum/xio.c new file mode 100644 index 00000000..2e6b141b --- /dev/null +++ b/src/cmd/auth/factotum/xio.c @@ -0,0 +1,165 @@ +#include "std.h" +#include "dat.h" + +static Ioproc *cache[5]; +static int ncache; + +static Ioproc* +xioproc(void) +{ + Ioproc *c; + int i; + + for(i=0; i