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