123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- /*
- * This file is part of the UCB release of Plan 9. It is subject to the license
- * terms in the LICENSE file found in the top-level directory of this
- * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
- * part of the UCB release of Plan 9, including this file, may be copied,
- * modified, propagated, or distributed except according to the terms contained
- * in the LICENSE file.
- */
- /* secstore - network login client */
- #include <u.h>
- #include <libc.h>
- #include <mp.h>
- #include <libsec.h>
- #include <authsrv.h>
- #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, uint8_t **buf, uint32_t *buflen,
- uint8_t *key,
- int nkey)
- {
- int fd = -1, i, n, nr, nw, len;
- char s[Maxmsg+1];
- uint8_t skey[SHA1dlen], ib[Maxmsg+CHK], *ibr, *ibw, *bufw, *bufe;
- AESstate aes;
- DigestState *sha;
- memset(&aes, 0, sizeof aes);
- snprint(s, Maxmsg, "GET %s", gf);
- conn->write(conn, (uint8_t*)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, (uint8_t*)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((uint8_t*)"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, uint8_t *buf, uint32_t len, uint8_t *key,
- int nkey)
- {
- int i, n, fd, ivo, bufi, done;
- char s[Maxmsg];
- uint8_t 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; i<AESbsize; i++)
- IV[i] = 0xff & rand();
- sha = sha1((uint8_t*)"aescbc file", 11, nil, nil);
- sha1(key, nkey, skey, sha);
- setupAESstate(&aes, skey, AESbsize, IV);
- memset(skey, 0, sizeof skey);
- snprint(s, Maxmsg, "PUT %s", pf);
- conn->write(conn, (uint8_t*)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, (uint8_t*)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", rf);
- conn->write(conn, (uint8_t*)buf, strlen(buf));
- return 0;
- }
- static int
- cmd(AuthConn *c, char **gf, int *Gflag, char **pf, char **rf)
- {
- uint32_t len;
- int rv = -1;
- uint8_t *memfile, *memcur, *memnext;
- while(*gf != nil){
- if(verbose)
- fprint(2, "get %s\n", *gf);
- if(getfile(c->conn, *gf, *Gflag? &memfile: nil, &len,
- (uint8_t*)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 = (uint8_t*)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, (uint8_t*)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, (uint8_t*)"BYE", 3);
- rv = 0;
- Out:
- c->conn->free(c->conn);
- return rv;
- }
- static int
- chpasswd(AuthConn *c, char *id)
- {
- int rv = -1, newpasslen = 0;
- uint32_t len;
- uint8_t *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, (uint8_t*)"CHPASS", strlen("CHPASS"));
- hexHi = PAK_Hi(id, newpass, H, Hi);
- c->conn->write(c->conn, (uint8_t*)hexHi, strlen(hexHi));
- free(hexHi);
- mpfree(H);
- mpfree(Hi);
- if(getfile(c->conn, ".", (uint8_t **) &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, (uint8_t*)c->pass,
- c->passlen) < 0){
- fprint(2, "secstore: getfile of '%s' failed\n", f[0]);
- continue;
- }
- if(putfile(c->conn, f[0], memfile, len, (uint8_t*)newpass,
- newpasslen) < 0)
- fprint(2, "secstore: putfile of '%s' failed\n", f[0]);
- free(memfile);
- }
- free(list);
- c->conn->write(c->conn, (uint8_t*)"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)<Maxmsg-3 */
- if(n < 1)
- exits("no password on standard input");
- s[n] = 0;
- nl = strchr(s, '\n');
- if(nl){
- *nl++ = 0;
- PINSTA = estrdup(nl);
- nl = strchr(PINSTA, '\n');
- if(nl)
- *nl = 0;
- }
- strecpy(c->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){
- int32_t 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, (uint8_t*)s, sn+3);
- readstr(c->conn, s); /* TODO: check for error? */
- }
- if(strcmp(s, "OK") != 0){
- fprint(2, "%s: %s\n", argv0, 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("");
- }
|