secstore.c 12 KB

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