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