diff options
author | rsc <devnull@localhost> | 2004-12-27 19:36:51 +0000 |
---|---|---|
committer | rsc <devnull@localhost> | 2004-12-27 19:36:51 +0000 |
commit | a812ae4b4392cade3321da1a146a231ff88db3b7 (patch) | |
tree | 5ddcda3efed1d7cf7791faa5f93dc7b33a56a944 /src/cmd/secstore/secstore.c | |
parent | 73bfbacb2409b10f45fd080891a95de29cafe4a0 (diff) | |
download | plan9port-a812ae4b4392cade3321da1a146a231ff88db3b7.tar.gz plan9port-a812ae4b4392cade3321da1a146a231ff88db3b7.tar.bz2 plan9port-a812ae4b4392cade3321da1a146a231ff88db3b7.zip |
secstore from ehg
Diffstat (limited to 'src/cmd/secstore/secstore.c')
-rw-r--r-- | src/cmd/secstore/secstore.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/src/cmd/secstore/secstore.c b/src/cmd/secstore/secstore.c new file mode 100644 index 00000000..0a361c14 --- /dev/null +++ b/src/cmd/secstore/secstore.c @@ -0,0 +1,580 @@ +/* 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 [-cin] [-g getfile] [-p putfile] [-r rmfile] [-s tcp!server!5356] [-u user] [-v]\n"); + exits("usage"); +} + +static int +getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nkey) +{ + int fd = -1; + int 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, '/')){ + fprint(2, "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, "remote: %s\n", s); + return -1; + } + len = atoi(s); + if(len == -1){ + fprint(2, "remote file %s does not exist\n", gf); + return -1; + }else if(len == -3){ + fprint(2, "implausible filesize for %s\n", gf); + return -1; + }else if(len < 0){ + fprint(2, "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, "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, "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, "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, "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, "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,"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; i<AESbsize; i++) + IV[i] = 0xff & rand(); + sha = sha1((uchar*)"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\n", pf); + conn->write(conn, (uchar*)s, strlen(s)); + + if(buf == nil){ + /* get file size */ + if((fd = open(pf, OREAD)) < 0){ + fprint(2, "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, "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, "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, "saved %ld bytes\n", len); + + return 0; +} + +static int +removefile(SConn *conn, char *rf) +{ + char buf[Maxmsg]; + + if(strchr(rf, '/')){ + fprint(2, "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 one 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) +{ + ulong len; + int rv = -1, newpasslen = 0; + mpint *H, *Hi; + uchar *memfile; + char *newpass, *passck; + char *list, *cur, *next, *hexHi; + char *f[8], prompt[128]; + + 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, "getpassmwd failed\n"); + goto Out; + } + if(strcmp(passck, newpass) != 0){ + fprint(2, "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 **)(void*)&list, &len, nil, 0) < 0){ + fprint(2, "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, "reencrypting '%s'\n", f[0]); + if(getfile(c->conn, f[0], &memfile, &len, (uchar*)c->pass, c->passlen) < 0){ + fprint(2, "getfile of '%s' failed\n", f[0]); + continue; + } + if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass, newpasslen) < 0) + fprint(2, "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) +{ + AuthConn *c; + int fd, n, ntry = 0; + char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass; + + if(dest == nil){ + fprint(2, "tried to login with nil dest\n"); + exits("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); + } + while(1){ + if(verbose) + fprint(2, "dialing %s\n", dest); + if((fd = dial(dest, nil, nil, nil)) < 0){ + fprint(2, "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, "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, "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; +} + +int +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 *gfile[MAXFILES], *pfile[MAXFILES], *rfile[MAXFILES]; + char *serve, *tcpserve, *user; + AuthConn *c; + + serve = "$auth"; + user = getenv("USER"); + memset(Gflag, 0, sizeof Gflag); + fmtinstall('B', mpfmt); + fmtinstall('H', encodefmt); + + ARGBEGIN{ + case 'c': + chpass = 1; + break; + case 'G': + Gflag[ngfile]++; + /* fall through */ + case 'g': + if(ngfile >= MAXFILES) + exits("too many gfiles"); + gfile[ngfile++] = ARGF(); + if(gfile[ngfile-1] == nil) + 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++] = ARGF(); + if(pfile[npfile-1] == nil) + usage(); + break; + case 'r': + if(nrfile >= MAXFILES) + exits("too many rfiles"); + rfile[nrfile++] = ARGF(); + if(rfile[nrfile-1] == nil) + 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, "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){ + fprint(2, "secstore authentication failed\n"); + exits("secstore authentication failed"); + } + if(chpass) + rc = chpasswd(c, user); + else + rc = cmd(c, gfile, Gflag, pfile, rfile); + if(rc < 0){ + fprint(2, "secstore cmd failed\n"); + exits("secstore cmd failed"); + } + exits(""); + return 0; +} + |