commit a812ae4b4392cade3321da1a146a231ff88db3b7 from: rsc date: Mon Dec 27 19:36:51 2004 UTC secstore from ehg commit - 73bfbacb2409b10f45fd080891a95de29cafe4a0 commit + a812ae4b4392cade3321da1a146a231ff88db3b7 blob - /dev/null blob + 7a8654acb3e8f36fbf2d1152c0a10c05ccbaa1cc (mode 644) --- /dev/null +++ src/cmd/secstore/SConn.c @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include "SConn.h" + +extern int verbose; + +typedef struct ConnState { + uchar secret[SHA1dlen]; + ulong seqno; + RC4state rc4; +} ConnState; + +typedef struct SS{ + int fd; // file descriptor for read/write of encrypted data + int alg; // if nonzero, "alg sha rc4_128" + ConnState in, out; +} SS; + +static int +SC_secret(SConn *conn, uchar *sigma, int direction) +{ + SS *ss = (SS*)(conn->chan); + int nsigma = conn->secretlen; + + if(direction != 0){ + hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->out.secret, nil); + hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->in.secret, nil); + }else{ + hmac_sha1(sigma, nsigma, (uchar*)"two", 3, ss->out.secret, nil); + hmac_sha1(sigma, nsigma, (uchar*)"one", 3, ss->in.secret, nil); + } + setupRC4state(&ss->in.rc4, ss->in.secret, 16); // restrict to 128 bits + setupRC4state(&ss->out.rc4, ss->out.secret, 16); + ss->alg = 1; + return 0; +} + +static void +hash(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen]) +{ + DigestState sha; + uchar seq[4]; + + seq[0] = seqno>>24; + seq[1] = seqno>>16; + seq[2] = seqno>>8; + seq[3] = seqno; + memset(&sha, 0, sizeof sha); + sha1(secret, SHA1dlen, nil, &sha); + sha1(data, len, nil, &sha); + sha1(seq, 4, d, &sha); +} + +static int +verify(uchar secret[SHA1dlen], uchar *data, int len, int seqno, uchar d[SHA1dlen]) +{ + DigestState sha; + uchar seq[4]; + uchar digest[SHA1dlen]; + + seq[0] = seqno>>24; + seq[1] = seqno>>16; + seq[2] = seqno>>8; + seq[3] = seqno; + memset(&sha, 0, sizeof sha); + sha1(secret, SHA1dlen, nil, &sha); + sha1(data, len, nil, &sha); + sha1(seq, 4, digest, &sha); + return memcmp(d, digest, SHA1dlen); +} + +static int +SC_read(SConn *conn, uchar *buf, int n) +{ + SS *ss = (SS*)(conn->chan); + uchar count[2], digest[SHA1dlen]; + int len, nr; + + if(read(ss->fd, count, 2) != 2 || (count[0]&0x80) == 0){ + snprint((char*)buf,n,"!SC_read invalid count"); + return -1; + } + len = (count[0]&0x7f)<<8 | count[1]; // SSL-style count; no pad + if(ss->alg){ + len -= SHA1dlen; + if(len <= 0 || readn(ss->fd, digest, SHA1dlen) != SHA1dlen){ + snprint((char*)buf,n,"!SC_read missing sha1"); + return -1; + } + if(len > n || readn(ss->fd, buf, len) != len){ + snprint((char*)buf,n,"!SC_read missing data"); + return -1; + } + rc4(&ss->in.rc4, digest, SHA1dlen); + rc4(&ss->in.rc4, buf, len); + if(verify(ss->in.secret, buf, len, ss->in.seqno, digest) != 0){ + snprint((char*)buf,n,"!SC_read integrity check failed"); + return -1; + } + }else{ + if(len <= 0 || len > n){ + snprint((char*)buf,n,"!SC_read implausible record length"); + return -1; + } + if( (nr = readn(ss->fd, buf, len)) != len){ + snprint((char*)buf,n,"!SC_read expected %d bytes, but got %d", len, nr); + return -1; + } + } + ss->in.seqno++; + return len; +} + +static int +SC_write(SConn *conn, uchar *buf, int n) +{ + SS *ss = (SS*)(conn->chan); + uchar count[2], digest[SHA1dlen], enc[Maxmsg+1]; + int len; + + if(n <= 0 || n > Maxmsg+1){ + werrstr("!SC_write invalid n %d", n); + return -1; + } + len = n; + if(ss->alg) + len += SHA1dlen; + count[0] = 0x80 | len>>8; + count[1] = len; + if(write(ss->fd, count, 2) != 2){ + werrstr("!SC_write invalid count"); + return -1; + } + if(ss->alg){ + hash(ss->out.secret, buf, n, ss->out.seqno, digest); + rc4(&ss->out.rc4, digest, SHA1dlen); + memcpy(enc, buf, n); + rc4(&ss->out.rc4, enc, n); + if(write(ss->fd, digest, SHA1dlen) != SHA1dlen || + write(ss->fd, enc, n) != n){ + werrstr("!SC_write error on send"); + return -1; + } + }else{ + if(write(ss->fd, buf, n) != n){ + werrstr("!SC_write error on send"); + return -1; + } + } + ss->out.seqno++; + return n; +} + +static void +SC_free(SConn *conn) +{ + SS *ss = (SS*)(conn->chan); + + close(ss->fd); + free(ss); + free(conn); +} + +SConn* +newSConn(int fd) +{ + SS *ss; + SConn *conn; + + if(fd < 0) + return nil; + ss = (SS*)emalloc(sizeof(*ss)); + conn = (SConn*)emalloc(sizeof(*conn)); + ss->fd = fd; + ss->alg = 0; + conn->chan = (void*)ss; + conn->secretlen = SHA1dlen; + conn->free = SC_free; + conn->secret = SC_secret; + conn->read = SC_read; + conn->write = SC_write; + return conn; +} + +void +writerr(SConn *conn, char *s) +{ + char buf[Maxmsg]; + + snprint(buf, Maxmsg, "!%s", s); + conn->write(conn, (uchar*)buf, strlen(buf)); +} + +int +readstr(SConn *conn, char *s) +{ + int n; + + n = conn->read(conn, (uchar*)s, Maxmsg); + if(n >= 0){ + s[n] = 0; + if(s[0] == '!'){ + memmove(s, s+1, n); + n = -1; + } + }else{ + strcpy(s, "read error"); + } + return n; +} + blob - /dev/null blob + 9a428d833b1ac5aa6b7e8be04252bf562fc5a9fc (mode 644) --- /dev/null +++ src/cmd/secstore/SConn.h @@ -0,0 +1,26 @@ +// delimited, authenticated, encrypted connection +enum{ Maxmsg=4096 }; // messages > Maxmsg bytes are truncated +typedef struct SConn SConn; + +extern SConn* newSConn(int); // arg is open file descriptor +struct SConn{ + void *chan; + int secretlen; + int (*secret)(SConn*, uchar*, int);// + int (*read)(SConn*, uchar*, int); // <0 if error; errmess in buffer + int (*write)(SConn*, uchar*, int); + void (*free)(SConn*); // also closes file descriptor +}; +// secret(s,b,dir) sets secret for digest, encrypt, using the secretlen +// bytes in b to form keys for the two directions; +// set dir=0 in client, dir=1 in server + +// error convention: write !message in-band +extern void writerr(SConn*, char*); +extern int readstr(SConn*, char*); // call with buf of size Maxmsg+1 + // returns -1 upon error, with error message in buf + +extern void *emalloc(ulong); /* dies on failure; clears memory */ +extern void *erealloc(void *, ulong); +extern char *estrdup(char *); + blob - /dev/null blob + 09e7955db3e73f6a657dfe5c35d93c5a5eb2883c (mode 644) --- /dev/null +++ src/cmd/secstore/aescbc.c @@ -0,0 +1,156 @@ +/* encrypt file by writing + v2hdr, + 16byte initialization vector, + AES-CBC(key, random | file), + HMAC_SHA1(md5(key), AES-CBC(random | file)) +*/ +#include +#include +#include +#include +#include + +extern char* getpassm(char*); + +enum{ CHK = 16, BUF = 4096 }; + +uchar v2hdr[AESbsize+1] = "AES CBC SHA1 2\n"; +Biobuf bin; +Biobuf bout; + +void +safewrite(uchar *buf, int n) +{ + int i = Bwrite(&bout, buf, n); + + if(i == n) + return; + fprint(2, "write error\n"); + exits("write error"); +} + +void +saferead(uchar *buf, int n) +{ + int i = Bread(&bin, buf, n); + + if(i == n) + return; + fprint(2, "read error\n"); + exits("read error"); +} + +int +main(int argc, char **argv) +{ + int encrypt = 0; /* 0=decrypt, 1=encrypt */ + int n, nkey, pass_stdin = 0; + char *pass; + uchar key[AESmaxkey], key2[SHA1dlen]; + uchar buf[BUF+SHA1dlen]; /* assumption: CHK <= SHA1dlen */ + AESstate aes; + DigestState *dstate; + + ARGBEGIN{ + case 'e': + encrypt = 1; + break; + case 'i': + pass_stdin = 1; + break; + }ARGEND; + if(argc!=0){ + fprint(2,"usage: %s -d < cipher.aes > clear.txt\n", argv0); + fprint(2," or: %s -e < clear.txt > cipher.aes\n", argv0); + exits("usage"); + } + Binit(&bin, 0, OREAD); + Binit(&bout, 1, OWRITE); + + if(pass_stdin){ + n = readn(3, buf, (sizeof buf)-1); + if(n < 1) + exits("usage: echo password |[3=1] auth/aescbc -i ..."); + buf[n] = 0; + while(buf[n-1] == '\n') + buf[--n] = 0; + }else{ + pass = getpassm("aescbc key:"); + n = strlen(pass); + if(n >= BUF) + exits("key too long"); + strcpy((char*)buf, pass); + memset(pass, 0, n); + free(pass); + } + if(n <= 0){ + fprint(2,"no key\n"); + exits("key"); + } + dstate = sha1((uchar*)"aescbc file", 11, nil, nil); + sha1(buf, n, key2, dstate); + memcpy(key, key2, 16); + nkey = 16; + md5(key, nkey, key2, 0); /* so even if HMAC_SHA1 is broken, encryption key is protected */ + + if(encrypt){ + safewrite(v2hdr, AESbsize); + genrandom(buf,2*AESbsize); /* CBC is semantically secure if IV is unpredictable. */ + setupAESstate(&aes, key, nkey, buf); /* use first AESbsize bytes as IV */ + aesCBCencrypt(buf+AESbsize, AESbsize, &aes); /* use second AESbsize bytes as initial plaintext */ + safewrite(buf, 2*AESbsize); + dstate = hmac_sha1(buf+AESbsize, AESbsize, key2, MD5dlen, 0, 0); + while(1){ + n = Bread(&bin, buf, BUF); + if(n < 0){ + fprint(2,"read error\n"); + exits("read error"); + } + aesCBCencrypt(buf, n, &aes); + safewrite(buf, n); + dstate = hmac_sha1(buf, n, key2, MD5dlen, 0, dstate); + if(n < BUF) + break; /* EOF */ + } + hmac_sha1(0, 0, key2, MD5dlen, buf, dstate); + safewrite(buf, SHA1dlen); + }else{ /* decrypt */ + saferead(buf, AESbsize); + if(memcmp(buf, v2hdr, AESbsize) == 0){ + saferead(buf, 2*AESbsize); /* read IV and random initial plaintext */ + setupAESstate(&aes, key, nkey, buf); + dstate = hmac_sha1(buf+AESbsize, AESbsize, key2, MD5dlen, 0, 0); + aesCBCdecrypt(buf+AESbsize, AESbsize, &aes); + saferead(buf, SHA1dlen); + while((n = Bread(&bin, buf+SHA1dlen, BUF)) > 0){ + dstate = hmac_sha1(buf, n, key2, MD5dlen, 0, dstate); + aesCBCdecrypt(buf, n, &aes); + safewrite(buf, n); + memmove(buf, buf+n, SHA1dlen); /* these bytes are not yet decrypted */ + } + hmac_sha1(0, 0, key2, MD5dlen, buf+SHA1dlen, dstate); + if(memcmp(buf, buf+SHA1dlen, SHA1dlen) != 0){ + fprint(2,"decrypted file failed to authenticate\n"); + exits("decrypted file failed to authenticate"); + } + }else{ /* compatibility with past mistake */ + // if file was encrypted with bad aescbc use this: + // memset(key, 0, AESmaxkey); + // else assume we're decrypting secstore files + setupAESstate(&aes, key, AESbsize, buf); + saferead(buf, CHK); + aesCBCdecrypt(buf, CHK, &aes); + while((n = Bread(&bin, buf+CHK, BUF)) > 0){ + aesCBCdecrypt(buf+CHK, n, &aes); + safewrite(buf, n); + memmove(buf, buf+n, CHK); + } + if(memcmp(buf, "XXXXXXXXXXXXXXXX", CHK) != 0){ + fprint(2,"decrypted file failed to authenticate\n"); + exits("decrypted file failed to authenticate"); + } + } + } + exits(""); + return 1; /* gcc */ +} blob - /dev/null blob + 0af9c410ea73683e99c12ed05a52bd8afb7e2640 (mode 644) --- /dev/null +++ src/cmd/secstore/dirls.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include "SConn.h" + +static long +ls(char *p, Dir **dirbuf) +{ + int fd; + long n; + Dir *db; + + if((db = dirstat(p)) == nil || + !(db->qid.type & QTDIR) || + (fd = open(p, OREAD)) < 0 ) + return -1; + free(db); + n = dirreadall(fd, dirbuf); + close(fd); + return n; +} + +static uchar* +sha1file(char *pfx, char *nm) +{ + int n, fd, len; + char *tmp; + uchar buf[8192]; + static uchar digest[SHA1dlen]; + DigestState *s; + + len = strlen(pfx)+1+strlen(nm)+1; + tmp = emalloc(len); + snprint(tmp, len, "%s/%s", pfx, nm); + if((fd = open(tmp, OREAD)) < 0){ + free(tmp); + return nil; + } + free(tmp); + s = nil; + while((n = read(fd, buf, sizeof buf)) > 0) + s = sha1(buf, n, nil, s); + close(fd); + sha1(nil, 0, digest, s); + return digest; +} + +static int +compare(Dir *a, Dir *b) +{ + return strcmp(a->name, b->name); +} + +/* list the (name mtime size sum) of regular, readable files in path */ +char * +dirls(char *path) +{ + char *list, *date, dig[30], buf[128]; + int m, nmwid, lenwid; + long i, n, ndir, len; + Dir *dirbuf; + + if(path==nil || (ndir = ls(path, &dirbuf)) < 0) + return nil; + + qsort(dirbuf, ndir, sizeof dirbuf[0], (int (*)(void *, void *))compare); + for(nmwid=lenwid=i=0; i nmwid) + nmwid = m; + snprint(buf, sizeof(buf), "%ulld", dirbuf[i].length); + if((m = strlen(buf)) > lenwid) + lenwid = m; + } + for(list=nil, len=0, i=0; i +#include +#include +#include +#include "SConn.h" +#include "secstore.h" + +extern int verbose; + +char VERSION[] = "secstore"; +static char *feedback[] = {"alpha","bravo","charlie","delta","echo","foxtrot","golf","hotel"}; + +typedef struct PAKparams{ + mpint *q, *p, *r, *g; +} PAKparams; + +static PAKparams *pak; + +// from seed EB7B6E35F7CD37B511D96C67D6688CC4DD440E1E +static void +initPAKparams(void) +{ + if(pak) + return; + pak = (PAKparams*)emalloc(sizeof(*pak)); + pak->q = strtomp("E0F0EF284E10796C5A2A511E94748BA03C795C13", nil, 16, nil); + pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CEC8CBB" + "DB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384224567A86" + "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE9C48F96D9" + "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", + nil, 16, nil); + pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332B3241" + "CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039F17A32A7E" + "887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F266895C67D" + "21C4656848614D888A4", nil, 16, nil); + pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D23271734" + "44ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F242AB94CD" + "410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E218E7819734" + "E3E2A6E0BAE08B14258F8C03CC1B30E0DDADFCF7CEDF0727684D3D255F1", + nil, 16, nil); +} + +// H = (sha(ver,C,sha(passphrase)))^r mod p, +// a hash function expensive to attack by brute force. +static void +longhash(char *ver, char *C, uchar *passwd, mpint *H) +{ + uchar *Cp; + int i, n, nver, nC; + uchar buf[140], key[1]; + + nver = strlen(ver); + nC = strlen(C); + n = nver + nC + SHA1dlen; + Cp = (uchar*)emalloc(n); + memmove(Cp, ver, nver); + memmove(Cp+nver, C, nC); + memmove(Cp+nver+nC, passwd, SHA1dlen); + for(i = 0; i < 7; i++){ + key[0] = 'A'+i; + hmac_sha1(Cp, n, key, sizeof key, buf+i*SHA1dlen, nil); + } + memset(Cp, 0, n); + free(Cp); + betomp(buf, sizeof buf, H); + mpmod(H, pak->p, H); + mpexp(H, pak->r, pak->p, H); +} + +// Hi = H^-1 mod p +char * +PAK_Hi(char *C, char *passphrase, mpint *H, mpint *Hi) +{ + uchar passhash[SHA1dlen]; + + sha1((uchar *)passphrase, strlen(passphrase), passhash, nil); + initPAKparams(); + longhash(VERSION, C, passhash, H); + mpinvert(H, pak->p, Hi); + return mptoa(Hi, 64, nil, 0); +} + +// another, faster, hash function for each party to +// confirm that the other has the right secrets. +static void +shorthash(char *mess, char *C, char *S, char *m, char *mu, char *sigma, char *Hi, uchar *digest) +{ + SHA1state *state; + + state = sha1((uchar*)mess, strlen(mess), 0, 0); + state = sha1((uchar*)C, strlen(C), 0, state); + state = sha1((uchar*)S, strlen(S), 0, state); + state = sha1((uchar*)m, strlen(m), 0, state); + state = sha1((uchar*)mu, strlen(mu), 0, state); + state = sha1((uchar*)sigma, strlen(sigma), 0, state); + state = sha1((uchar*)Hi, strlen(Hi), 0, state); + state = sha1((uchar*)mess, strlen(mess), 0, state); + state = sha1((uchar*)C, strlen(C), 0, state); + state = sha1((uchar*)S, strlen(S), 0, state); + state = sha1((uchar*)m, strlen(m), 0, state); + state = sha1((uchar*)mu, strlen(mu), 0, state); + state = sha1((uchar*)sigma, strlen(sigma), 0, state); + sha1((uchar*)Hi, strlen(Hi), digest, state); +} + +// On input, conn provides an open channel to the server; +// C is the name this client calls itself; +// pass is the user's passphrase +// On output, session secret has been set in conn +// (unless return code is negative, which means failure). +// If pS is not nil, it is set to the (alloc'd) name the server calls itself. +int +PAKclient(SConn *conn, char *C, char *pass, char **pS) +{ + char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *hexHi; + char kc[2*SHA1dlen+1]; + uchar digest[SHA1dlen]; + int rc = -1, n; + mpint *x, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0); + mpint *H = mpnew(0), *Hi = mpnew(0); + + hexHi = PAK_Hi(C, pass, H, Hi); + if(verbose) + fprint(2,"%s\n", feedback[H->p[0]&0x7]); // provide a clue to catch typos + + // random 1<=x<=q-1; send C, m=g**x H + x = mprand(240, genrandom, nil); + mpmod(x, pak->q, x); + if(mpcmp(x, mpzero) == 0) + mpassign(mpone, x); + mpexp(pak->g, x, pak->p, m); + mpmul(m, H, m); + mpmod(m, pak->p, m); + hexm = mptoa(m, 64, nil, 0); + mess = (char*)emalloc(2*Maxmsg+2); + mess2 = mess+Maxmsg+1; + snprint(mess, Maxmsg, "%s\tPAK\nC=%s\nm=%s\n", VERSION, C, hexm); + conn->write(conn, (uchar*)mess, strlen(mess)); + + // recv g**y, S, check hash1(g**xy) + if(readstr(conn, mess) < 0){ + fprint(2, "error: %s\n", mess); + writerr(conn, "couldn't read g**y"); + goto done; + } + eol = strchr(mess, '\n'); + if(strncmp("mu=", mess, 3) != 0 || !eol || strncmp("\nk=", eol, 3) != 0){ + writerr(conn, "verifier syntax error"); + goto done; + } + hexmu = mess+3; + *eol = 0; + ks = eol+3; + eol = strchr(ks, '\n'); + if(!eol || strncmp("\nS=", eol, 3) != 0){ + writerr(conn, "verifier syntax error for secstore 1.0"); + goto done; + } + *eol = 0; + S = eol+3; + eol = strchr(S, '\n'); + if(!eol){ + writerr(conn, "verifier syntax error for secstore 1.0"); + goto done; + } + *eol = 0; + if(pS) + *pS = estrdup(S); + strtomp(hexmu, nil, 64, mu); + mpexp(mu, x, pak->p, sigma); + hexsigma = mptoa(sigma, 64, nil, 0); + shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(kc, sizeof kc, digest, SHA1dlen); + if(strcmp(ks, kc) != 0){ + writerr(conn, "verifier didn't match"); + goto done; + } + + // send hash2(g**xy) + shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(kc, sizeof kc, digest, SHA1dlen); + snprint(mess2, Maxmsg, "k'=%s\n", kc); + conn->write(conn, (uchar*)mess2, strlen(mess2)); + + // set session key + shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest); + memset(hexsigma, 0, strlen(hexsigma)); + n = conn->secret(conn, digest, 0); + memset(digest, 0, SHA1dlen); + if(n < 0){ + writerr(conn, "can't set secret"); + goto done; + } + + rc = 0; +done: + mpfree(x); + mpfree(sigma); + mpfree(mu); + mpfree(m); + mpfree(Hi); + mpfree(H); + free(hexsigma); + free(hexHi); + free(hexm); + free(mess); + return rc; +} + +// On input, +// mess contains first message; +// name is name this server should call itself. +// On output, session secret has been set in conn; +// if pw!=nil, then *pw points to PW struct for authenticated user. +// returns -1 if error +int +PAKserver(SConn *conn, char *S, char *mess, PW **pwp) +{ + int rc = -1, n; + char mess2[Maxmsg+1], *eol; + char *C, ks[41], *kc, *hexm, *hexmu = nil, *hexsigma = nil, *hexHi = nil; + uchar digest[SHA1dlen]; + mpint *H = mpnew(0), *Hi = mpnew(0); + mpint *y = nil, *m = mpnew(0), *mu = mpnew(0), *sigma = mpnew(0); + PW *pw = nil; + + // secstore version and algorithm + snprint(mess2,Maxmsg,"%s\tPAK\n", VERSION); + n = strlen(mess2); + if(strncmp(mess,mess2,n) != 0){ + writerr(conn, "protocol should start with ver alg"); + return -1; + } + mess += n; + initPAKparams(); + + // parse first message into C, m + eol = strchr(mess, '\n'); + if(strncmp("C=", mess, 2) != 0 || !eol){ + fprint(2,"mess[1]=%s\n", mess); + writerr(conn, "PAK version mismatch"); + goto done; + } + C = mess+2; + *eol = 0; + hexm = eol+3; + eol = strchr(hexm, '\n'); + if(strncmp("m=", hexm-2, 2) != 0 || !eol){ + writerr(conn, "PAK version mismatch"); + goto done; + } + *eol = 0; + strtomp(hexm, nil, 64, m); + mpmod(m, pak->p, m); + + // lookup client + if((pw = getPW(C,0)) == nil) { + snprint(mess2, sizeof mess2, "%r"); + writerr(conn, mess2); + goto done; + } + if(mpcmp(m, mpzero) == 0) { + writerr(conn, "account exists"); + freePW(pw); + pw = nil; + goto done; + } + hexHi = mptoa(pw->Hi, 64, nil, 0); + + // random y, mu=g**y, sigma=g**xy + y = mprand(240, genrandom, nil); + mpmod(y, pak->q, y); + if(mpcmp(y, mpzero) == 0){ + mpassign(mpone, y); + } + mpexp(pak->g, y, pak->p, mu); + mpmul(m, pw->Hi, m); + mpmod(m, pak->p, m); + mpexp(m, y, pak->p, sigma); + + // send g**y, hash1(g**xy) + hexmu = mptoa(mu, 64, nil, 0); + hexsigma = mptoa(sigma, 64, nil, 0); + shorthash("server", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(ks, sizeof ks, digest, SHA1dlen); + snprint(mess2, sizeof mess2, "mu=%s\nk=%s\nS=%s\n", hexmu, ks, S); + conn->write(conn, (uchar*)mess2, strlen(mess2)); + + // recv hash2(g**xy) + if(readstr(conn, mess2) < 0){ + writerr(conn, "couldn't read verifier"); + goto done; + } + eol = strchr(mess2, '\n'); + if(strncmp("k'=", mess2, 3) != 0 || !eol){ + writerr(conn, "verifier syntax error"); + goto done; + } + kc = mess2+3; + *eol = 0; + shorthash("client", C, S, hexm, hexmu, hexsigma, hexHi, digest); + enc64(ks, sizeof ks, digest, SHA1dlen); + if(strcmp(ks, kc) != 0) { + rc = -2; + goto done; + } + + // set session key + shorthash("session", C, S, hexm, hexmu, hexsigma, hexHi, digest); + n = conn->secret(conn, digest, 1); + if(n < 0){ + writerr(conn, "can't set secret"); + goto done; + } + + rc = 0; +done: + if(rc<0 && pw){ + pw->failed++; + putPW(pw); + } + if(rc==0 && pw && pw->failed>0){ + pw->failed = 0; + putPW(pw); + } + if(pwp) + *pwp = pw; + else + freePW(pw); + free(hexsigma); + free(hexHi); + free(hexmu); + mpfree(y); + mpfree(sigma); + mpfree(mu); + mpfree(m); + mpfree(Hi); + mpfree(H); + return rc; +} + blob - /dev/null blob + aacadd9b184dd688aaebfe8296426a75d7893001 (mode 644) --- /dev/null +++ src/cmd/secstore/password.c @@ -0,0 +1,136 @@ +/* password.c */ +#include +#include +#include +#include +#include +#include "SConn.h" +#include "secstore.h" + +static Biobuf* +openPW(char *id, int mode) +{ + Biobuf *b; + int nfn = strlen(SECSTORE_DIR)+strlen(id)+20; + char *fn = emalloc(nfn); + + snprint(fn, nfn, "%s/who/%s", SECSTORE_DIR, id); + b = Bopen(fn, mode); + free(fn); + return b; +} + +static ulong +mtimePW(char *id) +{ + Dir *d; + int nfn = strlen(SECSTORE_DIR)+strlen(id)+20; + char *fn = emalloc(nfn); + ulong mt; + + snprint(fn, nfn, "%s/who/%s", SECSTORE_DIR, id); + d = dirstat(fn); + free(fn); + mt = d->mtime; + free(d); + return mt; +} + +PW * +getPW(char *id, int dead_or_alive) +{ + uint now = time(0); + Biobuf *bin; + PW *pw; + char *f1, *f2; // fields 1, 2 = attribute, value + + if((bin = openPW(id, OREAD)) == 0){ + id = "FICTITIOUS"; + if((bin = openPW(id, OREAD)) == 0){ + werrstr("account does not exist"); + return nil; + } + } + pw = emalloc(sizeof(*pw)); + pw->id = estrdup(id); + pw->status |= Enabled; + while( (f1 = Brdline(bin, '\n')) != 0){ + f1[Blinelen(bin)-1] = 0; + for(f2 = f1; *f2 && (*f2!=' ') && (*f2!='\t'); f2++){} + if(*f2) + for(*f2++ = 0; *f2 && (*f2==' ' || *f2=='\t'); f2++){} + if(strcmp(f1, "exp") == 0){ + pw->expire = strtoul(f2, 0, 10); + }else if(strcmp(f1, "DISABLED") == 0){ + pw->status &= ~Enabled; + }else if(strcmp(f1, "STA") == 0){ + pw->status |= STA; + }else if(strcmp(f1, "failed") == 0){ + pw->failed = strtoul(f2, 0, 10); + }else if(strcmp(f1, "other") == 0){ + pw->other = estrdup(f2); + }else if(strcmp(f1, "PAK-Hi") == 0){ + pw->Hi = strtomp(f2, nil, 64, nil); + } + } + Bterm(bin); + if(dead_or_alive) + return pw; // return PW entry for editing, whether currently valid or not + if(pw->expire <= now){ + werrstr("account expired"); + freePW(pw); + return nil; + } + if((pw->status & Enabled) == 0){ + werrstr("account disabled"); + freePW(pw); + return nil; + } + if(pw->failed < 10) + return pw; // success + if(now < mtimePW(id)+300){ + werrstr("too many failures; try again in five minutes"); + freePW(pw); + return nil; + } + pw->failed = 0; + putPW(pw); // reset failed-login-counter after five minutes + return pw; +} + +int +putPW(PW *pw) +{ + Biobuf *bout; + char *hexHi; + + if((bout = openPW(pw->id, OWRITE|OTRUNC)) ==0){ + werrstr("can't open PW file"); + return -1; + } + Bprint(bout, "exp %lud\n", pw->expire); + if(!(pw->status & Enabled)) + Bprint(bout, "DISABLED\n"); + if(pw->status & STA) + Bprint(bout, "STA\n"); + if(pw->failed) + Bprint(bout, "failed\t%d\n", pw->failed); + if(pw->other) + Bprint(bout,"other\t%s\n", pw->other); + hexHi = mptoa(pw->Hi, 64, nil, 0); + Bprint(bout, "PAK-Hi\t%s\n", hexHi); + free(hexHi); + return 0; +} + +void +freePW(PW *pw) +{ + if(pw == nil) + return; + free(pw->id); + free(pw->other); + mpfree(pw->Hi); + free(pw); +} + blob - /dev/null blob + 0a361c14adda2b9fe93426d0befc527131cdeb47 (mode 644) --- /dev/null +++ src/cmd/secstore/secstore.c @@ -0,0 +1,580 @@ +/* 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 [-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; iwrite(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)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; +} + blob - /dev/null blob + 1d6d6d34b35e50c78134956bc55f59b671459cc8 (mode 644) --- /dev/null +++ src/cmd/secstore/secstore.h @@ -0,0 +1,30 @@ +enum{ MAXFILESIZE = 10*1024*1024 }; + +enum{// PW status bits + Enabled = (1<<0), + STA = (1<<1), // extra SecurID step +}; + +typedef struct PW { + char *id; // user id + ulong expire; // expiration time (epoch seconds) + ushort status; // Enabled, STA, ... + ushort failed; // number of failed login attempts + char *other; // other information, e.g. sponsor + mpint *Hi; // H(passphrase)^-1 mod p +} PW; + +PW *getPW(char *, int); +int putPW(PW *); +void freePW(PW *); +char* getpassm(const char*); + +// *client: SConn, client name, passphrase +// *server: SConn, (partial) 1st msg, PW entry +// *setpass: Username, hashed passphrase, PW entry +int PAKclient(SConn *, char *, char *, char **); +int PAKserver(SConn *, char *, char *, PW **); +char *PAK_Hi(char *, char *, mpint *, mpint *); + +#define LOG "secstore" +#define SECSTORE_DIR "/adm/secstore" blob - /dev/null blob + d791bc72325fa53d9ca8aff019d4f93771efc8a5 (mode 644) --- /dev/null +++ src/cmd/secstore/util.c @@ -0,0 +1,38 @@ +#include +#include + +void * +emalloc(ulong n) +{ + void *p = malloc(n); + if(p == nil) + sysfatal("emalloc"); + memset(p, 0, n); + return p; +} + +void * +erealloc(void *p, ulong n) +{ + if ((p = realloc(p, n)) == nil) + sysfatal("erealloc"); + return p; +} + +char * +estrdup(char *s) +{ + if ((s = strdup(s)) == nil) + sysfatal("estrdup"); + return s; +} + +char * +getpassm(char *prompt) +{ + char *p = getpass(prompt); + + if(p == nil || (p = strdup(p)) == nil) + sysfatal("getpassm"); + return p; +}