/* * HTTPDIGEST - MD5 challenge/response authentication (RFC 2617) * * Client protocol: * write challenge: nonce method uri * read response: 2*MD5dlen hex digits * * Server protocol: * unimplemented */ #include "dat.h" enum { CNeedChal, CHaveResp, Maxphase, }; static char *phasenames[Maxphase] = { [CNeedChal] "CNeedChal", [CHaveResp] "CHaveResp", }; struct State { char resp[MD5dlen*2+1]; }; static int hdinit(Proto *p, Fsstate *fss) { int iscli; State *s; if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0) return failure(fss, nil); if(!iscli) return failure(fss, "%s server not supported", p->name); s = emalloc(sizeof *s); fss->phasename = phasenames; fss->maxphase = Maxphase; fss->phase = CNeedChal; fss->ps = s; return RpcOk; } static void strtolower(char *s) { while(*s){ *s = tolower(*s); s++; } } static void digest(char *user, char *realm, char *passwd, char *nonce, char *method, char *uri, char *dig) { uchar b[MD5dlen]; char ha1[MD5dlen*2+1]; char ha2[MD5dlen*2+1]; DigestState *s; /* * H(A1) = MD5(uid + ":" + realm ":" + passwd) */ s = md5((uchar*)user, strlen(user), nil, nil); md5((uchar*)":", 1, nil, s); md5((uchar*)realm, strlen(realm), nil, s); md5((uchar*)":", 1, nil, s); md5((uchar*)passwd, strlen(passwd), b, s); enc16(ha1, sizeof(ha1), b, MD5dlen); strtolower(ha1); /* * H(A2) = MD5(method + ":" + uri) */ s = md5((uchar*)method, strlen(method), nil, nil); md5((uchar*)":", 1, nil, s); md5((uchar*)uri, strlen(uri), b, s); enc16(ha2, sizeof(ha2), b, MD5dlen); strtolower(ha2); /* * digest = MD5(H(A1) + ":" + nonce + ":" + H(A2)) */ s = md5((uchar*)ha1, MD5dlen*2, nil, nil); md5((uchar*)":", 1, nil, s); md5((uchar*)nonce, strlen(nonce), nil, s); md5((uchar*)":", 1, nil, s); md5((uchar*)ha2, MD5dlen*2, b, s); enc16(dig, MD5dlen*2+1, b, MD5dlen); strtolower(dig); } static int hdwrite(Fsstate *fss, void *va, uint n) { State *s; int ret; char *a, *p, *r, *u, *t; char *tok[4]; Key *k; Keyinfo ki; Attr *attr; s = fss->ps; a = va; if(fss->phase != CNeedChal) return phaseerror(fss, "write"); attr = _delattr(_copyattr(fss->attr), "role"); mkkeyinfo(&ki, fss, attr); ret = findkey(&k, &ki, "%s", fss->proto->keyprompt); _freeattr(attr); if(ret != RpcOk) return ret; p = _strfindattr(k->privattr, "!password"); if(p == nil) return failure(fss, "key has no password"); r = _strfindattr(k->attr, "realm"); if(r == nil) return failure(fss, "key has no realm"); u = _strfindattr(k->attr, "user"); if(u == nil) return failure(fss, "key has no user"); setattrs(fss->attr, k->attr); /* copy in case a is not null-terminated */ t = emalloc(n+1); memcpy(t, a, n); t[n] = 0; /* get nonce, method, uri */ if(tokenize(t, tok, 4) != 3) return failure(fss, "bad challenge"); digest(u, r, p, tok[0], tok[1], tok[2], s->resp); free(t); closekey(k); fss->phase = CHaveResp; return RpcOk; } static int hdread(Fsstate *fss, void *va, uint *n) { State *s; s = fss->ps; if(fss->phase != CHaveResp) return phaseerror(fss, "read"); if(*n > strlen(s->resp)) *n = strlen(s->resp); memmove(va, s->resp, *n); fss->phase = Established; fss->haveai = 0; return RpcOk; } static void hdclose(Fsstate *fss) { State *s; s = fss->ps; free(s); } Proto httpdigest = { .name= "httpdigest", .init= hdinit, .write= hdwrite, .read= hdread, .close= hdclose, .addkey= replacekey, .keyprompt= "user? realm? !password?" };