xref: /plan9-contrib/sys/src/cmd/auth/factotum/apop.c (revision f54edc786b9c49b2c7ab1c0695cdc8c698b11f4d)
19a747e4fSDavid du Colombier /*
29a747e4fSDavid du Colombier  * APOP, CRAM - MD5 challenge/response authentication
39a747e4fSDavid du Colombier  *
49a747e4fSDavid du Colombier  * The client does not authenticate the server, hence no CAI
59a747e4fSDavid du Colombier  *
69a747e4fSDavid du Colombier  * Client protocol:
79a747e4fSDavid du Colombier  *	write challenge: randomstring@domain
89a747e4fSDavid du Colombier  *	read response: 2*MD5dlen hex digits
99a747e4fSDavid du Colombier  *
109a747e4fSDavid du Colombier  * Server protocol:
119a747e4fSDavid du Colombier  *	read challenge: randomstring@domain
129a747e4fSDavid du Colombier  *	write user: user
139a747e4fSDavid du Colombier  *	write response: 2*MD5dlen hex digits
149a747e4fSDavid du Colombier  */
159a747e4fSDavid du Colombier 
169a747e4fSDavid du Colombier #include "dat.h"
179a747e4fSDavid du Colombier 
189a747e4fSDavid du Colombier struct State
199a747e4fSDavid du Colombier {
209a747e4fSDavid du Colombier 	int asfd;
219a747e4fSDavid du Colombier 	int astype;
229a747e4fSDavid du Colombier 	Key *key;
239a747e4fSDavid du Colombier 	Ticket	t;
249a747e4fSDavid du Colombier 	Ticketreq	tr;
259a747e4fSDavid du Colombier 	char chal[128];
269a747e4fSDavid du Colombier 	char	resp[64];
279a747e4fSDavid du Colombier 	char *user;
289a747e4fSDavid du Colombier };
299a747e4fSDavid du Colombier 
309a747e4fSDavid du Colombier enum
319a747e4fSDavid du Colombier {
329a747e4fSDavid du Colombier 	CNeedChal,
339a747e4fSDavid du Colombier 	CHaveResp,
349a747e4fSDavid du Colombier 
359a747e4fSDavid du Colombier 	SHaveChal,
369a747e4fSDavid du Colombier 	SNeedUser,
379a747e4fSDavid du Colombier 	SNeedResp,
389a747e4fSDavid du Colombier 
399a747e4fSDavid du Colombier 	Maxphase,
409a747e4fSDavid du Colombier };
419a747e4fSDavid du Colombier 
429a747e4fSDavid du Colombier static char *phasenames[Maxphase] = {
439a747e4fSDavid du Colombier [CNeedChal]	"CNeedChal",
449a747e4fSDavid du Colombier [CHaveResp]	"CHaveResp",
459a747e4fSDavid du Colombier 
469a747e4fSDavid du Colombier [SHaveChal]	"SHaveChal",
479a747e4fSDavid du Colombier [SNeedUser]	"SNeedUser",
489a747e4fSDavid du Colombier [SNeedResp]	"SNeedResp",
499a747e4fSDavid du Colombier };
509a747e4fSDavid du Colombier 
519a747e4fSDavid du Colombier static int dochal(State*);
529a747e4fSDavid du Colombier static int doreply(State*, char*, char*);
539a747e4fSDavid du Colombier 
549a747e4fSDavid du Colombier static int
apopinit(Proto * p,Fsstate * fss)559a747e4fSDavid du Colombier apopinit(Proto *p, Fsstate *fss)
569a747e4fSDavid du Colombier {
579a747e4fSDavid du Colombier 	int iscli, ret;
589a747e4fSDavid du Colombier 	State *s;
599a747e4fSDavid du Colombier 
602ebbfa15SDavid du Colombier 	if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
619a747e4fSDavid du Colombier 		return failure(fss, nil);
629a747e4fSDavid du Colombier 
639a747e4fSDavid du Colombier 	s = emalloc(sizeof *s);
649a747e4fSDavid du Colombier 	fss->phasename = phasenames;
659a747e4fSDavid du Colombier 	fss->maxphase = Maxphase;
669a747e4fSDavid du Colombier 	s->asfd = -1;
679a747e4fSDavid du Colombier 	if(p == &apop)
689a747e4fSDavid du Colombier 		s->astype = AuthApop;
699a747e4fSDavid du Colombier 	else if(p == &cram)
709a747e4fSDavid du Colombier 		s->astype = AuthCram;
719a747e4fSDavid du Colombier 	else
729a747e4fSDavid du Colombier 		abort();
739a747e4fSDavid du Colombier 
749a747e4fSDavid du Colombier 	if(iscli)
759a747e4fSDavid du Colombier 		fss->phase = CNeedChal;
769a747e4fSDavid du Colombier 	else{
779a747e4fSDavid du Colombier 		if((ret = findp9authkey(&s->key, fss)) != RpcOk){
789a747e4fSDavid du Colombier 			free(s);
799a747e4fSDavid du Colombier 			return ret;
809a747e4fSDavid du Colombier 		}
819a747e4fSDavid du Colombier 		if(dochal(s) < 0){
829a747e4fSDavid du Colombier 			free(s);
839a747e4fSDavid du Colombier 			return failure(fss, nil);
849a747e4fSDavid du Colombier 		}
859a747e4fSDavid du Colombier 		fss->phase = SHaveChal;
869a747e4fSDavid du Colombier 	}
879a747e4fSDavid du Colombier 	fss->ps = s;
889a747e4fSDavid du Colombier 	return RpcOk;
899a747e4fSDavid du Colombier }
909a747e4fSDavid du Colombier 
919a747e4fSDavid du Colombier static int
apopwrite(Fsstate * fss,void * va,uint n)929a747e4fSDavid du Colombier apopwrite(Fsstate *fss, void *va, uint n)
939a747e4fSDavid du Colombier {
949a747e4fSDavid du Colombier 	char *a, *v;
9559c21d95SDavid du Colombier 	int i, ret;
969a747e4fSDavid du Colombier 	uchar digest[MD5dlen];
979a747e4fSDavid du Colombier 	DigestState *ds;
989a747e4fSDavid du Colombier 	Key *k;
999a747e4fSDavid du Colombier 	State *s;
100260f7b65SDavid du Colombier 	Keyinfo ki;
1019a747e4fSDavid du Colombier 
1029a747e4fSDavid du Colombier 	s = fss->ps;
1039a747e4fSDavid du Colombier 	a = va;
1049a747e4fSDavid du Colombier 	switch(fss->phase){
1059a747e4fSDavid du Colombier 	default:
1069a747e4fSDavid du Colombier 		return phaseerror(fss, "write");
1079a747e4fSDavid du Colombier 
1089a747e4fSDavid du Colombier 	case CNeedChal:
109260f7b65SDavid du Colombier 		ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
1109a747e4fSDavid du Colombier 		if(ret != RpcOk)
1119a747e4fSDavid du Colombier 			return ret;
1122ebbfa15SDavid du Colombier 		v = _strfindattr(k->privattr, "!password");
1139a747e4fSDavid du Colombier 		if(v == nil)
1149a747e4fSDavid du Colombier 			return failure(fss, "key has no password");
1159a747e4fSDavid du Colombier 		setattrs(fss->attr, k->attr);
1169a747e4fSDavid du Colombier 		switch(s->astype){
1179a747e4fSDavid du Colombier 		default:
1189a747e4fSDavid du Colombier 			abort();
1199a747e4fSDavid du Colombier 		case AuthCram:
1209a747e4fSDavid du Colombier 			hmac_md5((uchar*)a, n, (uchar*)v, strlen(v),
1219a747e4fSDavid du Colombier 				digest, nil);
122*f54edc78SDavid du Colombier 			snprint(s->resp, sizeof s->resp, "%.*H", MD5dlen, digest);
1239a747e4fSDavid du Colombier 			break;
1249a747e4fSDavid du Colombier 		case AuthApop:
1259a747e4fSDavid du Colombier 			ds = md5((uchar*)a, n, nil, nil);
1269a747e4fSDavid du Colombier 			md5((uchar*)v, strlen(v), digest, ds);
12759c21d95SDavid du Colombier 			for(i=0; i<MD5dlen; i++)
12859c21d95SDavid du Colombier 				sprint(&s->resp[2*i], "%2.2x", digest[i]);
1299a747e4fSDavid du Colombier 			break;
1309a747e4fSDavid du Colombier 		}
1319a747e4fSDavid du Colombier 		closekey(k);
1329a747e4fSDavid du Colombier 		fss->phase = CHaveResp;
1339a747e4fSDavid du Colombier 		return RpcOk;
1349a747e4fSDavid du Colombier 
1359a747e4fSDavid du Colombier 	case SNeedUser:
1362ebbfa15SDavid du Colombier 		if((v = _strfindattr(fss->attr, "user")) && strcmp(v, a) != 0)
1379a747e4fSDavid du Colombier 			return failure(fss, "bad user");
1389a747e4fSDavid du Colombier 		fss->attr = setattr(fss->attr, "user=%q", a);
1399a747e4fSDavid du Colombier 		s->user = estrdup(a);
1409a747e4fSDavid du Colombier 		fss->phase = SNeedResp;
1419a747e4fSDavid du Colombier 		return RpcOk;
1429a747e4fSDavid du Colombier 
1439a747e4fSDavid du Colombier 	case SNeedResp:
1449a747e4fSDavid du Colombier 		if(n != 2*MD5dlen)
1459a747e4fSDavid du Colombier 			return failure(fss, "response not MD5 digest");
1469a747e4fSDavid du Colombier 		if(doreply(s, s->user, a) < 0){
1479a747e4fSDavid du Colombier 			fss->phase = SNeedUser;
1489a747e4fSDavid du Colombier 			return failure(fss, nil);
1499a747e4fSDavid du Colombier 		}
1509a747e4fSDavid du Colombier 		fss->haveai = 1;
1519a747e4fSDavid du Colombier 		fss->ai.cuid = s->t.cuid;
1529a747e4fSDavid du Colombier 		fss->ai.suid = s->t.suid;
1539a747e4fSDavid du Colombier 		fss->ai.nsecret = 0;
1549a747e4fSDavid du Colombier 		fss->ai.secret = nil;
1559a747e4fSDavid du Colombier 		fss->phase = Established;
1569a747e4fSDavid du Colombier 		return RpcOk;
1579a747e4fSDavid du Colombier 	}
1589a747e4fSDavid du Colombier }
1599a747e4fSDavid du Colombier 
1609a747e4fSDavid du Colombier static int
apopread(Fsstate * fss,void * va,uint * n)1619a747e4fSDavid du Colombier apopread(Fsstate *fss, void *va, uint *n)
1629a747e4fSDavid du Colombier {
1639a747e4fSDavid du Colombier 	State *s;
1649a747e4fSDavid du Colombier 
1659a747e4fSDavid du Colombier 	s = fss->ps;
1669a747e4fSDavid du Colombier 	switch(fss->phase){
1679a747e4fSDavid du Colombier 	default:
1689a747e4fSDavid du Colombier 		return phaseerror(fss, "read");
1699a747e4fSDavid du Colombier 
1709a747e4fSDavid du Colombier 	case CHaveResp:
1719a747e4fSDavid du Colombier 		if(*n > strlen(s->resp))
1729a747e4fSDavid du Colombier 			*n = strlen(s->resp);
1739a747e4fSDavid du Colombier 		memmove(va, s->resp, *n);
1749a747e4fSDavid du Colombier 		fss->phase = Established;
1759a747e4fSDavid du Colombier 		fss->haveai = 0;
1769a747e4fSDavid du Colombier 		return RpcOk;
1779a747e4fSDavid du Colombier 
1789a747e4fSDavid du Colombier 	case SHaveChal:
1799a747e4fSDavid du Colombier 		if(*n > strlen(s->chal))
1809a747e4fSDavid du Colombier 			*n = strlen(s->chal);
1819a747e4fSDavid du Colombier 		memmove(va, s->chal, *n);
1829a747e4fSDavid du Colombier 		fss->phase = SNeedUser;
1839a747e4fSDavid du Colombier 		return RpcOk;
1849a747e4fSDavid du Colombier 	}
1859a747e4fSDavid du Colombier }
1869a747e4fSDavid du Colombier 
1879a747e4fSDavid du Colombier static void
apopclose(Fsstate * fss)1889a747e4fSDavid du Colombier apopclose(Fsstate *fss)
1899a747e4fSDavid du Colombier {
1909a747e4fSDavid du Colombier 	State *s;
1919a747e4fSDavid du Colombier 
1929a747e4fSDavid du Colombier 	s = fss->ps;
1939a747e4fSDavid du Colombier 	if(s->asfd >= 0){
1949a747e4fSDavid du Colombier 		close(s->asfd);
1959a747e4fSDavid du Colombier 		s->asfd = -1;
1969a747e4fSDavid du Colombier 	}
1979a747e4fSDavid du Colombier 	if(s->key != nil){
1989a747e4fSDavid du Colombier 		closekey(s->key);
1999a747e4fSDavid du Colombier 		s->key = nil;
2009a747e4fSDavid du Colombier 	}
2019a747e4fSDavid du Colombier 	if(s->user != nil){
2029a747e4fSDavid du Colombier 		free(s->user);
2039a747e4fSDavid du Colombier 		s->user = nil;
2049a747e4fSDavid du Colombier 	}
2059a747e4fSDavid du Colombier 	free(s);
2069a747e4fSDavid du Colombier }
2079a747e4fSDavid du Colombier 
2089a747e4fSDavid du Colombier static int
dochal(State * s)2099a747e4fSDavid du Colombier dochal(State *s)
2109a747e4fSDavid du Colombier {
2119a747e4fSDavid du Colombier 	char *dom, *user, trbuf[TICKREQLEN];
2129a747e4fSDavid du Colombier 
2139a747e4fSDavid du Colombier 	s->asfd = -1;
2149a747e4fSDavid du Colombier 
2159a747e4fSDavid du Colombier 	/* send request to authentication server and get challenge */
2169a747e4fSDavid du Colombier 	/* send request to authentication server and get challenge */
2172ebbfa15SDavid du Colombier 	if((dom = _strfindattr(s->key->attr, "dom")) == nil
2182ebbfa15SDavid du Colombier 	|| (user = _strfindattr(s->key->attr, "user")) == nil){
2199a747e4fSDavid du Colombier 		werrstr("apop/dochal cannot happen");
2209a747e4fSDavid du Colombier 		goto err;
2219a747e4fSDavid du Colombier 	}
2229a747e4fSDavid du Colombier 
2239a747e4fSDavid du Colombier 	s->asfd = _authdial(nil, dom);
2249a747e4fSDavid du Colombier 
2259a747e4fSDavid du Colombier 	/* could generate our own challenge on error here */
2269a747e4fSDavid du Colombier 	if(s->asfd < 0)
2279a747e4fSDavid du Colombier 		goto err;
2289a747e4fSDavid du Colombier 
2299a747e4fSDavid du Colombier 	memset(&s->tr, 0, sizeof(s->tr));
2309a747e4fSDavid du Colombier 	s->tr.type = s->astype;
2319a747e4fSDavid du Colombier 	safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
2329a747e4fSDavid du Colombier 	safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
2339a747e4fSDavid du Colombier 	convTR2M(&s->tr, trbuf);
2349a747e4fSDavid du Colombier 
2359a747e4fSDavid du Colombier 	if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
2369a747e4fSDavid du Colombier 		goto err;
2379a747e4fSDavid du Colombier 	if(_asrdresp(s->asfd, s->chal, sizeof s->chal) <= 5)
2389a747e4fSDavid du Colombier 		goto err;
2399a747e4fSDavid du Colombier 	return 0;
2409a747e4fSDavid du Colombier 
2419a747e4fSDavid du Colombier err:
2429a747e4fSDavid du Colombier 	if(s->asfd >= 0)
2439a747e4fSDavid du Colombier 		close(s->asfd);
2449a747e4fSDavid du Colombier 	s->asfd = -1;
2459a747e4fSDavid du Colombier 	return -1;
2469a747e4fSDavid du Colombier }
2479a747e4fSDavid du Colombier 
2489a747e4fSDavid du Colombier static int
doreply(State * s,char * user,char * response)2499a747e4fSDavid du Colombier doreply(State *s, char *user, char *response)
2509a747e4fSDavid du Colombier {
2519a747e4fSDavid du Colombier 	char ticket[TICKETLEN+AUTHENTLEN];
2529a747e4fSDavid du Colombier 	char trbuf[TICKREQLEN];
2539a747e4fSDavid du Colombier 	int n;
2549a747e4fSDavid du Colombier 	Authenticator a;
2559a747e4fSDavid du Colombier 
2569a747e4fSDavid du Colombier 	memrandom(s->tr.chal, CHALLEN);
2579a747e4fSDavid du Colombier 	safecpy(s->tr.uid, user, sizeof(s->tr.uid));
2589a747e4fSDavid du Colombier 	convTR2M(&s->tr, trbuf);
2599a747e4fSDavid du Colombier 	if((n=write(s->asfd, trbuf, TICKREQLEN)) != TICKREQLEN){
2609a747e4fSDavid du Colombier 		if(n >= 0)
2619a747e4fSDavid du Colombier 			werrstr("short write to auth server");
2629a747e4fSDavid du Colombier 		goto err;
2639a747e4fSDavid du Colombier 	}
2649a747e4fSDavid du Colombier 	/* send response to auth server */
2659a747e4fSDavid du Colombier 	if(strlen(response) != MD5dlen*2){
2669a747e4fSDavid du Colombier 		werrstr("response not MD5 digest");
2679a747e4fSDavid du Colombier 		goto err;
2689a747e4fSDavid du Colombier 	}
2699a747e4fSDavid du Colombier 	if((n=write(s->asfd, response, MD5dlen*2)) != MD5dlen*2){
2709a747e4fSDavid du Colombier 		if(n >= 0)
2719a747e4fSDavid du Colombier 			werrstr("short write to auth server");
2729a747e4fSDavid du Colombier 		goto err;
2739a747e4fSDavid du Colombier 	}
2749a747e4fSDavid du Colombier 	if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
2759a747e4fSDavid du Colombier 		/* leave connection open so we can try again */
2769a747e4fSDavid du Colombier 		return -1;
2779a747e4fSDavid du Colombier 	}
2789a747e4fSDavid du Colombier 	close(s->asfd);
2799a747e4fSDavid du Colombier 	s->asfd = -1;
2809a747e4fSDavid du Colombier 
2819a747e4fSDavid du Colombier 	convM2T(ticket, &s->t, (char*)s->key->priv);
2829a747e4fSDavid du Colombier 	if(s->t.num != AuthTs
2839a747e4fSDavid du Colombier 	|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
2846822557bSDavid du Colombier 		if(s->key->successes == 0)
285260f7b65SDavid du Colombier 			disablekey(s->key);
2869a747e4fSDavid du Colombier 		werrstr(Easproto);
2879a747e4fSDavid du Colombier 		goto err;
2889a747e4fSDavid du Colombier 	}
2896822557bSDavid du Colombier 	s->key->successes++;
2909a747e4fSDavid du Colombier 	convM2A(ticket+TICKETLEN, &a, s->t.key);
2919a747e4fSDavid du Colombier 	if(a.num != AuthAc
2929a747e4fSDavid du Colombier 	|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
2939a747e4fSDavid du Colombier 	|| a.id != 0){
2949a747e4fSDavid du Colombier 		werrstr(Easproto);
2959a747e4fSDavid du Colombier 		goto err;
2969a747e4fSDavid du Colombier 	}
2979a747e4fSDavid du Colombier 
2989a747e4fSDavid du Colombier 	return 0;
2999a747e4fSDavid du Colombier err:
3009a747e4fSDavid du Colombier 	if(s->asfd >= 0)
3019a747e4fSDavid du Colombier 		close(s->asfd);
3029a747e4fSDavid du Colombier 	s->asfd = -1;
3039a747e4fSDavid du Colombier 	return -1;
3049a747e4fSDavid du Colombier }
3059a747e4fSDavid du Colombier 
3069a747e4fSDavid du Colombier Proto apop = {
3079a747e4fSDavid du Colombier .name=	"apop",
3089a747e4fSDavid du Colombier .init=		apopinit,
3099a747e4fSDavid du Colombier .write=	apopwrite,
3109a747e4fSDavid du Colombier .read=	apopread,
3119a747e4fSDavid du Colombier .close=	apopclose,
3129a747e4fSDavid du Colombier .addkey=	replacekey,
3139a747e4fSDavid du Colombier .keyprompt=	"!password?"
3149a747e4fSDavid du Colombier };
3159a747e4fSDavid du Colombier 
3169a747e4fSDavid du Colombier Proto cram = {
3179a747e4fSDavid du Colombier .name=	"cram",
3189a747e4fSDavid du Colombier .init=		apopinit,
3199a747e4fSDavid du Colombier .write=	apopwrite,
3209a747e4fSDavid du Colombier .read=	apopread,
3219a747e4fSDavid du Colombier .close=	apopclose,
3229a747e4fSDavid du Colombier .addkey=	replacekey,
3239a747e4fSDavid du Colombier .keyprompt=	"!password?"
3249a747e4fSDavid du Colombier };
325