/*
* APOP, CRAM - MD5 challenge/response authentication
*
* The client does not authenticate the server, hence no CAI
*
* Client protocol:
* write challenge: randomstring@domain
* read response: 2*MD5dlen hex digits
*
* Server protocol:
* read challenge: randomstring@domain
* write user: user
* write response: 2*MD5dlen hex digits
*/
#include "dat.h"
struct State
{
int asfd;
int astype;
Key *key;
Ticket t;
Ticketreq tr;
char chal[128];
char resp[64];
char *user;
};
enum
{
CNeedChal,
CHaveResp,
SHaveChal,
SNeedUser,
SNeedResp,
Maxphase,
};
static char *phasenames[Maxphase] = {
[CNeedChal] "CNeedChal",
[CHaveResp] "CHaveResp",
[SHaveChal] "SHaveChal",
[SNeedUser] "SNeedUser",
[SNeedResp] "SNeedResp",
};
static int dochal(State*);
static int doreply(State*, char*, char*);
static int
apopinit(Proto *p, Fsstate *fss)
{
int iscli, ret;
State *s;
if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
return failure(fss, nil);
s = emalloc(sizeof *s);
fss->phasename = phasenames;
fss->maxphase = Maxphase;
s->asfd = -1;
if(p == &apop)
s->astype = AuthApop;
else if(p == &cram)
s->astype = AuthCram;
else
abort();
if(iscli)
fss->phase = CNeedChal;
else{
if((ret = findp9authkey(&s->key, fss)) != RpcOk){
free(s);
return ret;
}
if(dochal(s) < 0){
free(s);
return failure(fss, nil);
}
fss->phase = SHaveChal;
}
fss->ps = s;
return RpcOk;
}
static int
apopwrite(Fsstate *fss, void *va, uint n)
{
char *a, *v;
int i, ret;
uchar digest[MD5dlen];
DigestState *ds;
Key *k;
State *s;
Keyinfo ki;
s = fss->ps;
a = va;
switch(fss->phase){
default:
return phaseerror(fss, "write");
case CNeedChal:
ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
if(ret != RpcOk)
return ret;
v = _strfindattr(k->privattr, "!password");
if(v == nil)
return failure(fss, "key has no password");
setattrs(fss->attr, k->attr);
switch(s->astype){
default:
abort();
case AuthCram:
hmac_md5((uchar*)a, n, (uchar*)v, strlen(v),
digest, nil);
snprint(s->resp, sizeof s->resp, "%.*H", MD5dlen, digest);
break;
case AuthApop:
ds = md5((uchar*)a, n, nil, nil);
md5((uchar*)v, strlen(v), digest, ds);
for(i=0; i<MD5dlen; i++)
sprint(&s->resp[2*i], "%2.2x", digest[i]);
break;
}
closekey(k);
fss->phase = CHaveResp;
return RpcOk;
case SNeedUser:
if((v = _strfindattr(fss->attr, "user")) && strcmp(v, a) != 0)
return failure(fss, "bad user");
fss->attr = setattr(fss->attr, "user=%q", a);
s->user = estrdup(a);
fss->phase = SNeedResp;
return RpcOk;
case SNeedResp:
if(n != 2*MD5dlen)
return failure(fss, "response not MD5 digest");
if(doreply(s, s->user, a) < 0){
fss->phase = SNeedUser;
return failure(fss, nil);
}
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
apopread(Fsstate *fss, void *va, uint *n)
{
State *s;
s = fss->ps;
switch(fss->phase){
default:
return phaseerror(fss, "read");
case CHaveResp:
if(*n > strlen(s->resp))
*n = strlen(s->resp);
memmove(va, s->resp, *n);
fss->phase = Established;
fss->haveai = 0;
return RpcOk;
case SHaveChal:
if(*n > strlen(s->chal))
*n = strlen(s->chal);
memmove(va, s->chal, *n);
fss->phase = SNeedUser;
return RpcOk;
}
}
static void
apopclose(Fsstate *fss)
{
State *s;
s = fss->ps;
if(s->asfd >= 0){
close(s->asfd);
s->asfd = -1;
}
if(s->key != nil){
closekey(s->key);
s->key = nil;
}
if(s->user != nil){
free(s->user);
s->user = nil;
}
free(s);
}
static int
dochal(State *s)
{
char *dom, *user, trbuf[TICKREQLEN];
s->asfd = -1;
/* send request to authentication server and get challenge */
/* send request to authentication server and get challenge */
if((dom = _strfindattr(s->key->attr, "dom")) == nil
|| (user = _strfindattr(s->key->attr, "user")) == nil){
werrstr("apop/dochal cannot happen");
goto err;
}
s->asfd = _authdial(nil, dom);
/* could generate our own challenge on error here */
if(s->asfd < 0)
goto err;
memset(&s->tr, 0, sizeof(s->tr));
s->tr.type = s->astype;
safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
convTR2M(&s->tr, trbuf);
if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
goto err;
if(_asrdresp(s->asfd, s->chal, sizeof s->chal) <= 5)
goto err;
return 0;
err:
if(s->asfd >= 0)
close(s->asfd);
s->asfd = -1;
return -1;
}
static int
doreply(State *s, char *user, char *response)
{
char ticket[TICKETLEN+AUTHENTLEN];
char trbuf[TICKREQLEN];
int n;
Authenticator a;
memrandom(s->tr.chal, CHALLEN);
safecpy(s->tr.uid, user, sizeof(s->tr.uid));
convTR2M(&s->tr, trbuf);
if((n=write(s->asfd, trbuf, TICKREQLEN)) != TICKREQLEN){
if(n >= 0)
werrstr("short write to auth server");
goto err;
}
/* send response to auth server */
if(strlen(response) != MD5dlen*2){
werrstr("response not MD5 digest");
goto err;
}
if((n=write(s->asfd, response, MD5dlen*2)) != MD5dlen*2){
if(n >= 0)
werrstr("short write to auth server");
goto err;
}
if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
/* leave connection open so we can try again */
return -1;
}
close(s->asfd);
s->asfd = -1;
convM2T(ticket, &s->t, (char*)s->key->priv);
if(s->t.num != AuthTs
|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
if(s->key->successes == 0)
disablekey(s->key);
werrstr(Easproto);
goto err;
}
s->key->successes++;
convM2A(ticket+TICKETLEN, &a, s->t.key);
if(a.num != AuthAc
|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
|| a.id != 0){
werrstr(Easproto);
goto err;
}
return 0;
err:
if(s->asfd >= 0)
close(s->asfd);
s->asfd = -1;
return -1;
}
Proto apop = {
.name= "apop",
.init= apopinit,
.write= apopwrite,
.read= apopread,
.close= apopclose,
.addkey= replacekey,
.keyprompt= "!password?"
};
Proto cram = {
.name= "cram",
.init= apopinit,
.write= apopwrite,
.read= apopread,
.close= apopclose,
.addkey= replacekey,
.keyprompt= "!password?"
};
|