aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/auth/secstore/secstore.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/auth/secstore/secstore.c')
-rw-r--r--src/cmd/auth/secstore/secstore.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/src/cmd/auth/secstore/secstore.c b/src/cmd/auth/secstore/secstore.c
new file mode 100644
index 00000000..864aa88d
--- /dev/null
+++ b/src/cmd/auth/secstore/secstore.c
@@ -0,0 +1,585 @@
+/* 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;
+char *SECSTORE_DIR;
+
+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 = readcons(prompt, nil, 1);
+ 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 = readcons(prompt, nil, 1);
+ if(passck == nil){
+ fprint(2, "readcons 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 = readcons("secstore password", nil, 1);
+ if(pass == nil)
+ pass = estrdup("");
+ 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, "server: %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 = readcons("STA PIN+SecureID", nil, 1);
+ if(pass == nil)
+ pass = estrdup("");
+ 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 = getuser();
+ 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;
+}
+