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