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: 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".
19 */
21 #include "std.h"
22 #include "dat.h"
24 static int
25 apopcheck(Key *k)
26 {
27 if(!strfindattr(k->attr, "user") || !strfindattr(k->privattr, "!password")){
28 werrstr("need user and !password attributes");
29 return -1;
30 }
31 return 0;
32 }
34 static int
35 apopclient(Conv *c)
36 {
37 char *chal, *pw, *res;
38 int astype, nchal, npw, ntry, ret;
39 uchar resp[MD5dlen];
40 Attr *attr;
41 DigestState *ds;
42 Key *k;
44 chal = nil;
45 k = nil;
46 res = nil;
47 ret = -1;
48 attr = c->attr;
50 if(c->proto == &apop)
51 astype = AuthApop;
52 else if(c->proto == &cram)
53 astype = AuthCram;
54 else{
55 werrstr("bad proto");
56 goto out;
57 }
59 c->state = "find key";
60 k = keyfetch(c, "%A %s", attr, c->proto->keyprompt);
61 if(k == nil)
62 goto out;
64 c->state = "read challenge";
65 if((nchal = convreadm(c, &chal)) < 0)
66 goto out;
68 for(ntry=1;; ntry++){
69 if(c->attr != attr)
70 freeattr(c->attr);
71 c->attr = addattrs(copyattr(attr), k->attr);
72 if((pw = strfindattr(k->privattr, "!password")) == nil){
73 werrstr("key has no password (cannot happen?)");
74 goto out;
75 }
76 npw = strlen(pw);
78 switch(astype){
79 case AuthApop:
80 ds = md5((uchar*)chal, nchal, nil, nil);
81 md5((uchar*)pw, npw, resp, ds);
82 break;
83 case AuthCram:
84 hmac_md5((uchar*)chal, nchal, (uchar*)pw, npw, resp, nil);
85 break;
86 }
88 /* C->S: APOP user hex-response\n */
89 if(ntry == 1)
90 c->state = "write user";
91 else{
92 sprint(c->statebuf, "write user (auth attempt #%d)", ntry);
93 c->state = c->statebuf;
94 }
95 if(convprint(c, "%s", strfindattr(k->attr, "user")) < 0)
96 goto out;
98 c->state = "write response";
99 if(convprint(c, "%.*H", sizeof resp, resp) < 0)
100 goto out;
102 c->state = "read result";
103 if(convreadm(c, &res) < 0)
104 goto out;
106 if(strcmp(res, "ok") == 0)
107 break;
109 if(strncmp(res, "bad ", 4) != 0){
110 werrstr("bad result: %s", res);
111 goto out;
114 c->state = "replace key";
115 if((k = keyreplace(c, k, "%s", res+4)) == nil){
116 c->state = "auth failed";
117 werrstr("%s", res+4);
118 goto out;
120 free(res);
121 res = nil;
124 werrstr("succeeded");
125 ret = 0;
127 out:
128 keyclose(k);
129 free(chal);
130 if(c->attr != attr)
131 freeattr(attr);
132 return ret;
135 /* shared with auth dialing routines */
136 typedef struct ServerState ServerState;
137 struct ServerState
139 int asfd;
140 Key *k;
141 Ticketreq tr;
142 Ticket t;
143 char *dom;
144 char *hostid;
145 };
147 enum
149 APOPCHALLEN = 128,
150 };
152 static int apopchal(ServerState*, int, char[APOPCHALLEN]);
153 static int apopresp(ServerState*, char*, char*);
155 static int
156 apopserver(Conv *c)
158 char chal[APOPCHALLEN], *user, *resp;
159 ServerState s;
160 int astype, ret;
161 Attr *a;
163 ret = -1;
164 user = nil;
165 resp = nil;
166 memset(&s, 0, sizeof s);
167 s.asfd = -1;
169 if(c->proto == &apop)
170 astype = AuthApop;
171 else if(c->proto == &cram)
172 astype = AuthCram;
173 else{
174 werrstr("bad proto");
175 goto out;
178 c->state = "find key";
179 if((s.k = plan9authkey(c->attr)) == nil)
180 goto out;
182 a = copyattr(s.k->attr);
183 a = delattr(a, "proto");
184 c->attr = addattrs(c->attr, a);
185 freeattr(a);
187 c->state = "authdial";
188 s.hostid = strfindattr(s.k->attr, "user");
189 s.dom = strfindattr(s.k->attr, "dom");
190 if((s.asfd = xioauthdial(nil, s.dom)) < 0){
191 werrstr("authdial %s: %r", s.dom);
192 goto out;
195 c->state = "authchal";
196 if(apopchal(&s, astype, chal) < 0)
197 goto out;
199 c->state = "write challenge";
200 if(convprint(c, "%s", chal) < 0)
201 goto out;
203 for(;;){
204 c->state = "read user";
205 if(convreadm(c, &user) < 0)
206 goto out;
208 c->state = "read response";
209 if(convreadm(c, &resp) < 0)
210 goto out;
212 c->state = "authwrite";
213 switch(apopresp(&s, user, resp)){
214 case -1:
215 goto out;
216 case 0:
217 c->state = "write status";
218 if(convprint(c, "bad authentication failed") < 0)
219 goto out;
220 break;
221 case 1:
222 c->state = "write status";
223 if(convprint(c, "ok") < 0)
224 goto out;
225 goto ok;
227 free(user);
228 free(resp);
229 user = nil;
230 resp = nil;
233 ok:
234 ret = 0;
235 c->attr = addcap(c->attr, c->sysuser, &s.t);
237 out:
238 keyclose(s.k);
239 free(user);
240 free(resp);
241 // xioclose(s.asfd);
242 return ret;
245 static int
246 apopchal(ServerState *s, int astype, char chal[APOPCHALLEN])
248 char trbuf[TICKREQLEN];
249 Ticketreq tr;
251 memset(&tr, 0, sizeof tr);
253 tr.type = astype;
255 if(strlen(s->hostid) >= sizeof tr.hostid){
256 werrstr("hostid too long");
257 return -1;
259 strcpy(tr.hostid, s->hostid);
261 if(strlen(s->dom) >= sizeof tr.authdom){
262 werrstr("domain too long");
263 return -1;
265 strcpy(tr.authdom, s->dom);
267 convTR2M(&tr, trbuf);
268 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
269 return -1;
271 if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5)
272 return -1;
274 s->tr = tr;
275 return 0;
278 static int
279 apopresp(ServerState *s, char *user, char *resp)
281 char tabuf[TICKETLEN+AUTHENTLEN];
282 char trbuf[TICKREQLEN];
283 int len;
284 Authenticator a;
285 Ticket t;
286 Ticketreq tr;
288 tr = s->tr;
289 if(memrandom(tr.chal, CHALLEN) < 0)
290 return -1;
292 if(strlen(user) >= sizeof tr.uid){
293 werrstr("uid too long");
294 return -1;
296 strcpy(tr.uid, user);
298 convTR2M(&tr, trbuf);
299 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
300 return -1;
302 len = strlen(resp);
303 if(xiowrite(s->asfd, resp, len) != len)
304 return -1;
306 if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
307 return 0;
309 convM2T(tabuf, &t, s->k->priv);
310 if(t.num != AuthTs
311 || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){
312 werrstr("key mismatch with auth server");
313 return -1;
316 convM2A(tabuf+TICKETLEN, &a, t.key);
317 if(a.num != AuthAc
318 || memcmp(a.chal, tr.chal, sizeof a.chal) != 0
319 || a.id != 0){
320 werrstr("key2 mismatch with auth server");
321 return -1;
324 s->t = t;
325 return 1;
328 static Role
329 apoproles[] =
331 "client", apopclient,
332 "server", apopserver,
334 };
336 Proto apop = {
337 .name= "apop",
338 .roles= apoproles,
339 .checkkey= apopcheck,
340 .keyprompt= "user? !password?",
341 };
343 Proto cram = {
344 .name= "cram",
345 .roles= apoproles,
346 .checkkey= apopcheck,
347 .keyprompt= "user? !password?",
348 };