diff options
Diffstat (limited to 'src/cmd/auth/secstore')
-rw-r--r-- | src/cmd/auth/secstore/SConn.c | 213 | ||||
-rw-r--r-- | src/cmd/auth/secstore/SConn.h | 26 | ||||
-rw-r--r-- | src/cmd/auth/secstore/aescbc.c | 156 | ||||
-rw-r--r-- | src/cmd/auth/secstore/dirls.c | 87 | ||||
-rw-r--r-- | src/cmd/auth/secstore/mkfile | 27 | ||||
-rw-r--r-- | src/cmd/auth/secstore/pak.c | 344 | ||||
-rw-r--r-- | src/cmd/auth/secstore/password.c | 136 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secacct.c | 35 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secchk.c | 28 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secstore.c | 585 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secstore.h | 31 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secstored.c | 420 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secureidcheck.c | 446 | ||||
-rw-r--r-- | src/cmd/auth/secstore/secuser.c | 244 | ||||
-rw-r--r-- | src/cmd/auth/secstore/util.c | 28 |
15 files changed, 2806 insertions, 0 deletions
diff --git a/src/cmd/auth/secstore/SConn.c b/src/cmd/auth/secstore/SConn.c new file mode 100644 index 00000000..7a8654ac --- /dev/null +++ b/src/cmd/auth/secstore/SConn.c @@ -0,0 +1,213 @@ +#include <u.h> +#include <libc.h> +#include <mp.h> +#include <libsec.h> +#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; +} + diff --git a/src/cmd/auth/secstore/SConn.h b/src/cmd/auth/secstore/SConn.h new file mode 100644 index 00000000..9a428d83 --- /dev/null +++ b/src/cmd/auth/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 *); + diff --git a/src/cmd/auth/secstore/aescbc.c b/src/cmd/auth/secstore/aescbc.c new file mode 100644 index 00000000..56aeb00b --- /dev/null +++ b/src/cmd/auth/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 <u.h> +#include <libc.h> +#include <bio.h> +#include <mp.h> +#include <libsec.h> + +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 = readcons("aescbc key", nil, 1); + 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 */ +} diff --git a/src/cmd/auth/secstore/dirls.c b/src/cmd/auth/secstore/dirls.c new file mode 100644 index 00000000..b4479413 --- /dev/null +++ b/src/cmd/auth/secstore/dirls.c @@ -0,0 +1,87 @@ +#include <u.h> +#include <libc.h> +#include <mp.h> +#include <libsec.h> +#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 (*)(const void *, const void *))compare); + for(nmwid=lenwid=i=0; i<ndir; i++){ + if((m = strlen(dirbuf[i].name)) > 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<ndir; i++){ + date = ctime(dirbuf[i].mtime); + date[28] = 0; // trim newline + n = snprint(buf, sizeof buf, "%*ulld %s", lenwid, dirbuf[i].length, date+4); + n += enc64(dig, sizeof dig, sha1file(path, dirbuf[i].name), SHA1dlen); + n += nmwid+3+strlen(dirbuf[i].name); + list = erealloc(list, len+n+1); + len += snprint(list+len, n+1, "%-*s\t%s %s\n", nmwid, dirbuf[i].name, buf, dig); + } + free(dirbuf); + return list; +} + diff --git a/src/cmd/auth/secstore/mkfile b/src/cmd/auth/secstore/mkfile new file mode 100644 index 00000000..72986edd --- /dev/null +++ b/src/cmd/auth/secstore/mkfile @@ -0,0 +1,27 @@ +<$PLAN9/src/mkhdr + +BIN=$PLAN9/bin +#CFLAGS=-Fw +HFILES =\ + SConn.h\ + secstore.h\ + +OFILES =\ + pak.$O\ + password.$O\ + SConn.$O\ + util.$O\ + + +TARG=aescbc secstore secstored secuser + +<$PLAN9/src/mkmany + +$O.aescbc: aescbc.$O util.$O + $LD -o $target $prereq $LDFLAGS + +$O.secstored: secstored.$O dirls.$O secureidcheck.$O $OFILES + $LD -o $target $prereq + +$O.secuser: secuser.$O $OFILES + $LD -o $target $prereq diff --git a/src/cmd/auth/secstore/pak.c b/src/cmd/auth/secstore/pak.c new file mode 100644 index 00000000..fb008e0f --- /dev/null +++ b/src/cmd/auth/secstore/pak.c @@ -0,0 +1,344 @@ +// PAK is an encrypted key exchange protocol designed by Philip MacKenzie et al. +// It is patented and use outside Plan 9 requires you get a license. +// (All other EKE protocols are patented as well, by Lucent or others.) +#include <u.h> +#include <libc.h> +#include <mp.h> +#include <libsec.h> +#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; +} + diff --git a/src/cmd/auth/secstore/password.c b/src/cmd/auth/secstore/password.c new file mode 100644 index 00000000..aacadd9b --- /dev/null +++ b/src/cmd/auth/secstore/password.c @@ -0,0 +1,136 @@ +/* password.c */ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <mp.h> +#include <libsec.h> +#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); +} + diff --git a/src/cmd/auth/secstore/secacct.c b/src/cmd/auth/secstore/secacct.c new file mode 100644 index 00000000..4390129a --- /dev/null +++ b/src/cmd/auth/secstore/secacct.c @@ -0,0 +1,35 @@ +#include <u.h> +#include <libc.h> +#include <ip.h> + +int verbose = 1; +static char testmess[] = "__secstore\tPAK\nC=%s\nm=0\n"; + +void +main(int argc, char **argv) +{ + int n, m, fd; + uchar buf[500]; + + if(argc != 2) + exits("usage: secacct userid"); + + n = snprint((char*)buf, sizeof buf, testmess, argv[1]); + hnputs(buf, 0x8000+n-2); + + fd = dial("tcp!ruble.cs.bell-labs.com!5356", 0, 0, 0); + if(fd < 0) + exits("cannot dial ruble"); + if(write(fd, buf, n) != n || readn(fd, buf, 2) != 2) + exits("cannot exchange first round"); + n = ((buf[0]&0x7f)<<8) + buf[1]; + if(n+1 > sizeof buf) + exits("implausibly large count"); + m = readn(fd, buf, n); + close(fd); + if(m != n) + fprint(2,"short read from secstore\n"); + buf[m] = 0; + print("%s\n", (char*)buf); + exits(0); +} diff --git a/src/cmd/auth/secstore/secchk.c b/src/cmd/auth/secstore/secchk.c new file mode 100644 index 00000000..59e26d51 --- /dev/null +++ b/src/cmd/auth/secstore/secchk.c @@ -0,0 +1,28 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> + +extern char* secureidcheck(char *user, char *response); +Ndb *db; + +void +main(int argc, char **argv) +{ + Ndb *db2; + + if(argc!=2){ + fprint(2,"usage %s pinsecurid\n", argv[0]); + exits("usage"); + } + db = ndbopen("/lib/ndb/auth"); + if(db == 0) + syslog(0, "secstore", "no /lib/ndb/auth"); + db2 = ndbopen(0); + if(db2 == 0) + syslog(0, "secstore", "no /lib/ndb/local"); + db = ndbcat(db, db2); + print("user=%s\n", getenv("user")); + print("%s\n", secureidcheck(getenv("user"), argv[1])); + exits(0); +} 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; +} + diff --git a/src/cmd/auth/secstore/secstore.h b/src/cmd/auth/secstore/secstore.h new file mode 100644 index 00000000..dbd2ec9c --- /dev/null +++ b/src/cmd/auth/secstore/secstore.h @@ -0,0 +1,31 @@ +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 *); + +// *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" + +extern char *SECSTORE_DIR; + diff --git a/src/cmd/auth/secstore/secstored.c b/src/cmd/auth/secstore/secstored.c new file mode 100644 index 00000000..58f7459a --- /dev/null +++ b/src/cmd/auth/secstore/secstored.c @@ -0,0 +1,420 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include <mp.h> +#include <libsec.h> +#include "SConn.h" +#include "secstore.h" + +char *SECSTORE_DIR; +char* secureidcheck(char *, char *); // from /sys/src/cmd/auth/ +extern char* dirls(char *path); + +int verbose; +Ndb *db; + +static void +usage(void) +{ + fprint(2, "usage: secstored [-R] [-S servername] [-s tcp!*!5356] [-v] [-x netmtpt]\n"); + exits("usage"); +} + +static int +getdir(SConn *conn, char *id) +{ + char *ls, *s; + uchar *msg; + int n, len; + + s = emalloc(Maxmsg); + snprint(s, Maxmsg, "%s/store/%s", SECSTORE_DIR, id); + + if((ls = dirls(s)) == nil) + len = 0; + else + len = strlen(ls); + + /* send file size */ + snprint(s, Maxmsg, "%d", len); + conn->write(conn, (uchar*)s, strlen(s)); + + /* send directory listing in Maxmsg chunks */ + n = Maxmsg; + msg = (uchar*)ls; + while(len > 0){ + if(len < Maxmsg) + n = len; + conn->write(conn, msg, n); + msg += n; + len -= n; + } + free(s); + free(ls); + return 0; +} + +char * +validatefile(char *f) +{ + char *nl; + + if(f==nil || *f==0) + return nil; + if(nl = strchr(f, '\n')) + *nl = 0; + if(strchr(f,'/') != nil || strcmp(f,"..")==0 || strlen(f) >= 300){ + syslog(0, LOG, "no slashes allowed: %s\n", f); + return nil; + } + return f; +} + +static int +getfile(SConn *conn, char *id, char *gf) +{ + int n, gd, len; + ulong mode; + char *s; + Dir *st; + + if(strcmp(gf,".")==0) + return getdir(conn, id); + + /* send file size */ + s = emalloc(Maxmsg); + snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, gf); + gd = open(s, OREAD); + if(gd < 0){ + syslog(0, LOG, "can't open %s: %r\n", s); + free(s); + conn->write(conn, (uchar*)"-1", 2); + return -1; + } + st = dirfstat(gd); + if(st == nil){ + syslog(0, LOG, "can't stat %s: %r\n", s); + free(s); + conn->write(conn, (uchar*)"-1", 2); + return -1; + } + mode = st->mode; + len = st->length; + free(st); + if(mode & DMDIR) { + syslog(0, LOG, "%s should be a plain file, not a directory\n", s); + free(s); + conn->write(conn, (uchar*)"-1", 2); + return -1; + } + if(len < 0 || len > MAXFILESIZE){ + syslog(0, LOG, "implausible filesize %d for %s\n", len, gf); + free(s); + conn->write(conn, (uchar*)"-3", 2); + return -1; + } + snprint(s, Maxmsg, "%d", len); + conn->write(conn, (uchar*)s, strlen(s)); + + /* send file in Maxmsg chunks */ + while(len > 0){ + n = read(gd, s, Maxmsg); + if(n <= 0){ + syslog(0, LOG, "read error on %s: %r\n", gf); + free(s); + return -1; + } + conn->write(conn, (uchar*)s, n); + len -= n; + } + close(gd); + free(s); + return 0; +} + +static int +putfile(SConn *conn, char *id, char *pf) +{ + int n, nw, pd; + long len; + char s[Maxmsg+1]; + + /* get file size */ + n = readstr(conn, s); + if(n < 0){ + syslog(0, LOG, "remote: %s: %r\n", s); + return -1; + } + len = atoi(s); + if(len == -1){ + syslog(0, LOG, "remote file %s does not exist\n", pf); + return -1; + }else if(len < 0 || len > MAXFILESIZE){ + syslog(0, LOG, "implausible filesize %ld for %s\n", len, pf); + return -1; + } + + /* get file in Maxmsg chunks */ + if(strchr(pf,'/') != nil || strcmp(pf,"..")==0){ + syslog(0, LOG, "no slashes allowed: %s\n", pf); + return -1; + } + snprint(s, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, pf); + pd = create(s, OWRITE, 0660); + if(pd < 0){ + syslog(0, LOG, "can't open %s: %r\n", s); + return -1; + } + while(len > 0){ + n = conn->read(conn, (uchar*)s, Maxmsg); + if(n <= 0){ + syslog(0, LOG, "empty file chunk\n"); + return -1; + } + nw = write(pd, s, n); + if(nw != n){ + syslog(0, LOG, "write error on %s: %r", pf); + return -1; + } + len -= n; + } + close(pd); + return 0; + +} + +static int +removefile(SConn *conn, char *id, char *f) +{ + Dir *d; + char buf[Maxmsg]; + + snprint(buf, Maxmsg, "%s/store/%s/%s", SECSTORE_DIR, id, f); + + if((d = dirstat(buf)) == nil){ + snprint(buf, sizeof buf, "remove failed: %r"); + writerr(conn, buf); + return -1; + }else if(d->mode & DMDIR){ + snprint(buf, sizeof buf, "can't remove a directory"); + writerr(conn, buf); + free(d); + return -1; + } + + free(d); + if(remove(buf) < 0){ + snprint(buf, sizeof buf, "remove failed: %r"); + writerr(conn, buf); + return -1; + } + return 0; +} + +/* given line directory from accept, returns ipaddr!port */ +static char* +remoteIP(char *ldir) +{ + int fd, n; + char rp[100], ap[500]; + + snprint(rp, sizeof rp, "%s/remote", ldir); + fd = open(rp, OREAD); + if(fd < 0) + return strdup("?!?"); + n = read(fd, ap, sizeof ap); + if(n <= 0 || n == sizeof ap){ + fprint(2, "error %d reading %s: %r\n", n, rp); + return strdup("?!?"); + } + close(fd); + ap[n--] = 0; + if(ap[n] == '\n') + ap[n] = 0; + return strdup(ap); +} + +static int +dologin(int fd, char *S, int forceSTA) +{ + int i, n, rv; + char *file, *mess; + char msg[Maxmsg+1]; + PW *pw; + SConn *conn; + + pw = nil; + rv = -1; + + // collect the first message + if((conn = newSConn(fd)) == nil) + return -1; + if(readstr(conn, msg) < 0){ + fprint(2, "remote: %s: %r\n", msg); + writerr(conn, "can't read your first message"); + goto Out; + } + + // authenticate + if(PAKserver(conn, S, msg, &pw) < 0){ + if(pw != nil) + syslog(0, LOG, "secstore denied for %s", pw->id); + goto Out; + } + if((forceSTA || pw->status&STA) != 0){ + conn->write(conn, (uchar*)"STA", 3); + if(readstr(conn, msg) < 10 || strncmp(msg, "STA", 3) != 0){ + syslog(0, LOG, "no STA from %s", pw->id); + goto Out; + } + mess = secureidcheck(pw->id, msg+3); + if(mess != nil){ + syslog(0, LOG, "secureidcheck denied %s because %s", pw->id, mess); + goto Out; + } + } + conn->write(conn, (uchar*)"OK", 2); + syslog(0, LOG, "AUTH %s", pw->id); + + // perform operations as asked + while((n = readstr(conn, msg)) > 0){ + syslog(0, LOG, "[%s] %s", pw->id, msg); + + if(strncmp(msg, "GET ", 4) == 0){ + file = validatefile(msg+4); + if(file==nil || getfile(conn, pw->id, file) < 0) + goto Err; + + }else if(strncmp(msg, "PUT ", 4) == 0){ + file = validatefile(msg+4); + if(file==nil || putfile(conn, pw->id, file) < 0){ + syslog(0, LOG, "failed PUT %s/%s", pw->id, file); + goto Err; + } + + }else if(strncmp(msg, "RM ", 3) == 0){ + file = validatefile(msg+3); + if(file==nil || removefile(conn, pw->id, file) < 0){ + syslog(0, LOG, "failed RM %s/%s", pw->id, file); + goto Err; + } + + }else if(strncmp(msg, "CHPASS", 6) == 0){ + if(readstr(conn, msg) < 0){ + syslog(0, LOG, "protocol botch CHPASS for %s", pw->id); + writerr(conn, "protocol botch while setting PAK"); + goto Out; + } + pw->Hi = strtomp(msg, nil, 64, pw->Hi); + for(i=0; i < 4 && putPW(pw) < 0; i++) + syslog(0, LOG, "password change failed for %s (%d): %r", pw->id, i); + if(i==4) + goto Out; + + }else if(strncmp(msg, "BYE", 3) == 0){ + rv = 0; + break; + + }else{ + writerr(conn, "unrecognized operation"); + break; + } + + } + if(n <= 0) + syslog(0, LOG, "%s closed connection without saying goodbye\n", pw->id); + +Out: + freePW(pw); + conn->free(conn); + return rv; +Err: + writerr(conn, "operation failed"); + goto Out; +} + +void +main(int argc, char **argv) +{ + int afd, dfd, lcfd, forceSTA = 0; + char adir[40], ldir[40], *remote; + char *serve = "tcp!*!5356", *p, aserve[128]; + char *S = "secstore"; + char *dbpath; + Ndb *db2; + + S = sysname(); + SECSTORE_DIR = unsharp("#9/secstore"); +// setnetmtpt(net, sizeof(net), nil); + ARGBEGIN{ + case 'R': + forceSTA = 1; + break; + case 's': + serve = EARGF(usage()); + break; + case 'S': + S = EARGF(usage()); + break; + case 'x': + p = ARGF(); + if(p == nil) + usage(); + USED(p); + // setnetmtpt(net, sizeof(net), p); + break; + case 'v': + verbose++; + break; + default: + usage(); + }ARGEND; + + if(!verbose) + switch(rfork(RFNOTEG|RFPROC|RFFDG)) { + case -1: + sysfatal("fork: %r"); + case 0: + break; + default: + exits(0); + } + + snprint(aserve, sizeof aserve, "%s", serve); + afd = announce(aserve, adir); + if(afd < 0) + sysfatal("%s: %r\n", aserve); + syslog(0, LOG, "ANNOUNCE %s", aserve); + for(;;){ + if((lcfd = listen(adir, ldir)) < 0) + exits("can't listen"); + switch(fork()){ + case -1: + fprint(2, "secstore forking: %r\n"); + close(lcfd); + break; + case 0: + // "/lib/ndb/common.radius does not exist" if db set before fork + db = ndbopen(dbpath=unsharp("#9/ndb/auth")); + if(db == 0) + syslog(0, LOG, "no ndb/auth"); + db2 = ndbopen(0); + if(db2 == 0) + syslog(0, LOG, "no ndb/local"); + db = ndbcat(db, db2); + if((dfd = accept(lcfd, ldir)) < 0) + exits("can't accept"); + alarm(30*60*1000); // 30 min + remote = remoteIP(ldir); + syslog(0, LOG, "secstore from %s", remote); + free(remote); + dologin(dfd, S, forceSTA); + exits(nil); + default: + close(lcfd); + break; + } + } +} + diff --git a/src/cmd/auth/secstore/secureidcheck.c b/src/cmd/auth/secstore/secureidcheck.c new file mode 100644 index 00000000..95adb385 --- /dev/null +++ b/src/cmd/auth/secstore/secureidcheck.c @@ -0,0 +1,446 @@ +/* RFC2138 */ +#include <u.h> +#include <libc.h> +#include <ip.h> +#include <ctype.h> +#include <mp.h> +#include <libsec.h> +#include <bio.h> +#include <ndb.h> +#define AUTHLOG "auth" + +enum{ R_AccessRequest=1, /* Packet code */ + R_AccessAccept=2, + R_AccessReject=3, + R_AccessChallenge=11, + R_UserName=1, + R_UserPassword=2, + R_NASIPAddress=4, + R_ReplyMessage=18, + R_State=24, + R_NASIdentifier=32 +}; + +typedef struct Secret{ + uchar *s; + int len; +} Secret; + +typedef struct Attribute{ + struct Attribute *next; + uchar type; + uchar len; // number of bytes in value + uchar val[256]; +} Attribute; + +typedef struct Packet{ + uchar code, ID; + uchar authenticator[16]; + Attribute first; +} Packet; + +// assumes pass is at most 16 chars +void +hide(Secret *shared, uchar *auth, Secret *pass, uchar *x) +{ + DigestState *M; + int i, n = pass->len; + + M = md5(shared->s, shared->len, nil, nil); + md5(auth, 16, x, M); + if(n > 16) + n = 16; + for(i = 0; i < n; i++) + x[i] ^= (pass->s)[i]; +} + +int +authcmp(Secret *shared, uchar *buf, int m, uchar *auth) +{ + DigestState *M; + uchar x[16]; + + M = md5(buf, 4, nil, nil); // Code+ID+Length + M = md5(auth, 16, nil, M); // RequestAuth + M = md5(buf+20, m-20, nil, M); // Attributes + md5(shared->s, shared->len, x, M); + return memcmp(x, buf+4, 16); +} + +Packet* +newRequest(uchar *auth) +{ + static uchar ID = 0; + Packet *p; + + p = (Packet*)malloc(sizeof(*p)); + if(p == nil) + return nil; + p->code = R_AccessRequest; + p->ID = ++ID; + memmove(p->authenticator, auth, 16); + p->first.next = nil; + p->first.type = 0; + return p; +} + +void +freePacket(Packet *p) +{ + Attribute *a, *x; + + if(!p) + return; + a = p->first.next; + while(a){ + x = a; + a = a->next; + free(x); + } + free(p); +} + +int +ding(void *v, char *msg) +{ + USED(v); +/* syslog(0, AUTHLOG, "ding %s", msg); */ + if(strstr(msg, "alarm")) + return 1; + return 0; +} + +Packet * +rpc(char *dest, Secret *shared, Packet *req) +{ + uchar buf[4096], buf2[4096], *b, *e; + Packet *resp; + Attribute *a; + int m, n, fd, try; + + // marshal request + e = buf + sizeof buf; + buf[0] = req->code; + buf[1] = req->ID; + memmove(buf+4, req->authenticator, 16); + b = buf+20; + for(a = &req->first; a; a = a->next){ + if(b + 2 + a->len > e) + return nil; + *b++ = a->type; + *b++ = 2 + a->len; + memmove(b, a->val, a->len); + b += a->len; + } + n = b-buf; + buf[2] = n>>8; + buf[3] = n; + + // send request, wait for reply + fd = dial(dest, 0, 0, 0); + if(fd < 0){ + syslog(0, AUTHLOG, "%s: rpc can't get udp channel", dest); + return nil; + } + atnotify(ding, 1); + m = -1; + for(try = 0; try < 2; try++){ + alarm(4000); + m = write(fd, buf, n); + if(m != n){ + syslog(0, AUTHLOG, "%s: rpc write err %d %d: %r", dest, m, n); + m = -1; + break; + } + m = read(fd, buf2, sizeof buf2); + alarm(0); + if(m < 0){ + syslog(0, AUTHLOG, "%s rpc read err %d: %r", dest, m); + break; // failure + } + if(m == 0 || buf2[1] != buf[1]){ // need matching ID + syslog(0, AUTHLOG, "%s unmatched reply %d", dest, m); + continue; + } + if(authcmp(shared, buf2, m, buf+4) == 0) + break; + syslog(0, AUTHLOG, "%s bad rpc chksum", dest); + } + close(fd); + if(m <= 0) + return nil; + + // unmarshal reply + b = buf2; + e = buf2+m; + resp = (Packet*)malloc(sizeof(*resp)); + if(resp == nil) + return nil; + resp->code = *b++; + resp->ID = *b++; + n = *b++; + n = (n<<8) | *b++; + if(m != n){ + syslog(0, AUTHLOG, "rpc got %d bytes, length said %d", m, n); + if(m > n) + e = buf2+n; + } + memmove(resp->authenticator, b, 16); + b += 16; + a = &resp->first; + a->type = 0; + while(1){ + if(b >= e){ + a->next = nil; + break; // exit loop + } + a->type = *b++; + a->len = (*b++) - 2; + if(b + a->len > e){ // corrupt packet + a->next = nil; + freePacket(resp); + return nil; + } + memmove(a->val, b, a->len); + b += a->len; + if(b < e){ // any more attributes? + a->next = (Attribute*)malloc(sizeof(*a)); + if(a->next == nil){ + free(req); + return nil; + } + a = a->next; + } + } + return resp; +} + +int +setAttribute(Packet *p, uchar type, uchar *s, int n) +{ + Attribute *a; + + a = &p->first; + if(a->type != 0){ + a = (Attribute*)malloc(sizeof(*a)); + if(a == nil) + return -1; + a->next = p->first.next; + p->first.next = a; + } + a->type = type; + a->len = n; + if(a->len > 253 ) // RFC2138, section 5 + a->len = 253; + memmove(a->val, s, a->len); + return 0; +} + +/* return a reply message attribute string */ +char* +replymsg(Packet *p) +{ + Attribute *a; + static char buf[255]; + + for(a = &p->first; a; a = a->next){ + if(a->type == R_ReplyMessage){ + if(a->len >= sizeof buf) + a->len = sizeof(buf)-1; + memmove(buf, a->val, a->len); + buf[a->len] = 0; + } + } + return buf; +} + +/* for convenience while debugging */ +char *replymess; +Attribute *stateattr; + +void +logPacket(Packet *p) +{ + Attribute *a; + char buf[255]; + char pbuf[4*1024]; + uchar *au = p->authenticator; + int i; + char *np, *e; + + e = pbuf + sizeof(pbuf); + + np = seprint(pbuf, e, "Packet ID=%d auth=%x %x %x... ", p->ID, au[0], au[1], au[2]); + switch(p->code){ + case R_AccessRequest: + np = seprint(np, e, "request\n"); + break; + case R_AccessAccept: + np = seprint(np, e, "accept\n"); + break; + case R_AccessReject: + np = seprint(np, e, "reject\n"); + break; + case R_AccessChallenge: + np = seprint(np, e, "challenge\n"); + break; + default: + np = seprint(np, e, "code=%d\n", p->code); + break; + } + replymess = "0000000"; + for(a = &p->first; a; a = a->next){ + if(a->len > 253 ) + a->len = 253; + memmove(buf, a->val, a->len); + np = seprint(np, e, " [%d]", a->type); + for(i = 0; i<a->len; i++) + if(isprint(a->val[i])) + np = seprint(np, e, "%c", a->val[i]); + else + np = seprint(np, e, "\\%o", a->val[i]); + np = seprint(np, e, "\n"); + buf[a->len] = 0; + if(a->type == R_ReplyMessage) + replymess = strdup(buf); + else if(a->type == R_State) + stateattr = a; + } + + syslog(0, AUTHLOG, "%s", pbuf); +} + +static uchar* +getipv4addr(void) +{ + Ipifc *nifc; + Iplifc *lifc; + static Ipifc *ifc; + + ifc = readipifc("/net", ifc, -1); + for(nifc = ifc; nifc; nifc = nifc->next) + for(lifc = nifc->lifc; lifc; lifc = lifc->next) + if(ipcmp(lifc->ip, IPnoaddr) != 0 && ipcmp(lifc->ip, v4prefix) != 0) + return lifc->ip; + return nil; +} + +extern Ndb *db; + +/* returns 0 on success, error message on failure */ +char* +secureidcheck(char *user, char *response) +{ + Packet *req = nil, *resp = nil; + ulong u[4]; + uchar x[16]; + char *radiussecret; + char ruser[ 64]; + char dest[3*IPaddrlen+20]; + Secret shared, pass; + char *rv = "authentication failed"; + Ndbs s; + Ndbtuple *t, *nt, *tt; + uchar *ip; + static Ndb *netdb; + + if(netdb == nil) + netdb = ndbopen(0); + + /* bad responses make them disable the fob, avoid silly checks */ + if(strlen(response) < 4 || strpbrk(response,"abcdefABCDEF") != nil) + goto out; + + /* get radius secret */ + radiussecret = ndbgetvalue(db, &s, "radius", "lra-radius", "secret", &t); + if(radiussecret == nil){ + syslog(0, AUTHLOG, "secureidcheck: nil radius secret: %r"); + goto out; + } + + /* translate user name if we have to */ + strcpy(ruser, user); + for(nt = t; nt; nt = nt->entry){ + if(strcmp(nt->attr, "uid") == 0 && strcmp(nt->val, user) == 0) + for(tt = nt->line; tt != nt; tt = tt->line) + if(strcmp(tt->attr, "rid") == 0){ + strcpy(ruser, tt->val); + break; + } + } + ndbfree(t); + + u[0] = fastrand(); + u[1] = fastrand(); + u[2] = fastrand(); + u[3] = fastrand(); + req = newRequest((uchar*)u); + if(req == nil) + goto out; + shared.s = (uchar*)radiussecret; + shared.len = strlen(radiussecret); + ip = getipv4addr(); + if(ip == nil){ + syslog(0, AUTHLOG, "no interfaces: %r\n"); + goto out; + } + if(setAttribute(req, R_NASIPAddress, ip + IPv4off, 4) < 0) + goto out; + + if(setAttribute(req, R_UserName, (uchar*)ruser, strlen(ruser)) < 0) + goto out; + pass.s = (uchar*)response; + pass.len = strlen(response); + hide(&shared, req->authenticator, &pass, x); + if(setAttribute(req, R_UserPassword, x, 16) < 0) + goto out; + + t = ndbsearch(netdb, &s, "sys", "lra-radius"); + if(t == nil){ + syslog(0, AUTHLOG, "secureidcheck: nil radius sys search: %r\n"); + goto out; + } + for(nt = t; nt; nt = nt->entry){ + if(strcmp(nt->attr, "ip") != 0) + continue; + + snprint(dest,sizeof dest,"udp!%s!oradius", nt->val); + resp = rpc(dest, &shared, req); + if(resp == nil){ + syslog(0, AUTHLOG, "%s nil response", dest); + continue; + } + if(resp->ID != req->ID){ + syslog(0, AUTHLOG, "%s mismatched ID req=%d resp=%d", + dest, req->ID, resp->ID); + freePacket(resp); + resp = nil; + continue; + } + + switch(resp->code){ + case R_AccessAccept: + syslog(0, AUTHLOG, "%s accepted ruser=%s", dest, ruser); + rv = nil; + break; + case R_AccessReject: + syslog(0, AUTHLOG, "%s rejected ruser=%s %s", dest, ruser, replymsg(resp)); + rv = "secureid failed"; + break; + case R_AccessChallenge: + syslog(0, AUTHLOG, "%s challenge ruser=%s %s", dest, ruser, replymsg(resp)); + rv = "secureid out of sync"; + break; + default: + syslog(0, AUTHLOG, "%s code=%d ruser=%s %s", dest, resp->code, ruser, replymsg(resp)); + break; + } + break; // we have a proper reply, no need to ask again + } + ndbfree(t); + free(radiussecret); +out: + freePacket(req); + freePacket(resp); + return rv; +} diff --git a/src/cmd/auth/secstore/secuser.c b/src/cmd/auth/secstore/secuser.c new file mode 100644 index 00000000..31ba184b --- /dev/null +++ b/src/cmd/auth/secstore/secuser.c @@ -0,0 +1,244 @@ +#include <u.h> +#include <libc.h> +#include <mp.h> +#include <libsec.h> +#include "SConn.h" +#include "secstore.h" + +int verbose; + +static void userinput(char *, int); +char *SECSTORE_DIR; + +static void +ensure_exists(char *f, ulong perm) +{ + int fd; + + if(access(f, AEXIST) >= 0) + return; + if(verbose) + fprint(2,"first time setup for secstore: create %s %lo\n", f, perm); + fd = create(f, OREAD, perm); + if(fd < 0){ + fprint(2, "unable to create %s\n", f); + exits("secstored directories"); + } + close(fd); +} + + +int +main(int argc, char **argv) +{ + int isnew; + char *id, buf[Maxmsg], home[Maxmsg], prompt[100], *hexHi; + char *pass, *passck; + long expsecs; + mpint *H = mpnew(0), *Hi = mpnew(0); + PW *pw; + Tm *tm; + + SECSTORE_DIR = unsharp("#9/secstore"); + + ARGBEGIN{ + case 'v': + verbose++; + break; + }ARGEND; + if(argc!=1){ + print("usage: secuser [-v] <user>\n"); + exits("usage"); + } + + ensure_exists(SECSTORE_DIR, DMDIR|0755L); + snprint(home, sizeof(home), "%s/who", SECSTORE_DIR); + ensure_exists(home, DMDIR|0755L); + snprint(home, sizeof(home), "%s/store", SECSTORE_DIR); + ensure_exists(home, DMDIR|0700L); + + id = argv[0]; + if(verbose) + fprint(2,"secuser %s\n", id); + if((pw = getPW(id,1)) == nil){ + isnew = 1; + print("new account (because %s/%s %r)\n", SECSTORE_DIR, id); + pw = emalloc(sizeof(*pw)); + pw->id = estrdup(id); + snprint(home, sizeof(home), "%s/store/%s", SECSTORE_DIR, id); + if(access(home, AEXIST) == 0){ + print("new user, but directory %s already exists\n", home); + exits(home); + } + }else{ + isnew = 0; + } + + /* get main password for id */ + for(;;){ + if(isnew) + snprint(prompt, sizeof(prompt), "%s password", id); + else + snprint(prompt, sizeof(prompt), "%s password [default = don't change]", id); + pass = readcons(prompt, nil, 1); + if(pass == nil){ + print("getpass failed\n"); + exits("getpass failed"); + } + if(verbose) + print("%ld characters\n", strlen(pass)); + if(pass[0] == '\0' && isnew == 0) + break; + if(strlen(pass) >= 7) + break; + print("password must be at least 7 characters\n"); + } + + if(pass[0] != '\0'){ + snprint(prompt, sizeof(prompt), "retype password"); + if(verbose) + print("confirming...\n"); + passck = readcons(prompt, nil, 1); + if(passck == nil){ + print("getpass failed\n"); + exits("getpass failed"); + } + if(strcmp(pass, passck) != 0){ + print("passwords didn't match\n"); + exits("no match"); + } + memset(passck, 0, strlen(passck)); + free(passck); + hexHi = PAK_Hi(id, pass, H, Hi); + memset(pass, 0, strlen(pass)); + free(pass); + free(hexHi); + mpfree(H); + pw->Hi = Hi; + } + + /* get expiration time (midnight of date specified) */ + if(isnew) + expsecs = time(0) + 365*24*60*60; + else + expsecs = pw->expire; + + for(;;){ + tm = localtime(expsecs); + print("expires [DDMMYYYY, default = %2.2d%2.2d%4.4d]: ", + tm->mday, tm->mon, tm->year+1900); + userinput(buf, sizeof(buf)); + if(strlen(buf) == 0) + break; + if(strlen(buf) != 8){ + print("!bad date format: %s\n", buf); + continue; + } + tm->mday = (buf[0]-'0')*10 + (buf[1]-'0'); + if(tm->mday > 31 || tm->mday < 1){ + print("!bad day of month: %d\n", tm->mday); + continue; + } + tm->mon = (buf[2]-'0')*10 + (buf[3]-'0') - 1; + if(tm->mon > 11 || tm->mday < 0){ + print("!bad month: %d\n", tm->mon + 1); + continue; + } + tm->year = atoi(buf+4) - 1900; + if(tm->year < 70){ + print("!bad year: %d\n", tm->year + 1900); + continue; + } + tm->sec = 59; + tm->min = 59; + tm->hour = 23; + tm->yday = 0; + expsecs = tm2sec(tm); + break; + } + pw->expire = expsecs; + + /* failed logins */ + if(pw->failed != 0 ) + print("clearing %d failed login attempts\n", pw->failed); + pw->failed = 0; + + /* status bits */ + if(isnew) + pw->status = Enabled; + for(;;){ + print("Enabled or Disabled [default %s]: ", + (pw->status & Enabled) ? "Enabled" : "Disabled" ); + userinput(buf, sizeof(buf)); + if(strlen(buf) == 0) + break; + if(buf[0]=='E' || buf[0]=='e'){ + pw->status |= Enabled; + break; + } + if(buf[0]=='D' || buf[0]=='d'){ + pw->status = pw->status & ~Enabled; + break; + } + } + for(;;){ + print("require STA? [default %s]: ", + (pw->status & STA) ? "yes" : "no" ); + userinput(buf, sizeof(buf)); + if(strlen(buf) == 0) + break; + if(buf[0]=='Y' || buf[0]=='y'){ + pw->status |= STA; + break; + } + if(buf[0]=='N' || buf[0]=='n'){ + pw->status = pw->status & ~STA; + break; + } + } + + /* free form field */ + if(isnew) + pw->other = nil; + print("comments [default = %s]: ", (pw->other == nil) ? "" : pw->other); + userinput(buf, 72); /* 72 comes from password.h */ + if(buf[0]) + if((pw->other = strdup(buf)) == nil) + sysfatal("strdup"); + + syslog(0, LOG, "CHANGELOGIN for '%s'", pw->id); + if(putPW(pw) < 0){ + print("error writing entry: %r\n"); + exits("can't write password file"); + }else{ + print("change written\n"); + if(isnew && create(home, OREAD, DMDIR | 0775L) < 0){ + print("unable to create %s: %r\n", home); + exits(home); + } + } + + exits(""); + return 1; /* keep other compilers happy */ +} + + +static void +userinput(char *buf, int blen) +{ + int n; + + while(1){ + n = read(0, buf, blen); + if(n<=0) + exits("read error"); + if(buf[n-1]=='\n'){ + buf[n-1] = '\0'; + return; + } + buf += n; blen -= n; + if(blen<=0) + exits("input too large"); + } +} + diff --git a/src/cmd/auth/secstore/util.c b/src/cmd/auth/secstore/util.c new file mode 100644 index 00000000..ebbb12df --- /dev/null +++ b/src/cmd/auth/secstore/util.c @@ -0,0 +1,28 @@ +#include <u.h> +#include <libc.h> + +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; +} |