secstore.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  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. /* secstore - network login client */
  10. #include <u.h>
  11. #include <libc.h>
  12. #include <mp.h>
  13. #include <libsec.h>
  14. #include <authsrv.h>
  15. #include "SConn.h"
  16. #include "secstore.h"
  17. enum{ CHK = 16, MAXFILES = 100 };
  18. typedef struct AuthConn{
  19. SConn *conn;
  20. char pass[64];
  21. int passlen;
  22. } AuthConn;
  23. int verbose;
  24. Nvrsafe nvr;
  25. void
  26. usage(void)
  27. {
  28. fprint(2, "usage: secstore [-cinv] [-[gG] getfile] [-p putfile] "
  29. "[-r rmfile] [-s tcp!server!5356] [-u user]\n");
  30. exits("usage");
  31. }
  32. static int
  33. getfile(SConn *conn, char *gf, uint8_t **buf, uint32_t *buflen,
  34. uint8_t *key,
  35. int nkey)
  36. {
  37. int fd = -1, i, n, nr, nw, len;
  38. char s[Maxmsg+1];
  39. uint8_t skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
  40. AESstate aes;
  41. DigestState *sha;
  42. memset(&aes, 0, sizeof aes);
  43. snprint(s, Maxmsg, "GET %s", gf);
  44. conn->write(conn, (uint8_t*)s, strlen(s));
  45. /* get file size */
  46. s[0] = '\0';
  47. bufw = bufe = nil;
  48. if(readstr(conn, s) < 0){
  49. fprint(2, "secstore: remote: %s\n", s);
  50. return -1;
  51. }
  52. len = atoi(s);
  53. if(len == -1){
  54. fprint(2, "secstore: remote file %s does not exist\n", gf);
  55. return -1;
  56. }else if(len == -3){
  57. fprint(2, "secstore: implausible filesize for %s\n", gf);
  58. return -1;
  59. }else if(len < 0){
  60. fprint(2, "secstore: GET refused for %s\n", gf);
  61. return -1;
  62. }
  63. if(buf != nil){
  64. *buflen = len - AESbsize - CHK;
  65. *buf = bufw = emalloc(len);
  66. bufe = bufw + len;
  67. }
  68. /* directory listing */
  69. if(strcmp(gf,".")==0){
  70. if(buf != nil)
  71. *buflen = len;
  72. for(i=0; i < len; i += n){
  73. if((n = conn->read(conn, (uint8_t*)s, Maxmsg)) <= 0){
  74. fprint(2, "secstore: empty file chunk\n");
  75. return -1;
  76. }
  77. if(buf == nil)
  78. write(1, s, n);
  79. else
  80. memmove(*buf + i, s, n);
  81. }
  82. return 0;
  83. }
  84. /*
  85. * conn is already encrypted against wiretappers, but gf is also
  86. * encrypted against server breakin.
  87. */
  88. if(buf == nil && (fd = create(gf, OWRITE, 0600)) < 0){
  89. fprint(2, "secstore: can't open %s: %r\n", gf);
  90. return -1;
  91. }
  92. ibr = ibw = ib;
  93. for(nr=0; nr < len;){
  94. if((n = conn->read(conn, ibw, Maxmsg)) <= 0){
  95. fprint(2, "secstore: empty file chunk n=%d nr=%d len=%d: %r\n",
  96. n, nr, len);
  97. return -1;
  98. }
  99. nr += n;
  100. ibw += n;
  101. if(!aes.setup){ /* first time, read 16 byte IV */
  102. if(n < AESbsize){
  103. fprint(2, "secstore: no IV in file\n");
  104. return -1;
  105. }
  106. sha = sha1((uint8_t*)"aescbc file", 11, nil, nil);
  107. sha1(key, nkey, skey, sha);
  108. setupAESstate(&aes, skey, AESbsize, ibr);
  109. memset(skey, 0, sizeof skey);
  110. ibr += AESbsize;
  111. n -= AESbsize;
  112. }
  113. aesCBCdecrypt(ibw-n, n, &aes);
  114. n = ibw - ibr - CHK;
  115. if(n > 0){
  116. if(buf == nil){
  117. nw = write(fd, ibr, n);
  118. if(nw != n){
  119. fprint(2, "secstore: write error on %s", gf);
  120. return -1;
  121. }
  122. }else{
  123. assert(bufw + n <= bufe);
  124. memmove(bufw, ibr, n);
  125. bufw += n;
  126. }
  127. ibr += n;
  128. }
  129. memmove(ib, ibr, ibw-ibr);
  130. ibw = ib + (ibw-ibr);
  131. ibr = ib;
  132. }
  133. if(buf == nil)
  134. close(fd);
  135. n = ibw-ibr;
  136. if(n != CHK || memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0){
  137. fprint(2, "secstore: decrypted file failed to authenticate!\n");
  138. return -1;
  139. }
  140. return 0;
  141. }
  142. /*
  143. * This sends a file to the secstore disk that can, in an emergency, be
  144. * decrypted by the program aescbc.c.
  145. */
  146. static int
  147. putfile(SConn *conn, char *pf, uint8_t *buf, uint32_t len, uint8_t *key,
  148. int nkey)
  149. {
  150. int i, n, fd, ivo, bufi, done;
  151. char s[Maxmsg];
  152. uint8_t skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize];
  153. AESstate aes;
  154. DigestState *sha;
  155. /* create initialization vector */
  156. srand(time(0)); /* doesn't need to be unpredictable */
  157. for(i=0; i<AESbsize; i++)
  158. IV[i] = 0xff & rand();
  159. sha = sha1((uint8_t*)"aescbc file", 11, nil, nil);
  160. sha1(key, nkey, skey, sha);
  161. setupAESstate(&aes, skey, AESbsize, IV);
  162. memset(skey, 0, sizeof skey);
  163. snprint(s, Maxmsg, "PUT %s", pf);
  164. conn->write(conn, (uint8_t*)s, strlen(s));
  165. if(buf == nil){
  166. /* get file size */
  167. if((fd = open(pf, OREAD)) < 0){
  168. fprint(2, "secstore: can't open %s: %r\n", pf);
  169. return -1;
  170. }
  171. len = seek(fd, 0, 2);
  172. seek(fd, 0, 0);
  173. } else
  174. fd = -1;
  175. if(len > MAXFILESIZE){
  176. fprint(2, "secstore: implausible filesize %ld for %s\n",
  177. len, pf);
  178. return -1;
  179. }
  180. /* send file size */
  181. snprint(s, Maxmsg, "%ld", len + AESbsize + CHK);
  182. conn->write(conn, (uint8_t*)s, strlen(s));
  183. /* send IV and file+XXXXX in Maxmsg chunks */
  184. ivo = AESbsize;
  185. bufi = 0;
  186. memcpy(b, IV, ivo);
  187. for(done = 0; !done; ){
  188. if(buf == nil){
  189. n = read(fd, b+ivo, Maxmsg-ivo);
  190. if(n < 0){
  191. fprint(2, "secstore: read error on %s: %r\n",
  192. pf);
  193. return -1;
  194. }
  195. }else{
  196. if((n = len - bufi) > Maxmsg-ivo)
  197. n = Maxmsg-ivo;
  198. memcpy(b+ivo, buf+bufi, n);
  199. bufi += n;
  200. }
  201. n += ivo;
  202. ivo = 0;
  203. if(n < Maxmsg){ /* EOF on input; append XX... */
  204. memset(b+n, 'X', CHK);
  205. n += CHK; /* might push n>Maxmsg */
  206. done = 1;
  207. }
  208. aesCBCencrypt(b, n, &aes);
  209. if(n > Maxmsg){
  210. assert(done==1);
  211. conn->write(conn, b, Maxmsg);
  212. n -= Maxmsg;
  213. memmove(b, b+Maxmsg, n);
  214. }
  215. conn->write(conn, b, n);
  216. }
  217. if(buf == nil)
  218. close(fd);
  219. fprint(2, "secstore: saved %ld bytes\n", len);
  220. return 0;
  221. }
  222. static int
  223. removefile(SConn *conn, char *rf)
  224. {
  225. char buf[Maxmsg];
  226. if(strchr(rf, '/') != nil){
  227. fprint(2, "secstore: simple filenames, not paths like %s\n", rf);
  228. return -1;
  229. }
  230. snprint(buf, Maxmsg, "RM %s", rf);
  231. conn->write(conn, (uint8_t*)buf, strlen(buf));
  232. return 0;
  233. }
  234. static int
  235. cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
  236. {
  237. uint32_t len;
  238. int rv = -1;
  239. uint8_t *memfile, *memcur, *memnext;
  240. while(*gf != nil){
  241. if(verbose)
  242. fprint(2, "get %s\n", *gf);
  243. if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len,
  244. (uint8_t*)c->pass, c->passlen) < 0)
  245. goto Out;
  246. if(*Gflag){
  247. /* write 1 line at a time, as required by /mnt/factotum/ctl */
  248. memcur = memfile;
  249. while(len>0){
  250. memnext = (uint8_t*)strchr((char*)memcur,
  251. '\n');
  252. if(memnext){
  253. write(1, memcur, memnext-memcur+1);
  254. len -= memnext-memcur+1;
  255. memcur = memnext+1;
  256. }else{
  257. write(1, memcur, len);
  258. break;
  259. }
  260. }
  261. free(memfile);
  262. }
  263. gf++;
  264. Gflag++;
  265. }
  266. while(*pf != nil){
  267. if(verbose)
  268. fprint(2, "put %s\n", *pf);
  269. if(putfile(c->conn, *pf, nil, 0, (uint8_t*)c->pass, c->passlen) < 0)
  270. goto Out;
  271. pf++;
  272. }
  273. while(*rf != nil){
  274. if(verbose)
  275. fprint(2, "rm %s\n", *rf);
  276. if(removefile(c->conn, *rf) < 0)
  277. goto Out;
  278. rf++;
  279. }
  280. c->conn->write(c->conn, (uint8_t*)"BYE", 3);
  281. rv = 0;
  282. Out:
  283. c->conn->free(c->conn);
  284. return rv;
  285. }
  286. static int
  287. chpasswd(AuthConn *c, char *id)
  288. {
  289. int rv = -1, newpasslen = 0;
  290. uint32_t len;
  291. uint8_t *memfile;
  292. char *newpass, *passck, *list, *cur, *next, *hexHi;
  293. char *f[8], prompt[128];
  294. mpint *H, *Hi;
  295. H = mpnew(0);
  296. Hi = mpnew(0);
  297. /* changing our password is vulnerable to connection failure */
  298. for(;;){
  299. snprint(prompt, sizeof(prompt), "new password for %s: ", id);
  300. newpass = getpassm(prompt);
  301. if(newpass == nil)
  302. goto Out;
  303. if(strlen(newpass) >= 7)
  304. break;
  305. else if(strlen(newpass) == 0){
  306. fprint(2, "!password change aborted\n");
  307. goto Out;
  308. }
  309. print("!password must be at least 7 characters\n");
  310. }
  311. newpasslen = strlen(newpass);
  312. snprint(prompt, sizeof(prompt), "retype password: ");
  313. passck = getpassm(prompt);
  314. if(passck == nil){
  315. fprint(2, "secstore: getpassm failed\n");
  316. goto Out;
  317. }
  318. if(strcmp(passck, newpass) != 0){
  319. fprint(2, "secstore: passwords didn't match\n");
  320. goto Out;
  321. }
  322. c->conn->write(c->conn, (uint8_t*)"CHPASS", strlen("CHPASS"));
  323. hexHi = PAK_Hi(id, newpass, H, Hi);
  324. c->conn->write(c->conn, (uint8_t*)hexHi, strlen(hexHi));
  325. free(hexHi);
  326. mpfree(H);
  327. mpfree(Hi);
  328. if(getfile(c->conn, ".", (uint8_t **) &list, &len, nil, 0) < 0){
  329. fprint(2, "secstore: directory listing failed.\n");
  330. goto Out;
  331. }
  332. /* Loop over files and reencrypt them; try to keep going after error */
  333. for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){
  334. *next = '\0';
  335. if(tokenize(cur, f, nelem(f))< 1)
  336. break;
  337. fprint(2, "secstore: reencrypting '%s'\n", f[0]);
  338. if(getfile(c->conn, f[0], &memfile, &len, (uint8_t*)c->pass,
  339. c->passlen) < 0){
  340. fprint(2, "secstore: getfile of '%s' failed\n", f[0]);
  341. continue;
  342. }
  343. if(putfile(c->conn, f[0], memfile, len, (uint8_t*)newpass,
  344. newpasslen) < 0)
  345. fprint(2, "secstore: putfile of '%s' failed\n", f[0]);
  346. free(memfile);
  347. }
  348. free(list);
  349. c->conn->write(c->conn, (uint8_t*)"BYE", 3);
  350. rv = 0;
  351. Out:
  352. if(newpass != nil){
  353. memset(newpass, 0, newpasslen);
  354. free(newpass);
  355. }
  356. c->conn->free(c->conn);
  357. return rv;
  358. }
  359. static AuthConn*
  360. login(char *id, char *dest, int pass_stdin, int pass_nvram)
  361. {
  362. int fd, n, ntry = 0;
  363. char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass;
  364. AuthConn *c;
  365. if(dest == nil)
  366. sysfatal("tried to login with nil dest");
  367. c = emalloc(sizeof(*c));
  368. if(pass_nvram){
  369. if(readnvram(&nvr, 0) < 0)
  370. exits("readnvram: %r");
  371. strecpy(c->pass, c->pass+sizeof c->pass, nvr.config);
  372. }
  373. if(pass_stdin){
  374. n = readn(0, s, Maxmsg-2); /* so len(PINSTA)<Maxmsg-3 */
  375. if(n < 1)
  376. exits("no password on standard input");
  377. s[n] = 0;
  378. nl = strchr(s, '\n');
  379. if(nl){
  380. *nl++ = 0;
  381. PINSTA = estrdup(nl);
  382. nl = strchr(PINSTA, '\n');
  383. if(nl)
  384. *nl = 0;
  385. }
  386. strecpy(c->pass, c->pass+sizeof c->pass, s);
  387. }
  388. for(;;){
  389. if(verbose)
  390. fprint(2, "dialing %s\n", dest);
  391. if((fd = dial(dest, nil, nil, nil)) < 0){
  392. fprint(2, "secstore: can't dial %s\n", dest);
  393. free(c);
  394. return nil;
  395. }
  396. if((c->conn = newSConn(fd)) == nil){
  397. free(c);
  398. return nil;
  399. }
  400. ntry++;
  401. if(!pass_stdin && !pass_nvram){
  402. pass = getpassm("secstore password: ");
  403. if(strlen(pass) >= sizeof c->pass){
  404. fprint(2, "secstore: password too long, skipping secstore login\n");
  405. exits("password too long");
  406. }
  407. strcpy(c->pass, pass);
  408. memset(pass, 0, strlen(pass));
  409. free(pass);
  410. }
  411. if(c->pass[0]==0){
  412. fprint(2, "secstore: null password, skipping secstore login\n");
  413. exits("no password");
  414. }
  415. if(PAKclient(c->conn, id, c->pass, &S) >= 0)
  416. break;
  417. c->conn->free(c->conn);
  418. if(pass_stdin)
  419. exits("invalid password on standard input");
  420. if(pass_nvram)
  421. exits("invalid password in nvram");
  422. /* and let user try retyping the password */
  423. if(ntry==3)
  424. fprint(2, "Enter an empty password to quit.\n");
  425. }
  426. c->passlen = strlen(c->pass);
  427. fprint(2, "%s\n", S);
  428. free(S);
  429. if(readstr(c->conn, s) < 0){
  430. c->conn->free(c->conn);
  431. free(c);
  432. return nil;
  433. }
  434. if(strcmp(s, "STA") == 0){
  435. int32_t sn;
  436. if(pass_stdin){
  437. if(PINSTA)
  438. strncpy(s+3, PINSTA, sizeof s - 3);
  439. else
  440. exits("missing PIN+SecureID on standard input");
  441. free(PINSTA);
  442. }else{
  443. pass = getpassm("STA PIN+SecureID: ");
  444. strncpy(s+3, pass, sizeof s - 4);
  445. memset(pass, 0, strlen(pass));
  446. free(pass);
  447. }
  448. sn = strlen(s+3);
  449. if(verbose)
  450. fprint(2, "%ld\n", sn);
  451. c->conn->write(c->conn, (uint8_t*)s, sn+3);
  452. readstr(c->conn, s); /* TODO: check for error? */
  453. }
  454. if(strcmp(s, "OK") != 0){
  455. fprint(2, "%s: %s\n", argv0, s);
  456. c->conn->free(c->conn);
  457. free(c);
  458. return nil;
  459. }
  460. return c;
  461. }
  462. void
  463. main(int argc, char **argv)
  464. {
  465. int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc;
  466. int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1];
  467. char *serve, *tcpserve, *user;
  468. char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES];
  469. AuthConn *c;
  470. serve = "$auth";
  471. user = getuser();
  472. memset(Gflag, 0, sizeof Gflag);
  473. ARGBEGIN{
  474. case 'c':
  475. chpass = 1;
  476. break;
  477. case 'G':
  478. Gflag[ngfile]++;
  479. /* fall through */
  480. case 'g':
  481. if(ngfile >= MAXFILES)
  482. exits("too many gfiles");
  483. gfile[ngfile++] = EARGF(usage());
  484. break;
  485. case 'i':
  486. pass_stdin = 1;
  487. break;
  488. case 'n':
  489. pass_nvram = 1;
  490. break;
  491. case 'p':
  492. if(npfile >= MAXFILES)
  493. exits("too many pfiles");
  494. pfile[npfile++] = EARGF(usage());
  495. break;
  496. case 'r':
  497. if(nrfile >= MAXFILES)
  498. exits("too many rfiles");
  499. rfile[nrfile++] = EARGF(usage());
  500. break;
  501. case 's':
  502. serve = EARGF(usage());
  503. break;
  504. case 'u':
  505. user = EARGF(usage());
  506. break;
  507. case 'v':
  508. verbose++;
  509. break;
  510. default:
  511. usage();
  512. break;
  513. }ARGEND;
  514. gfile[ngfile] = nil;
  515. pfile[npfile] = nil;
  516. rfile[nrfile] = nil;
  517. if(argc!=0 || user==nil)
  518. usage();
  519. if(chpass && (ngfile || npfile || nrfile)){
  520. fprint(2, "secstore: Get, put, and remove invalid with password change.\n");
  521. exits("usage");
  522. }
  523. rc = strlen(serve) + sizeof "tcp!!99990";
  524. tcpserve = emalloc(rc);
  525. if(strchr(serve,'!'))
  526. strcpy(tcpserve, serve);
  527. else
  528. snprint(tcpserve, rc, "tcp!%s!5356", serve);
  529. c = login(user, tcpserve, pass_stdin, pass_nvram);
  530. free(tcpserve);
  531. if(c == nil)
  532. sysfatal("secstore authentication failed");
  533. if(chpass)
  534. rc = chpasswd(c, user);
  535. else
  536. rc = cmd(c, gfile, Gflag, pfile, rfile);
  537. if(rc < 0)
  538. sysfatal("secstore cmd failed");
  539. exits("");
  540. }