secstore.c 12 KB

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