apop.c 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /*
  2. * This file is part of the UCB release of Plan 9. It is subject to the license
  3. * terms in the LICENSE file found in the top-level directory of this
  4. * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
  5. * part of the UCB release of Plan 9, including this file, may be copied,
  6. * modified, propagated, or distributed except according to the terms contained
  7. * in the LICENSE file.
  8. */
  9. /*
  10. * APOP, CRAM - MD5 challenge/response authentication
  11. *
  12. * The client does not authenticate the server, hence no CAI
  13. *
  14. * Client protocol:
  15. * write challenge: randomstring@domain
  16. * read response: 2*MD5dlen hex digits
  17. *
  18. * Server protocol:
  19. * read challenge: randomstring@domain
  20. * write user: user
  21. * write response: 2*MD5dlen hex digits
  22. */
  23. #include "dat.h"
  24. struct State
  25. {
  26. int asfd;
  27. int astype;
  28. Key *key;
  29. Ticket t;
  30. Ticketreq tr;
  31. char chal[128];
  32. char resp[64];
  33. char *user;
  34. };
  35. enum
  36. {
  37. CNeedChal,
  38. CHaveResp,
  39. SHaveChal,
  40. SNeedUser,
  41. SNeedResp,
  42. Maxphase,
  43. };
  44. static char *phasenames[Maxphase] = {
  45. [CNeedChal] "CNeedChal",
  46. [CHaveResp] "CHaveResp",
  47. [SHaveChal] "SHaveChal",
  48. [SNeedUser] "SNeedUser",
  49. [SNeedResp] "SNeedResp",
  50. };
  51. static int dochal(State*);
  52. static int doreply(State*, char*, char*);
  53. static int
  54. apopinit(Proto *p, Fsstate *fss)
  55. {
  56. int iscli, ret;
  57. State *s;
  58. if((iscli = isclient(_strfindattr(fss->attr, "role"))) < 0)
  59. return failure(fss, nil);
  60. s = emalloc(sizeof *s);
  61. fss->phasename = phasenames;
  62. fss->maxphase = Maxphase;
  63. s->asfd = -1;
  64. if(p == &apop)
  65. s->astype = AuthApop;
  66. else if(p == &cram)
  67. s->astype = AuthCram;
  68. else
  69. abort();
  70. if(iscli)
  71. fss->phase = CNeedChal;
  72. else{
  73. if((ret = findp9authkey(&s->key, fss)) != RpcOk){
  74. free(s);
  75. return ret;
  76. }
  77. if(dochal(s) < 0){
  78. free(s);
  79. return failure(fss, nil);
  80. }
  81. fss->phase = SHaveChal;
  82. }
  83. fss->ps = s;
  84. return RpcOk;
  85. }
  86. static int
  87. apopwrite(Fsstate *fss, void *va, uint n)
  88. {
  89. char *a, *v;
  90. int i, ret;
  91. uint8_t digest[MD5dlen];
  92. DigestState *ds;
  93. Key *k;
  94. State *s;
  95. Keyinfo ki;
  96. s = fss->ps;
  97. a = va;
  98. switch(fss->phase){
  99. default:
  100. return phaseerror(fss, "write");
  101. case CNeedChal:
  102. ret = findkey(&k, mkkeyinfo(&ki, fss, nil), "%s", fss->proto->keyprompt);
  103. if(ret != RpcOk)
  104. return ret;
  105. v = _strfindattr(k->privattr, "!password");
  106. if(v == nil)
  107. return failure(fss, "key has no password");
  108. setattrs(fss->attr, k->attr);
  109. switch(s->astype){
  110. default:
  111. abort();
  112. case AuthCram:
  113. hmac_md5((uint8_t*)a, n, (uint8_t*)v, strlen(v),
  114. digest, nil);
  115. snprint(s->resp, sizeof s->resp, "%.*H", MD5dlen, digest);
  116. break;
  117. case AuthApop:
  118. ds = md5((uint8_t*)a, n, nil, nil);
  119. md5((uint8_t*)v, strlen(v), digest, ds);
  120. for(i=0; i<MD5dlen; i++)
  121. sprint(&s->resp[2*i], "%2.2x", digest[i]);
  122. break;
  123. }
  124. closekey(k);
  125. fss->phase = CHaveResp;
  126. return RpcOk;
  127. case SNeedUser:
  128. if((v = _strfindattr(fss->attr, "user")) && strcmp(v, a) != 0)
  129. return failure(fss, "bad user");
  130. fss->attr = setattr(fss->attr, "user=%q", a);
  131. s->user = estrdup(a);
  132. fss->phase = SNeedResp;
  133. return RpcOk;
  134. case SNeedResp:
  135. if(n != 2*MD5dlen)
  136. return failure(fss, "response not MD5 digest");
  137. if(doreply(s, s->user, a) < 0){
  138. fss->phase = SNeedUser;
  139. return failure(fss, nil);
  140. }
  141. fss->haveai = 1;
  142. fss->ai.cuid = s->t.cuid;
  143. fss->ai.suid = s->t.suid;
  144. fss->ai.nsecret = 0;
  145. fss->ai.secret = nil;
  146. fss->phase = Established;
  147. return RpcOk;
  148. }
  149. }
  150. static int
  151. apopread(Fsstate *fss, void *va, uint *n)
  152. {
  153. State *s;
  154. s = fss->ps;
  155. switch(fss->phase){
  156. default:
  157. return phaseerror(fss, "read");
  158. case CHaveResp:
  159. if(*n > strlen(s->resp))
  160. *n = strlen(s->resp);
  161. memmove(va, s->resp, *n);
  162. fss->phase = Established;
  163. fss->haveai = 0;
  164. return RpcOk;
  165. case SHaveChal:
  166. if(*n > strlen(s->chal))
  167. *n = strlen(s->chal);
  168. memmove(va, s->chal, *n);
  169. fss->phase = SNeedUser;
  170. return RpcOk;
  171. }
  172. }
  173. static void
  174. apopclose(Fsstate *fss)
  175. {
  176. State *s;
  177. s = fss->ps;
  178. if(s->asfd >= 0){
  179. close(s->asfd);
  180. s->asfd = -1;
  181. }
  182. if(s->key != nil){
  183. closekey(s->key);
  184. s->key = nil;
  185. }
  186. if(s->user != nil){
  187. free(s->user);
  188. s->user = nil;
  189. }
  190. free(s);
  191. }
  192. static int
  193. dochal(State *s)
  194. {
  195. char *dom, *user, trbuf[TICKREQLEN];
  196. s->asfd = -1;
  197. /* send request to authentication server and get challenge */
  198. /* send request to authentication server and get challenge */
  199. if((dom = _strfindattr(s->key->attr, "dom")) == nil
  200. || (user = _strfindattr(s->key->attr, "user")) == nil){
  201. werrstr("apop/dochal cannot happen");
  202. goto err;
  203. }
  204. s->asfd = _authdial(nil, dom);
  205. /* could generate our own challenge on error here */
  206. if(s->asfd < 0)
  207. goto err;
  208. memset(&s->tr, 0, sizeof(s->tr));
  209. s->tr.type = s->astype;
  210. safecpy(s->tr.authdom, dom, sizeof s->tr.authdom);
  211. safecpy(s->tr.hostid, user, sizeof(s->tr.hostid));
  212. convTR2M(&s->tr, trbuf);
  213. if(write(s->asfd, trbuf, TICKREQLEN) != TICKREQLEN)
  214. goto err;
  215. if(_asrdresp(s->asfd, s->chal, sizeof s->chal) <= 5)
  216. goto err;
  217. return 0;
  218. err:
  219. if(s->asfd >= 0)
  220. close(s->asfd);
  221. s->asfd = -1;
  222. return -1;
  223. }
  224. static int
  225. doreply(State *s, char *user, char *response)
  226. {
  227. char ticket[TICKETLEN+AUTHENTLEN];
  228. char trbuf[TICKREQLEN];
  229. int n;
  230. Authenticator a;
  231. memrandom(s->tr.chal, CHALLEN);
  232. safecpy(s->tr.uid, user, sizeof(s->tr.uid));
  233. convTR2M(&s->tr, trbuf);
  234. if((n=write(s->asfd, trbuf, TICKREQLEN)) != TICKREQLEN){
  235. if(n >= 0)
  236. werrstr("short write to auth server");
  237. goto err;
  238. }
  239. /* send response to auth server */
  240. if(strlen(response) != MD5dlen*2){
  241. werrstr("response not MD5 digest");
  242. goto err;
  243. }
  244. if((n=write(s->asfd, response, MD5dlen*2)) != MD5dlen*2){
  245. if(n >= 0)
  246. werrstr("short write to auth server");
  247. goto err;
  248. }
  249. if(_asrdresp(s->asfd, ticket, TICKETLEN+AUTHENTLEN) < 0){
  250. /* leave connection open so we can try again */
  251. return -1;
  252. }
  253. close(s->asfd);
  254. s->asfd = -1;
  255. convM2T(ticket, &s->t, (char*)s->key->priv);
  256. if(s->t.num != AuthTs
  257. || memcmp(s->t.chal, s->tr.chal, sizeof(s->t.chal)) != 0){
  258. if(s->key->successes == 0)
  259. disablekey(s->key);
  260. werrstr(Easproto);
  261. goto err;
  262. }
  263. s->key->successes++;
  264. convM2A(ticket+TICKETLEN, &a, s->t.key);
  265. if(a.num != AuthAc
  266. || memcmp(a.chal, s->tr.chal, sizeof(a.chal)) != 0
  267. || a.id != 0){
  268. werrstr(Easproto);
  269. goto err;
  270. }
  271. return 0;
  272. err:
  273. if(s->asfd >= 0)
  274. close(s->asfd);
  275. s->asfd = -1;
  276. return -1;
  277. }
  278. Proto apop = {
  279. .name= "apop",
  280. .init= apopinit,
  281. .write= apopwrite,
  282. .read= apopread,
  283. .close= apopclose,
  284. .addkey= replacekey,
  285. .keyprompt= "!password?"
  286. };
  287. Proto cram = {
  288. .name= "cram",
  289. .init= apopinit,
  290. .write= apopwrite,
  291. .read= apopread,
  292. .close= apopclose,
  293. .addkey= replacekey,
  294. .keyprompt= "!password?"
  295. };