secstore.c 10 KB

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