xref: /plan9/sys/src/cmd/auth/factotum/apop.c (revision f54edc786b9c49b2c7ab1c0695cdc8c698b11f4d)
1 /*
2  * APOP, CRAM - MD5 challenge/response authentication
3  *
4  * The client does not authenticate the server, hence no CAI
5  *
6  * Client protocol:
7  *	write challenge: randomstring@domain
8  *	read response: 2*MD5dlen hex digits
9  *
10  * Server protocol:
11  *	read challenge: randomstring@domain
12  *	write user: user
13  *	write response: 2*MD5dlen hex digits
14  */
15 
16 #include "dat.h"
17 
18 struct State
19 {
20 	int asfd;
21 	int astype;
22 	Key *key;
23 	Ticket	t;
24 	Ticketreq	tr;
25 	char chal[128];
26 	char	resp[64];
27 	char *user;
28 };
29 
30 enum
31 {
32 	CNeedChal,
33 	CHaveResp,
34 
35 	SHaveChal,
36 	SNeedUser,
37 	SNeedResp,
38 
39 	Maxphase,
40 };
41 
42 static char *phasenames[Maxphase] = {
43 [CNeedChal]	"CNeedChal",
44 [CHaveResp]	"CHaveResp",
45 
46 [SHaveChal]	"SHaveChal",
47 [SNeedUser]	"SNeedUser",
48 [SNeedResp]	"SNeedResp",
49 };
50 
51 static int dochal(State*);
52 static int doreply(State*, char*, char*);
53 
54 static int
apopinit(Proto * p,Fsstate * fss)55 apopinit(Proto *p, Fsstate *fss)
56 {
57 	int iscli, ret;
58 	State *s;
59 
60 	if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
61 		return failure(fss, nil);
62 
63 	s = emalloc(sizeof *s);
64 	fss->phasename = phasenames;
65 	fss->maxphase = Maxphase;
66 	s->asfd = -1;
67 	if(p == &apop)
68 		s->astype = AuthApop;
69 	else if(p == &cram)
70 		s->astype = AuthCram;
71 	else
72 		abort();
73 
74 	if(iscli)
75 		fss->phase = CNeedChal;
76 	else{
77 		if((ret = findp9authkey(&s->key, fss)) != RpcOk){
78 			free(s);
79 			return ret;
80 		}
81 		if(dochal(s) < 0){
82 			free(s);
83 			return failure(fss, nil);
84 		}
85 		fss->phase = SHaveChal;
86 	}
87 	fss->ps = s;
88 	return RpcOk;
89 }
90 
91 static int
apopwrite(Fsstate * fss,void * va,uint n)92 apopwrite(Fsstate *fss, void *va, uint n)
93 {
94 	char *a, *v;
95 	int i, ret;
96 	uchar digest[MD5dlen];
97 	DigestState *ds;
98 	Key *k;
99 	State *s;
100 	Keyinfo ki;
101 
102 	s = fss->ps;
103 	a = va;
104 	switch(fss->phase){
105 	default:
106 		return phaseerror(fss, "write");
107 
108 	case CNeedChal:
109 		ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
110 		if(ret != RpcOk)
111 			return ret;
112 		v = _strfindattr(k->privattr, "!password");
113 		if(v == nil)
114 			return failure(fss, "key has no password");
115 		setattrs(fss->attr, k->attr);
116 		switch(s->astype){
117 		default:
118 			abort();
119 		case AuthCram:
120 			hmac_md5((uchar*)a, n, (uchar*)v, strlen(v),
121 				digest, nil);
122 			snprint(s->resp, sizeof s->resp, "%.*H", MD5dlen, digest);
123 			break;
124 		case AuthApop:
125 			ds = md5((uchar*)a, n, nil, nil);
126 			md5((uchar*)v, strlen(v), digest, ds);
127 			for(i=0; i<MD5dlen; i++)
128 				sprint(&s->resp[2*i], "%2.2x", digest[i]);
129 			break;
130 		}
131 		closekey(k);
132 		fss->phase = CHaveResp;
133 		return RpcOk;
134 
135 	case SNeedUser:
136 		if((v = _strfindattr(fss->attr, "user")) && strcmp(v, a) != 0)
137 			return failure(fss, "bad user");
138 		fss->attr = setattr(fss->attr, "user=%q", a);
139 		s->user = estrdup(a);
140 		fss->phase = SNeedResp;
141 		return RpcOk;
142 
143 	case SNeedResp:
144 		if(n != 2*MD5dlen)
145 			return failure(fss, "response not MD5 digest");
146 		if(doreply(s, s->user, a) < 0){
147 			fss->phase = SNeedUser;
148 			return failure(fss, nil);
149 		}
150 		fss->haveai = 1;
151 		fss->ai.cuid = s->t.cuid;
152 		fss->ai.suid = s->t.suid;
153 		fss->ai.nsecret = 0;
154 		fss->ai.secret = nil;
155 		fss->phase = Established;
156 		return RpcOk;
157 	}
158 }
159 
160 static int
apopread(Fsstate * fss,void * va,uint * n)161 apopread(Fsstate *fss, void *va, uint *n)
162 {
163 	State *s;
164 
165 	s = fss->ps;
166 	switch(fss->phase){
167 	default:
168 		return phaseerror(fss, "read");
169 
170 	case CHaveResp:
171 		if(*n > strlen(s->resp))
172 			*n = strlen(s->resp);
173 		memmove(va, s->resp, *n);
174 		fss->phase = Established;
175 		fss->haveai = 0;
176 		return RpcOk;
177 
178 	case SHaveChal:
179 		if(*n > strlen(s->chal))
180 			*n = strlen(s->chal);
181 		memmove(va, s->chal, *n);
182 		fss->phase = SNeedUser;
183 		return RpcOk;
184 	}
185 }
186 
187 static void
apopclose(Fsstate * fss)188 apopclose(Fsstate *fss)
189 {
190 	State *s;
191 
192 	s = fss->ps;
193 	if(s->asfd >= 0){
194 		close(s->asfd);
195 		s->asfd = -1;
196 	}
197 	if(s->key != nil){
198 		closekey(s->key);
199 		s->key = nil;
200 	}
201 	if(s->user != nil){
202 		free(s->user);
203 		s->user = nil;
204 	}
205 	free(s);
206 }
207 
208 static int
dochal(State * s)209 dochal(State *s)
210 {
211 	char *dom, *user, trbuf[TICKREQLEN];
212 
213 	s->asfd = -1;
214 
215 	/* send request to authentication server and get challenge */
216 	/* send request to authentication server and get challenge */
217 	if((dom = _strfindattr(s->key->attr, "dom")) == nil
218 	|| (user = _strfindattr(s->key->attr, "user")) == nil){
219 		werrstr("apop/dochal cannot happen");
220 		goto err;
221 	}
222 
223 	s->asfd = _authdial(nil, dom);
224 
225 	/* could generate our own challenge on error here */
226 	if(s->asfd < 0)
227 		goto err;
228 
229 	memset(&s->tr, 0, sizeof(s->tr));
230 	s->tr.type = s->astype;
231 	safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
232 	safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
233 	convTR2M(&s->tr, trbuf);
234 
235 	if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
236 		goto err;
237 	if(_asrdresp(s->asfd, s->chal, sizeof s->chal) <= 5)
238 		goto err;
239 	return 0;
240 
241 err:
242 	if(s->asfd >= 0)
243 		close(s->asfd);
244 	s->asfd = -1;
245 	return -1;
246 }
247 
248 static int
doreply(State * s,char * user,char * response)249 doreply(State *s, char *user, char *response)
250 {
251 	char ticket[TICKETLEN+AUTHENTLEN];
252 	char trbuf[TICKREQLEN];
253 	int n;
254 	Authenticator a;
255 
256 	memrandom(s->tr.chal, CHALLEN);
257 	safecpy(s->tr.uid, user, sizeof(s->tr.uid));
258 	convTR2M(&s->tr, trbuf);
259 	if((n=write(s->asfd, trbuf, TICKREQLEN)) != TICKREQLEN){
260 		if(n >= 0)
261 			werrstr("short write to auth server");
262 		goto err;
263 	}
264 	/* send response to auth server */
265 	if(strlen(response) != MD5dlen*2){
266 		werrstr("response not MD5 digest");
267 		goto err;
268 	}
269 	if((n=write(s->asfd, response, MD5dlen*2)) != MD5dlen*2){
270 		if(n >= 0)
271 			werrstr("short write to auth server");
272 		goto err;
273 	}
274 	if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
275 		/* leave connection open so we can try again */
276 		return -1;
277 	}
278 	close(s->asfd);
279 	s->asfd = -1;
280 
281 	convM2T(ticket, &s->t, (char*)s->key->priv);
282 	if(s->t.num != AuthTs
283 	|| memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
284 		if(s->key->successes == 0)
285 			disablekey(s->key);
286 		werrstr(Easproto);
287 		goto err;
288 	}
289 	s->key->successes++;
290 	convM2A(ticket+TICKETLEN, &a, s->t.key);
291 	if(a.num != AuthAc
292 	|| memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
293 	|| a.id != 0){
294 		werrstr(Easproto);
295 		goto err;
296 	}
297 
298 	return 0;
299 err:
300 	if(s->asfd >= 0)
301 		close(s->asfd);
302 	s->asfd = -1;
303 	return -1;
304 }
305 
306 Proto apop = {
307 .name=	"apop",
308 .init=		apopinit,
309 .write=	apopwrite,
310 .read=	apopread,
311 .close=	apopclose,
312 .addkey=	replacekey,
313 .keyprompt=	"!password?"
314 };
315 
316 Proto cram = {
317 .name=	"cram",
318 .init=		apopinit,
319 .write=	apopwrite,
320 .read=	apopread,
321 .close=	apopclose,
322 .addkey=	replacekey,
323 .keyprompt=	"!password?"
324 };
325