apop.c (6353B)
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 */ 20 21 #include "std.h" 22 #include "dat.h" 23 24 extern Proto apop, cram; 25 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 } 35 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; 45 46 chal = nil; 47 k = nil; 48 res = nil; 49 ret = -1; 50 attr = c->attr; 51 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 } 60 61 c->state = "find key"; 62 k = keyfetch(c, "%A %s", attr, c->proto->keyprompt); 63 if(k == nil) 64 goto out; 65 66 c->state = "read challenge"; 67 if((nchal = convreadm(c, &chal)) < 0) 68 goto out; 69 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); 79 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 } 89 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; 99 100 c->state = "write response"; 101 if(convprint(c, "%.*H", sizeof resp, resp) < 0) 102 goto out; 103 104 c->state = "read result"; 105 if(convreadm(c, &res) < 0) 106 goto out; 107 108 if(strcmp(res, "ok") == 0) 109 break; 110 111 if(strncmp(res, "bad ", 4) != 0){ 112 werrstr("bad result: %s", res); 113 goto out; 114 } 115 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; 121 } 122 free(res); 123 res = nil; 124 } 125 126 werrstr("succeeded"); 127 ret = 0; 128 129 out: 130 keyclose(k); 131 free(chal); 132 free(res); 133 if(c->attr != attr) 134 freeattr(attr); 135 return ret; 136 } 137 138 /* shared with auth dialing routines */ 139 typedef struct ServerState ServerState; 140 struct ServerState 141 { 142 int asfd; 143 Key *k; 144 Ticketreq tr; 145 Ticket t; 146 char *dom; 147 char *hostid; 148 }; 149 150 enum 151 { 152 APOPCHALLEN = 128 153 }; 154 155 static int apopchal(ServerState*, int, char[APOPCHALLEN]); 156 static int apopresp(ServerState*, char*, char*); 157 158 static int 159 apopserver(Conv *c) 160 { 161 char chal[APOPCHALLEN], *user, *resp; 162 ServerState s; 163 int astype, ret; 164 Attr *a; 165 166 ret = -1; 167 user = nil; 168 resp = nil; 169 memset(&s, 0, sizeof s); 170 s.asfd = -1; 171 172 if(c->proto == &apop) 173 astype = AuthApop; 174 else if(c->proto == &cram) 175 astype = AuthCram; 176 else{ 177 werrstr("bad proto"); 178 goto out; 179 } 180 181 c->state = "find key"; 182 if((s.k = plan9authkey(c->attr)) == nil) 183 goto out; 184 185 a = copyattr(s.k->attr); 186 a = delattr(a, "proto"); 187 c->attr = addattrs(c->attr, a); 188 freeattr(a); 189 190 c->state = "authdial"; 191 s.hostid = strfindattr(s.k->attr, "user"); 192 s.dom = strfindattr(s.k->attr, "dom"); 193 if((s.asfd = xioauthdial(nil, s.dom)) < 0){ 194 werrstr("authdial %s: %r", s.dom); 195 goto out; 196 } 197 198 c->state = "authchal"; 199 if(apopchal(&s, astype, chal) < 0) 200 goto out; 201 202 c->state = "write challenge"; 203 if(convprint(c, "%s", chal) < 0) 204 goto out; 205 206 for(;;){ 207 c->state = "read user"; 208 if(convreadm(c, &user) < 0) 209 goto out; 210 211 c->state = "read response"; 212 if(convreadm(c, &resp) < 0) 213 goto out; 214 215 c->state = "authwrite"; 216 switch(apopresp(&s, user, resp)){ 217 case -1: 218 goto out; 219 case 0: 220 c->state = "write status"; 221 if(convprint(c, "bad authentication failed") < 0) 222 goto out; 223 break; 224 case 1: 225 c->state = "write status"; 226 if(convprint(c, "ok") < 0) 227 goto out; 228 goto ok; 229 } 230 free(user); 231 free(resp); 232 user = nil; 233 resp = nil; 234 } 235 236 ok: 237 ret = 0; 238 c->attr = addcap(c->attr, c->sysuser, &s.t); 239 240 out: 241 keyclose(s.k); 242 free(user); 243 free(resp); 244 xioclose(s.asfd); 245 return ret; 246 } 247 248 static int 249 apopchal(ServerState *s, int astype, char chal[APOPCHALLEN]) 250 { 251 char trbuf[TICKREQLEN]; 252 Ticketreq tr; 253 254 memset(&tr, 0, sizeof tr); 255 256 tr.type = astype; 257 258 if(strlen(s->hostid) >= sizeof tr.hostid){ 259 werrstr("hostid too long"); 260 return -1; 261 } 262 strcpy(tr.hostid, s->hostid); 263 264 if(strlen(s->dom) >= sizeof tr.authdom){ 265 werrstr("domain too long"); 266 return -1; 267 } 268 strcpy(tr.authdom, s->dom); 269 270 convTR2M(&tr, trbuf); 271 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) 272 return -1; 273 274 if(xioasrdresp(s->asfd, chal, APOPCHALLEN) <= 5) 275 return -1; 276 277 s->tr = tr; 278 return 0; 279 } 280 281 static int 282 apopresp(ServerState *s, char *user, char *resp) 283 { 284 char tabuf[TICKETLEN+AUTHENTLEN]; 285 char trbuf[TICKREQLEN]; 286 int len; 287 Authenticator a; 288 Ticket t; 289 Ticketreq tr; 290 291 tr = s->tr; 292 if(memrandom(tr.chal, CHALLEN) < 0) 293 return -1; 294 295 if(strlen(user) >= sizeof tr.uid){ 296 werrstr("uid too long"); 297 return -1; 298 } 299 strcpy(tr.uid, user); 300 301 convTR2M(&tr, trbuf); 302 if(xiowrite(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN) 303 return -1; 304 305 len = strlen(resp); 306 if(xiowrite(s->asfd, resp, len) != len) 307 return -1; 308 309 if(xioasrdresp(s->asfd, tabuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN) 310 return 0; 311 312 convM2T(tabuf, &t, s->k->priv); 313 if(t.num != AuthTs 314 || memcmp(t.chal, tr.chal, sizeof tr.chal) != 0){ 315 werrstr("key mismatch with auth server"); 316 return -1; 317 } 318 319 convM2A(tabuf+TICKETLEN, &a, t.key); 320 if(a.num != AuthAc 321 || memcmp(a.chal, tr.chal, sizeof a.chal) != 0 322 || a.id != 0){ 323 werrstr("key2 mismatch with auth server"); 324 return -1; 325 } 326 327 s->t = t; 328 return 1; 329 } 330 331 static Role 332 apoproles[] = 333 { 334 "client", apopclient, 335 "server", apopserver, 336 0 337 }; 338 339 Proto apop = { 340 "apop", 341 apoproles, 342 "user? !password?", 343 apopcheck, 344 nil 345 }; 346 347 Proto cram = { 348 "cram", 349 apoproles, 350 "user? !password?", 351 apopcheck, 352 nil 353 };