| /* |
| * 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); |
| flog("p9skclient success %A", c->attr); /* before adding secret! */ |
| des56to64((uchar*)t.key, secret); |
| c->attr = addattr(c->attr, "secret=%.8H", secret); |
| ret = 0; |
| |
| out: |
| if(ret < 0) |
| flog("p9skclient: %r"); |
| 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); |
| flog("p9skserver success %A", c->attr); /* before adding secret! */ |
| des56to64((uchar*)t.key, secret); |
| c->attr = addattr(c->attr, "secret=%.8H", secret); |
| ret = 0; |
| |
| out: |
| if(ret < 0) |
| flog("p9skserver: %r"); |
| 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 = { |
| "p9sk1", |
| p9sk1roles, |
| "user? dom? !password?", |
| p9sk1check, |
| p9sk1close |
| }; |
| |
| Proto p9sk2 = { |
| "p9sk2", |
| p9sk2roles |
| }; |
| |