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 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 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 sprint(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 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 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 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 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