Blob


1 /*
2 * APOP, CRAM - MD5 challenge/response authentication
3 *
4 * The client does not authenticate the server, hence no CAI.
5 *
6 * Protocol:
7 *
8 * S -> C: random@domain
9 * C -> S: user hex-response
10 * S -> C: ok
11 *
12 * Note that this is the protocol between factotum and the local
13 * program, not between the two factotums. The information
14 * exchanged here is wrapped in the APOP protocol by the local
15 * programs.
16 *
17 * If S sends "bad [msg]" instead of "ok", that is a hint that the key is bad.
18 * The protocol goes back to "C -> S: user hex-response".
19 */
21 #include "std.h"
22 #include "dat.h"
24 extern Proto apop, cram;
26 static int
27 apopcheck(Key *k)
28 {
29 if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
30 werrstr("need user and !password attributes");
31 return -1;
32 }
33 return 0;
34 }
36 static int
37 apopclient(Conv *c)
38 {
39 char *chal, *pw, *res;
40 int astype, nchal, npw, ntry, ret;
41 uchar resp[MD5dlen];
42 Attr *attr;
43 DigestState *ds;
44 Key *k;
46 chal = nil;
47 k = nil;
48 res = nil;
49 ret = -1;
50 attr = c->attr;
52 if(c->proto == &apop)
53 astype = AuthApop;
54 else if(c->proto == &cram)
55 astype = AuthCram;
56 else{
57 werrstr("bad proto");
58 goto out;
59 }
61 c->state = "find key";
62 k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
63 if(k == nil)
64 goto out;
66 c->state = "read challenge";
67 if((nchal = convreadm(c, &chal)) < 0)
68 goto out;
70 for(ntry=1;; ntry++){
71 if(c->attr != attr)
72 freeattr(c->attr);
73 c->attr = addattrs(copyattr(attr), k->attr);
74 if((pw = strfindattr(k->privattr, "!password")) == nil){
75 werrstr("key has no password (cannot happen?)");
76 goto out;
77 }
78 npw = strlen(pw);
80 switch(astype){
81 case AuthApop:
82 ds = md5((uchar*)chal, nchal, nil, nil);
83 md5((uchar*)pw, npw, resp, ds);
84 break;
85 case AuthCram:
86 hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil);
87 break;
88 }
90 /* C->S: APOP user hex-response\n */
91 if(ntry == 1)
92 c->state = "write user";
93 else{
94 sprint(c->statebuf, "write user (auth attempt #%d)", ntry);
95 c->state = c->statebuf;
96 }
97 if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0)
98 goto out;
100 c->state = "write response";
101 if(convprint(c, "%.*H", sizeof resp, resp) < 0)
102 goto out;
104 c->state = "read result";
105 if(convreadm(c, &res) < 0)
106 goto out;
108 if(strcmp(res, "ok") == 0)
109 break;
111 if(strncmp(res, "bad ", 4) != 0){
112 werrstr("bad result: %s", res);
113 goto out;
116 c->state = "replace key";
117 if((k = keyreplace(c, k, "%s", res+4)) == nil){
118 c->state = "auth failed";
119 werrstr("%s", res+4);
120 goto out;
122 free(res);
123 res = nil;
126 werrstr("succeeded");
127 ret = 0;
129 out:
130 keyclose(k);
131 free(chal);
132 if(c->attr != attr)
133 freeattr(attr);
134 return ret;
137 /* shared with auth dialing routines */
138 typedef struct ServerState ServerState;
139 struct ServerState
141 int asfd;
142 Key *k;
143 Ticketreq tr;
144 Ticket t;
145 char *dom;
146 char *hostid;
147 };
149 enum
151 APOPCHALLEN = 128
152 };
154 static int apopchal(ServerState*, int, char[APOPCHALLEN]);
155 static int apopresp(ServerState*, char*, char*);
157 static int
158 apopserver(Conv *c)
160 char chal[APOPCHALLEN], *user, *resp;
161 ServerState s;
162 int astype, ret;
163 Attr *a;
165 ret = -1;
166 user = nil;
167 resp = nil;
168 memset(&s, 0, sizeof s);
169 s.asfd = -1;
171 if(c->proto == &apop)
172 astype = AuthApop;
173 else if(c->proto == &cram)
174 astype = AuthCram;
175 else{
176 werrstr("bad proto");
177 goto out;
180 c->state = "find key";
181 if((s.k = plan9authkey(c->attr)) == nil)
182 goto out;
184 a = copyattr(s.k->attr);
185 a = delattr(a, "proto");
186 c->attr = addattrs(c->attr, a);
187 freeattr(a);
189 c->state = "authdial";
190 s.hostid = strfindattr(s.k->attr, "user");
191 s.dom = strfindattr(s.k->attr, "dom");
192 if((s.asfd = xioauthdial(nil, s.dom)) < 0){
193 werrstr("authdial %s: %r", s.dom);
194 goto out;
197 c->state = "authchal";
198 if(apopchal(&s, astype, chal) < 0)
199 goto out;
201 c->state = "write challenge";
202 if(convprint(c, "%s", chal) < 0)
203 goto out;
205 for(;;){
206 c->state = "read user";
207 if(convreadm(c, &user) < 0)
208 goto out;
210 c->state = "read response";
211 if(convreadm(c, &resp) < 0)
212 goto out;
214 c->state = "authwrite";
215 switch(apopresp(&s, user, resp)){
216 case -1:
217 goto out;
218 case 0:
219 c->state = "write status";
220 if(convprint(c, "bad authentication failed") < 0)
221 goto out;
222 break;
223 case 1:
224 c->state = "write status";
225 if(convprint(c, "ok") < 0)
226 goto out;
227 goto ok;
229 free(user);
230 free(resp);
231 user = nil;
232 resp = nil;
235 ok:
236 ret = 0;
237 c->attr = addcap(c->attr, c->sysuser, &s.t);
239 out:
240 keyclose(s.k);
241 free(user);
242 free(resp);
243 xioclose(s.asfd);
244 return ret;
247 static int
248 apopchal(ServerState *s, int astype, char chal[APOPCHALLEN])
250 char trbuf[TICKREQLEN];
251 Ticketreq tr;
253 memset(&tr, 0, sizeof tr);
255 tr.type = astype;
257 if(strlen(s->hostid) >= sizeof tr.hostid){
258 werrstr("hostid too long");
259 return -1;
261 strcpy(tr.hostid, s->hostid);
263 if(strlen(s->dom) >= sizeof tr.authdom){
264 werrstr("domain too long");
265 return -1;
267 strcpy(tr.authdom, s->dom);
269 convTR2M(&tr, trbuf);
270 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
271 return -1;
273 if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5)
274 return -1;
276 s->tr = tr;
277 return 0;
280 static int
281 apopresp(ServerState *s, char *user, char *resp)
283 char tabuf[TICKETLEN+AUTHENTLEN];
284 char trbuf[TICKREQLEN];
285 int len;
286 Authenticator a;
287 Ticket t;
288 Ticketreq tr;
290 tr = s->tr;
291 if(memrandom(tr.chal, CHALLEN) < 0)
292 return -1;
294 if(strlen(user) >= sizeof tr.uid){
295 werrstr("uid too long");
296 return -1;
298 strcpy(tr.uid, user);
300 convTR2M(&tr, trbuf);
301 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
302 return -1;
304 len = strlen(resp);
305 if(xiowrite(s->asfd, resp, len) != len)
306 return -1;
308 if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
309 return 0;
311 convM2T(tabuf, &t, s->k->priv);
312 if(t.num != AuthTs
313 || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
314 werrstr("key mismatch with auth server");
315 return -1;
318 convM2A(tabuf+TICKETLEN, &a, t.key);
319 if(a.num != AuthAc
320 || memcmp(a.chal, tr.chal, sizeof a.chal) != 0
321 || a.id != 0){
322 werrstr("key2 mismatch with auth server");
323 return -1;
326 s->t = t;
327 return 1;
330 static Role
331 apoproles[] =
333 "client", apopclient,
334 "server", apopserver,
336 };
338 Proto apop = {
339 "apop",
340 apoproles,
341 "user? !password?",
342 apopcheck,
343 nil
344 };
346 Proto cram = {
347 "cram",
348 apoproles,
349 "user? !password?",
350 apopcheck,
351 nil
352 };