ref: 3b7e03a30ed29ee0666c00568bf67b8179c28d12
dir: /sys/src/cmd/auth/factotum/p9sk1.c/
/* * p9sk1, dp9ik - Plan 9 secret (private) key authentication. * dp9ik uses AuthPAK diffie hellman key exchange with the * auth server to protect the password derived key from offline * dictionary attacks. * * Client protocol: * write challenge[challen] * read pakreq[ticketreqlen + pakylen] (dp9ik only) * write paky[pakylen] * read tickreq[tickreqlen] (p9sk1 only) * write ticket + authenticator * read authenticator * * Server protocol: * read challenge[challen] * write pakreq[ticketreqlen + pakylen] (dp9ik only) * read paky[pakylen] * write tickreq[tickreqlen] (p9sk1 only) * read ticket + authenticator * write authenticator * * Login protocol: * write user * write password */ #include "dat.h" struct State { Key *key; Ticket t; Ticketreq tr; Authkey k; PAKpriv p; char cchal[CHALLEN]; uchar y[PAKYLEN], rand[2*NONCELEN]; char tbuf[MAXTICKETLEN + MAXAUTHENTLEN]; int tbuflen; uchar *secret; int speakfor; }; enum { /* client phases */ CHaveChal, CNeedPAKreq, /* dp9ik only */ CHavePAKy, CNeedTreq, /* p9sk1 only */ CHaveTicket, CNeedAuth, /* server phases */ SNeedChal, SHavePAKreq, /* dp9ik only */ SNeedPAKy, SHaveTreq, /* p9sk1 only */ SNeedTicket, SHaveAuth, /* login phases */ SNeedUser, SNeedPass, Maxphase, }; static char *phasenames[Maxphase] = { [CHaveChal] "CHaveChal", [CNeedPAKreq] "CNeedPAKreq", [CHavePAKy] "CHavePAKy", [CNeedTreq] "CNeedTreq", [CHaveTicket] "CHaveTicket", [CNeedAuth] "CNeedAuth", [SNeedChal] "SNeedChal", [SHavePAKreq] "SHavePAKreq", [SNeedPAKy] "SNeedPAKy", [SHaveTreq] "SHaveTreq", [SNeedTicket] "SNeedTicket", [SHaveAuth] "SHaveAuth", [SNeedUser] "SNeedUser", [SNeedPass] "SNeedPass", }; static int gettickets(State*, uchar*, char*, int); static int p9skinit(Proto *p, Fsstate *fss) { State *s; int iscli, ret; Key *k; Keyinfo ki; Attr *attr; char *role; role = _strfindattr(fss->attr, "role"); if(role != nil && strcmp(role, "login") == 0) iscli = 0; else if((iscli = isclient(role)) < 0) return failure(fss, nil); s = emalloc(sizeof *s); fss = fss; fss->phasename = phasenames; fss->maxphase = Maxphase; fss->proto = p; if(iscli){ fss->phase = CHaveChal; genrandom((uchar*)s->cchal, CHALLEN); }else{ fss->phase = SNeedChal; s->tr.type = AuthTreq; attr = setattr(_copyattr(fss->attr), "proto=%q", p->name); if(strcmp(role, "login") == 0){ attr = setattr(attr, "role=server"); fss->phase = SNeedUser; } mkkeyinfo(&ki, fss, attr); ki.user = nil; ret = findkey(&k, &ki, "user? dom?"); _freeattr(attr); if(ret != RpcOk){ free(s); return ret; } safecpy(s->tr.authid, _strfindattr(k->attr, "user"), sizeof(s->tr.authid)); safecpy(s->tr.authdom, _strfindattr(k->attr, "dom"), sizeof(s->tr.authdom)); s->key = k; memmove(&s->k, k->priv, sizeof(Authkey)); genrandom((uchar*)s->tr.chal, sizeof s->tr.chal); } s->tbuflen = 0; s->secret = nil; fss->ps = s; return RpcOk; } static int establish(Fsstate *fss) { AuthInfo *ai; State *s; s = fss->ps; ai = &fss->ai; ai->cuid = s->t.cuid; ai->suid = s->t.suid; if(fss->proto == &dp9ik){ static char info[] = "Plan 9 session secret"; ai->nsecret = 256; ai->secret = emalloc(ai->nsecret); hkdf_x( s->rand, 2*NONCELEN, (uchar*)info, sizeof(info)-1, s->t.key, NONCELEN, ai->secret, ai->nsecret, hmac_sha2_256, SHA2_256dlen); } else { ai->nsecret = 8; ai->secret = emalloc(ai->nsecret); des56to64((uchar*)s->t.key, ai->secret); } s->secret = ai->secret; fss->haveai = 1; fss->phase = Established; return RpcOk; } static int p9skread(Fsstate *fss, void *a, uint *n) { int m; State *s; s = fss->ps; switch(fss->phase){ default: return phaseerror(fss, "read"); case CHaveChal: m = CHALLEN; if(*n < m) return toosmall(fss, m); *n = m; memmove(a, s->cchal, m); if(fss->proto == &dp9ik) fss->phase = CNeedPAKreq; else fss->phase = CNeedTreq; return RpcOk; case SHavePAKreq: m = TICKREQLEN + PAKYLEN; if(*n < m) return toosmall(fss, m); s->tr.type = AuthPAK; *n = convTR2M(&s->tr, a, *n); authpak_new(&s->p, &s->k, (uchar*)a + *n, 1); *n += PAKYLEN; fss->phase = SNeedPAKy; return RpcOk; case CHavePAKy: m = PAKYLEN; if(*n < m) return toosmall(fss, m); *n = m; memmove(a, s->y, m); fss->phase = CHaveTicket; return RpcOk; case SHaveTreq: m = TICKREQLEN; if(*n < m) return toosmall(fss, m); s->tr.type = AuthTreq; *n = convTR2M(&s->tr, a, *n); fss->phase = SNeedTicket; return RpcOk; case CHaveTicket: m = s->tbuflen; if(*n < m) return toosmall(fss, m); *n = m; memmove(a, s->tbuf, m); fss->phase = CNeedAuth; return RpcOk; case SHaveAuth: m = s->tbuflen; if(*n < m) return toosmall(fss, m); *n = m; memmove(a, s->tbuf, m); return establish(fss); } } static int p9skwrite(Fsstate *fss, void *a, uint n) { int m, ret, sret; char tbuf[2*PAKYLEN+2*MAXTICKETLEN], *user; Attr *attr; State *s; Key *srvkey; Keyinfo ki; Authenticator auth; s = fss->ps; switch(fss->phase){ default: return phaseerror(fss, "write"); case SNeedChal: m = CHALLEN; if(n < m) return toosmall(fss, m); memmove(s->cchal, a, m); if(fss->proto == &dp9ik) fss->phase = SHavePAKreq; else fss->phase = SHaveTreq; return RpcOk; case CNeedPAKreq: m = TICKREQLEN+PAKYLEN; if(n < m) return toosmall(fss, m); case CNeedTreq: m = TICKREQLEN; if(n < m) return toosmall(fss, m); m = convM2TR(a, n, &s->tr); if(m <= 0 || s->tr.type != (fss->phase==CNeedPAKreq ? AuthPAK : AuthTreq)) return failure(fss, Easproto); if(s->key != nil) closekey(s->key); attr = _delattr(_delattr(_copyattr(fss->attr), "role"), "user"); attr = setattr(attr, "proto=%q", fss->proto->name); user = _strfindattr(fss->attr, "user"); /* * If our client is the user who started factotum (client==owner), then * he can use whatever keys we have to speak as whoever he pleases. * If, on the other hand, we're speaking on behalf of someone else, * we will only vouch for their name on the local system. * * We do the sysuser findkey second so that if we return RpcNeedkey, * the correct key information gets asked for. */ srvkey = nil; s->speakfor = 0; sret = RpcFailure; if(user==nil || strcmp(user, fss->sysuser) == 0){ mkkeyinfo(&ki, fss, attr); ki.user = nil; sret = findkey(&srvkey, &ki, "role=speakfor dom=%q user?", s->tr.authdom); } if(user != nil) attr = setattr(attr, "user=%q", user); mkkeyinfo(&ki, fss, attr); ret = findkey(&s->key, &ki, "role=client dom=%q %s", s->tr.authdom, fss->proto->keyprompt); if(ret == RpcOk) closekey(srvkey); else if(sret == RpcOk){ s->key = srvkey; s->speakfor = 1; }else if(ret == RpcConfirm || sret == RpcConfirm){ _freeattr(attr); return RpcConfirm; }else{ _freeattr(attr); return ret; } /* fill in the rest of the request */ safecpy(s->tr.hostid, _strfindattr(s->key->attr, "user"), sizeof s->tr.hostid); if(s->speakfor) safecpy(s->tr.uid, fss->sysuser, sizeof s->tr.uid); else safecpy(s->tr.uid, s->tr.hostid, sizeof s->tr.uid); /* get tickets from authserver or invent if we can */ memmove(&s->k, s->key->priv, sizeof(Authkey)); if(fss->phase == CNeedPAKreq) ret = gettickets(s, (uchar*)a + m, tbuf, sizeof(tbuf)); else ret = gettickets(s, nil, tbuf, sizeof(tbuf)); if(ret <= 0){ _freeattr(attr); return failure(fss, nil); } m = convM2T(tbuf, ret, &s->t, &s->k); if(m <= 0 || (fss->proto == &dp9ik && s->t.form == 0)){ _freeattr(attr); return failure(fss, Easproto); } if(s->t.num != AuthTc){ if(s->key->successes == 0 && !s->speakfor) disablekey(s->key); if(askforkeys && !s->speakfor){ snprint(fss->keyinfo, sizeof fss->keyinfo, "%A %s", attr, fss->proto->keyprompt); _freeattr(attr); return RpcNeedkey; }else{ _freeattr(attr); return failure(fss, Ebadkey); } } s->key->successes++; _freeattr(attr); ret -= m; memmove(s->tbuf, tbuf+m, ret); genrandom(s->rand, NONCELEN); auth.num = AuthAc; memmove(auth.chal, s->tr.chal, CHALLEN); memmove(auth.rand, s->rand, NONCELEN); ret += convA2M(&auth, s->tbuf+ret, sizeof(s->tbuf)-ret, &s->t); s->tbuflen = ret; if(fss->phase == CNeedPAKreq) fss->phase = CHavePAKy; else fss->phase = CHaveTicket; return RpcOk; case SNeedPAKy: m = PAKYLEN; if(n < m) return toosmall(fss, m); if(authpak_finish(&s->p, &s->k, (uchar*)a)) return failure(fss, Easproto); fss->phase = SNeedTicket; return RpcOk; case SNeedTicket: m = convM2T(a, n, &s->t, &s->k); if(m <= 0 || convM2A((char*)a+m, n-m, &auth, &s->t) <= 0) return toosmall(fss, MAXTICKETLEN + MAXAUTHENTLEN); if(fss->proto == &dp9ik && s->t.form == 0) return failure(fss, Easproto); if(s->t.num != AuthTs || tsmemcmp(s->t.chal, s->tr.chal, CHALLEN) != 0) return failure(fss, Easproto); if(auth.num != AuthAc || tsmemcmp(auth.chal, s->tr.chal, CHALLEN) != 0) return failure(fss, Easproto); memmove(s->rand, auth.rand, NONCELEN); genrandom(s->rand + NONCELEN, NONCELEN); auth.num = AuthAs; memmove(auth.chal, s->cchal, CHALLEN); memmove(auth.rand, s->rand + NONCELEN, NONCELEN); s->tbuflen = convA2M(&auth, s->tbuf, sizeof(s->tbuf), &s->t); fss->phase = SHaveAuth; return RpcOk; case CNeedAuth: m = convM2A(a, n, &auth, &s->t); if(m <= 0) return toosmall(fss, -m); if(auth.num != AuthAs || tsmemcmp(auth.chal, s->cchal, CHALLEN) != 0) return failure(fss, Easproto); memmove(s->rand+NONCELEN, auth.rand, NONCELEN); return establish(fss); case SNeedUser: if(n >= sizeof(s->tr.hostid)) return failure(fss, "user id too long"); strncpy(s->tr.hostid, a, n); s->tr.hostid[n] = 0; strncpy(s->tr.uid, a, n); s->tr.uid[n] = 0; fss->phase = SNeedPass; return RpcOk; case SNeedPass: { Authkey ks; uchar tk[NONCELEN]; char pass[PASSWDLEN]; if(n >= sizeof(pass)) return failure(fss, "password too long"); /* save server key */ memmove(&ks, &s->k, sizeof(ks)); /* derive client key */ strncpy(pass, a, n); pass[n] = 0; passtokey(&s->k, pass); memset(pass, 0, sizeof(pass)); if(fss->proto == &dp9ik){ uchar ys[PAKYLEN]; PAKpriv ps; authpak_hash(&s->k, s->tr.hostid); authpak_new(&ps, &ks, ys, 1); ret = gettickets(s, ys, tbuf, sizeof(tbuf)); if(ret > 0 && authpak_finish(&ps, &ks, s->y)) return failure(fss, Easproto); } else { ret = gettickets(s, nil, tbuf, sizeof(tbuf)); } if(ret <= 0) return failure(fss, nil); /* verify client ticket */ m = convM2T(tbuf, ret, &s->t, &s->k); if(m <= 0 || (fss->proto == &dp9ik && s->t.form == 0)) return failure(fss, Easproto); if(s->t.num != AuthTc || tsmemcmp(s->t.chal, s->tr.chal, CHALLEN) != 0) return failure(fss, Ebadkey); memmove(tk, s->t.key, sizeof(tk)); /* restore sever key */ memmove(&s->k, &ks, sizeof(ks)); /* verify server ticket */ m = convM2T(tbuf+m, ret-m, &s->t, &s->k); if(m <= 0 || (fss->proto == &dp9ik && s->t.form == 0)) return failure(fss, Easproto); if(s->t.num != AuthTs || tsmemcmp(s->t.chal, s->tr.chal, CHALLEN) != 0 || tsmemcmp(tk, s->t.key, sizeof(tk)) != 0) return failure(fss, Ebadkey); } genrandom(s->rand, 2*NONCELEN); return establish(fss); } } static void p9skclose(Fsstate *fss) { State *s; s = fss->ps; if(s->secret != nil){ free(s->secret); s->secret = nil; } if(s->key != nil){ closekey(s->key); s->key = nil; } memset(s, 0, sizeof(State)); free(s); } static int p9skaddkey(Key *k, int before) { Authkey *akey; char *s, *u; u = _strfindattr(k->attr, "user"); if(u == nil){ werrstr("no user attribute"); return -1; } akey = emalloc(sizeof(Authkey)); if(s = _strfindattr(k->privattr, "!hex")){ if(k->proto == &dp9ik){ if(dec16(akey->aes, AESKEYLEN, s, strlen(s)) != AESKEYLEN){ free(akey); werrstr("malformed key data"); return -1; } } else { if(dec16((uchar*)akey->des, DESKEYLEN, s, strlen(s)) != DESKEYLEN){ free(akey); werrstr("malformed key data"); return -1; } } }else if(s = _strfindattr(k->privattr, "!password")){ passtokey(akey, s); }else{ werrstr("no key data"); free(akey); return -1; } if(k->proto == &dp9ik) authpak_hash(akey, u); else memset(akey->aes, 0, AESKEYLEN); /* don't attempt AuthPAK for p9sk1 key */ k->priv = akey; return replacekey(k, before); } static void p9skclosekey(Key *k) { if(k->priv == nil) return; memset(k->priv, 0, sizeof(Authkey)); free(k->priv); } static int mkservertickets(State *s, uchar *y, char *tbuf, int tbuflen) { Ticketreq *tr = &s->tr; Ticket t; int ret; if(strcmp(tr->authid, tr->hostid) != 0) return -1; /* this keeps creating accounts on martha from working. -- presotto if(strcmp(tr->uid, "none") == 0) return -1; */ memset(&t, 0, sizeof(t)); ret = 0; if(y != nil){ t.form = 1; authpak_new(&s->p, &s->k, s->y, 0); authpak_finish(&s->p, &s->k, y); } memmove(t.chal, tr->chal, CHALLEN); strcpy(t.cuid, tr->uid); strcpy(t.suid, tr->uid); genrandom((uchar*)t.key, sizeof(t.key)); t.num = AuthTc; ret += convT2M(&t, tbuf+ret, tbuflen-ret, &s->k); t.num = AuthTs; ret += convT2M(&t, tbuf+ret, tbuflen-ret, &s->k); memset(&t, 0, sizeof(t)); return ret; } static int getastickets(State *s, uchar *y, char *tbuf, int tbuflen) { Ticketreq *tr = &s->tr; int asfd, rv; char *dom; if((dom = _strfindattr(s->key->attr, "dom")) == nil){ werrstr("auth key has no domain"); return -1; } asfd = _authdial(dom); if(asfd < 0) return -1; alarm(30*1000); if(y != nil){ rv = -1; s->tr.type = AuthPAK; if(_asrequest(asfd, tr) != 0 || write(asfd, y, PAKYLEN) != PAKYLEN) goto Out; authpak_new(&s->p, &s->k, (uchar*)tbuf, 1); if(write(asfd, tbuf, PAKYLEN) != PAKYLEN) goto Out; if(_asrdresp(asfd, tbuf, 2*PAKYLEN) != 2*PAKYLEN) goto Out; memmove(s->y, tbuf, PAKYLEN); if(authpak_finish(&s->p, &s->k, (uchar*)tbuf+PAKYLEN)) goto Out; } s->tr.type = AuthTreq; rv = _asgetticket(asfd, tr, tbuf, tbuflen); Out: alarm(0); close(asfd); return rv; } static int gettickets(State *s, uchar *y, char *tbuf, int tbuflen) { int ret; if(s->tr.authdom[0] == 0 || s->tr.authid[0] == 0 || s->tr.hostid[0] == 0 || s->tr.uid[0] == 0){ werrstr("bad ticket request"); return -1; } ret = getastickets(s, y, tbuf, tbuflen); if(ret > 0) return ret; return mkservertickets(s, y, tbuf, tbuflen); } Proto p9sk1 = { .name= "p9sk1", .init= p9skinit, .write= p9skwrite, .read= p9skread, .close= p9skclose, .addkey= p9skaddkey, .closekey= p9skclosekey, .keyprompt= "user? !password?" }; Proto dp9ik = { .name= "dp9ik", .init= p9skinit, .write= p9skwrite, .read= p9skread, .close= p9skclose, .addkey= p9skaddkey, .closekey= p9skclosekey, .keyprompt= "user? !password?" };