/* secstore - network login client */ #include #include #include #include #include #include "SConn.h" #include "secstore.h" enum{ CHK = 16, MAXFILES = 100 }; typedef struct AuthConn{ SConn *conn; char pass[64]; int passlen; } AuthConn; int verbose; Nvrsafe nvr; void usage(void) { fprint(2, "usage: secstore [-cinv] [-[gG] getfile] [-p putfile] " "[-r rmfile] [-s tcp!server!5356] [-u user]\n"); exits("usage"); } static int getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey) { int fd = -1, i, n, nr, nw, len; char s[Maxmsg+1]; uchar skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe; AESstate aes; DigestState *sha; if(strchr(gf, '/') != nil){ fprint(2, "secstore: simple filenames, not paths like %s\n", gf); return -1; } memset(&aes, 0, sizeof aes); snprint(s, Maxmsg, "GET %s\n", gf); conn->write(conn, (uchar*)s, strlen(s)); /* get file size */ s[0] = '\0'; bufw = bufe = nil; if(readstr(conn, s) < 0){ fprint(2, "secstore: remote: %s\n", s); return -1; } len = atoi(s); if(len == -1){ fprint(2, "secstore: remote file %s does not exist\n", gf); return -1; }else if(len == -3){ fprint(2, "secstore: implausible filesize for %s\n", gf); return -1; }else if(len < 0){ fprint(2, "secstore: GET refused for %s\n", gf); return -1; } if(buf != nil){ *buflen = len - AESbsize - CHK; *buf = bufw = emalloc(len); bufe = bufw + len; } /* directory listing */ if(strcmp(gf,".")==0){ if(buf != nil) *buflen = len; for(i=0; i < len; i += n){ if((n = conn->read(conn, (uchar*)s, Maxmsg)) <= 0){ fprint(2, "secstore: empty file chunk\n"); return -1; } if(buf == nil) write(1, s, n); else memmove(*buf + i, s, n); } return 0; } /* * conn is already encrypted against wiretappers, but gf is also * encrypted against server breakin. */ if(buf == nil && (fd = create(gf, OWRITE, 0600)) < 0){ fprint(2, "secstore: can't open %s: %r\n", gf); return -1; } ibr = ibw = ib; for(nr=0; nr < len;){ if((n = conn->read(conn, ibw, Maxmsg)) <= 0){ fprint(2, "secstore: empty file chunk n=%d nr=%d len=%d: %r\n", n, nr, len); return -1; } nr += n; ibw += n; if(!aes.setup){ /* first time, read 16 byte IV */ if(n < AESbsize){ fprint(2, "secstore: no IV in file\n"); return -1; } sha = sha1((uchar*)"aescbc file", 11, nil, nil); sha1(key, nkey, skey, sha); setupAESstate(&aes, skey, AESbsize, ibr); memset(skey, 0, sizeof skey); ibr += AESbsize; n -= AESbsize; } aesCBCdecrypt(ibw-n, n, &aes); n = ibw - ibr - CHK; if(n > 0){ if(buf == nil){ nw = write(fd, ibr, n); if(nw != n){ fprint(2, "secstore: write error on %s", gf); return -1; } }else{ assert(bufw + n <= bufe); memmove(bufw, ibr, n); bufw += n; } ibr += n; } memmove(ib, ibr, ibw-ibr); ibw = ib + (ibw-ibr); ibr = ib; } if(buf == nil) close(fd); n = ibw-ibr; if(n != CHK || memcmp(ib, "XXXXXXXXXXXXXXXX", CHK) != 0){ fprint(2, "secstore: decrypted file failed to authenticate!\n"); return -1; } return 0; } /* * This sends a file to the secstore disk that can, in an emergency, be * decrypted by the program aescbc.c. */ static int putfile(SConn *conn, char *pf, uchar *buf, ulong len, uchar *key, int nkey) { int i, n, fd, ivo, bufi, done; char s[Maxmsg]; uchar skey[SHA1dlen], b[CHK+Maxmsg], IV[AESbsize]; AESstate aes; DigestState *sha; /* create initialization vector */ srand(time(0)); /* doesn't need to be unpredictable */ for(i=0; iwrite(conn, (uchar*)s, strlen(s)); if(buf == nil){ /* get file size */ if((fd = open(pf, OREAD)) < 0){ fprint(2, "secstore: can't open %s: %r\n", pf); return -1; } len = seek(fd, 0, 2); seek(fd, 0, 0); } else fd = -1; if(len > MAXFILESIZE){ fprint(2, "secstore: implausible filesize %ld for %s\n", len, pf); return -1; } /* send file size */ snprint(s, Maxmsg, "%ld", len + AESbsize + CHK); conn->write(conn, (uchar*)s, strlen(s)); /* send IV and file+XXXXX in Maxmsg chunks */ ivo = AESbsize; bufi = 0; memcpy(b, IV, ivo); for(done = 0; !done; ){ if(buf == nil){ n = read(fd, b+ivo, Maxmsg-ivo); if(n < 0){ fprint(2, "secstore: read error on %s: %r\n", pf); return -1; } }else{ if((n = len - bufi) > Maxmsg-ivo) n = Maxmsg-ivo; memcpy(b+ivo, buf+bufi, n); bufi += n; } n += ivo; ivo = 0; if(n < Maxmsg){ /* EOF on input; append XX... */ memset(b+n, 'X', CHK); n += CHK; /* might push n>Maxmsg */ done = 1; } aesCBCencrypt(b, n, &aes); if(n > Maxmsg){ assert(done==1); conn->write(conn, b, Maxmsg); n -= Maxmsg; memmove(b, b+Maxmsg, n); } conn->write(conn, b, n); } if(buf == nil) close(fd); fprint(2, "secstore: saved %ld bytes\n", len); return 0; } static int removefile(SConn *conn, char *rf) { char buf[Maxmsg]; if(strchr(rf, '/') != nil){ fprint(2, "secstore: simple filenames, not paths like %s\n", rf); return -1; } snprint(buf, Maxmsg, "RM %s\n", rf); conn->write(conn, (uchar*)buf, strlen(buf)); return 0; } static int cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf) { ulong len; int rv = -1; uchar *memfile, *memcur, *memnext; while(*gf != nil){ if(verbose) fprint(2, "get %s\n", *gf); if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len, (uchar*)c->pass, c->passlen) < 0) goto Out; if(*Gflag){ /* write 1 line at a time, as required by /mnt/factotum/ctl */ memcur = memfile; while(len>0){ memnext = (uchar*)strchr((char*)memcur, '\n'); if(memnext){ write(1, memcur, memnext-memcur+1); len -= memnext-memcur+1; memcur = memnext+1; }else{ write(1, memcur, len); break; } } free(memfile); } gf++; Gflag++; } while(*pf != nil){ if(verbose) fprint(2, "put %s\n", *pf); if(putfile(c->conn, *pf, nil, 0, (uchar*)c->pass, c->passlen) < 0) goto Out; pf++; } while(*rf != nil){ if(verbose) fprint(2, "rm %s\n", *rf); if(removefile(c->conn, *rf) < 0) goto Out; rf++; } c->conn->write(c->conn, (uchar*)"BYE", 3); rv = 0; Out: c->conn->free(c->conn); return rv; } static int chpasswd(AuthConn *c, char *id) { int rv = -1, newpasslen = 0; ulong len; uchar *memfile; char *newpass, *passck, *list, *cur, *next, *hexHi; char *f[8], prompt[128]; mpint *H, *Hi; H = mpnew(0); Hi = mpnew(0); /* changing our password is vulnerable to connection failure */ for(;;){ snprint(prompt, sizeof(prompt), "new password for %s: ", id); newpass = getpassm(prompt); if(newpass == nil) goto Out; if(strlen(newpass) >= 7) break; else if(strlen(newpass) == 0){ fprint(2, "!password change aborted\n"); goto Out; } print("!password must be at least 7 characters\n"); } newpasslen = strlen(newpass); snprint(prompt, sizeof(prompt), "retype password: "); passck = getpassm(prompt); if(passck == nil){ fprint(2, "secstore: getpassm failed\n"); goto Out; } if(strcmp(passck, newpass) != 0){ fprint(2, "secstore: passwords didn't match\n"); goto Out; } c->conn->write(c->conn, (uchar*)"CHPASS", strlen("CHPASS")); hexHi = PAK_Hi(id, newpass, H, Hi); c->conn->write(c->conn, (uchar*)hexHi, strlen(hexHi)); free(hexHi); mpfree(H); mpfree(Hi); if(getfile(c->conn, ".", (uchar **) &list, &len, nil, 0) < 0){ fprint(2, "secstore: directory listing failed.\n"); goto Out; } /* Loop over files and reencrypt them; try to keep going after error */ for(cur=list; (next=strchr(cur, '\n')) != nil; cur=next+1){ *next = '\0'; if(tokenize(cur, f, nelem(f))< 1) break; fprint(2, "secstore: reencrypting '%s'\n", f[0]); if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass, c->passlen) < 0){ fprint(2, "secstore: getfile of '%s' failed\n", f[0]); continue; } if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass, newpasslen) < 0) fprint(2, "secstore: putfile of '%s' failed\n", f[0]); free(memfile); } free(list); c->conn->write(c->conn, (uchar*)"BYE", 3); rv = 0; Out: if(newpass != nil){ memset(newpass, 0, newpasslen); free(newpass); } c->conn->free(c->conn); return rv; } static AuthConn* login(char *id, char *dest, int pass_stdin, int pass_nvram) { int fd, n, ntry = 0; char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass; AuthConn *c; if(dest == nil) sysfatal("tried to login with nil dest"); c = emalloc(sizeof(*c)); if(pass_nvram){ if(readnvram(&nvr, 0) < 0) exits("readnvram: %r"); strecpy(c->pass, c->pass+sizeof c->pass, nvr.config); } if(pass_stdin){ n = readn(0, s, Maxmsg-2); /* so len(PINSTA)pass, c->pass+sizeof c->pass, s); } for(;;){ if(verbose) fprint(2, "dialing %s\n", dest); if((fd = dial(dest, nil, nil, nil)) < 0){ fprint(2, "secstore: can't dial %s\n", dest); free(c); return nil; } if((c->conn = newSConn(fd)) == nil){ free(c); return nil; } ntry++; if(!pass_stdin && !pass_nvram){ pass = getpassm("secstore password: "); if(strlen(pass) >= sizeof c->pass){ fprint(2, "secstore: password too long, skipping secstore login\n"); exits("password too long"); } strcpy(c->pass, pass); memset(pass, 0, strlen(pass)); free(pass); } if(c->pass[0]==0){ fprint(2, "secstore: null password, skipping secstore login\n"); exits("no password"); } if(PAKclient(c->conn, id, c->pass, &S) >= 0) break; c->conn->free(c->conn); if(pass_stdin) exits("invalid password on standard input"); if(pass_nvram) exits("invalid password in nvram"); /* and let user try retyping the password */ if(ntry==3) fprint(2, "Enter an empty password to quit.\n"); } c->passlen = strlen(c->pass); fprint(2, "%s\n", S); free(S); if(readstr(c->conn, s) < 0){ c->conn->free(c->conn); free(c); return nil; } if(strcmp(s, "STA") == 0){ long sn; if(pass_stdin){ if(PINSTA) strncpy(s+3, PINSTA, sizeof s - 3); else exits("missing PIN+SecureID on standard input"); free(PINSTA); }else{ pass = getpassm("STA PIN+SecureID: "); strncpy(s+3, pass, sizeof s - 4); memset(pass, 0, strlen(pass)); free(pass); } sn = strlen(s+3); if(verbose) fprint(2, "%ld\n", sn); c->conn->write(c->conn, (uchar*)s, sn+3); readstr(c->conn, s); } if(strcmp(s, "OK") != 0){ fprint(2, "%s\n", s); c->conn->free(c->conn); free(c); return nil; } return c; } void main(int argc, char **argv) { int chpass = 0, pass_stdin = 0, pass_nvram = 0, rc; int ngfile = 0, npfile = 0, nrfile = 0, Gflag[MAXFILES+1]; char *serve, *tcpserve, *user; char *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES]; AuthConn *c; serve = "$auth"; user = getuser(); memset(Gflag, 0, sizeof Gflag); ARGBEGIN{ case 'c': chpass = 1; break; case 'G': Gflag[ngfile]++; /* fall through */ case 'g': if(ngfile >= MAXFILES) exits("too many gfiles"); gfile[ngfile++] = EARGF(usage()); break; case 'i': pass_stdin = 1; break; case 'n': pass_nvram = 1; break; case 'p': if(npfile >= MAXFILES) exits("too many pfiles"); pfile[npfile++] = EARGF(usage()); break; case 'r': if(nrfile >= MAXFILES) exits("too many rfiles"); rfile[nrfile++] = EARGF(usage()); break; case 's': serve = EARGF(usage()); break; case 'u': user = EARGF(usage()); break; case 'v': verbose++; break; default: usage(); break; }ARGEND; gfile[ngfile] = nil; pfile[npfile] = nil; rfile[nrfile] = nil; if(argc!=0 || user==nil) usage(); if(chpass && (ngfile || npfile || nrfile)){ fprint(2, "secstore: Get, put, and remove invalid with password change.\n"); exits("usage"); } rc = strlen(serve) + sizeof "tcp!!99990"; tcpserve = emalloc(rc); if(strchr(serve,'!')) strcpy(tcpserve, serve); else snprint(tcpserve, rc, "tcp!%s!5356", serve); c = login(user, tcpserve, pass_stdin, pass_nvram); free(tcpserve); if(c == nil) sysfatal("secstore authentication failed"); if(chpass) rc = chpasswd(c, user); else rc = cmd(c, gfile, Gflag, pfile, rfile); if(rc < 0) sysfatal("secstore cmd failed"); exits(""); }