/*
* 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?"
};
|