tsecstore from ehg - plan9port - [fork] Plan 9 from user space | |
git clone git://src.adamsgaard.dk/plan9port | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit a812ae4b4392cade3321da1a146a231ff88db3b7 | |
parent 73bfbacb2409b10f45fd080891a95de29cafe4a0 | |
Author: rsc <devnull@localhost> | |
Date: Mon, 27 Dec 2004 19:36:51 +0000 | |
secstore from ehg | |
Diffstat: | |
A src/cmd/secstore/SConn.c | 213 +++++++++++++++++++++++++++++… | |
A src/cmd/secstore/SConn.h | 26 ++++++++++++++++++++++++++ | |
A src/cmd/secstore/aescbc.c | 156 +++++++++++++++++++++++++++++… | |
A src/cmd/secstore/dirls.c | 87 +++++++++++++++++++++++++++++… | |
A src/cmd/secstore/mkfile | 23 +++++++++++++++++++++++ | |
A src/cmd/secstore/pak.c | 344 ++++++++++++++++++++++++++++++ | |
A src/cmd/secstore/password.c | 136 +++++++++++++++++++++++++++++… | |
A src/cmd/secstore/secstore.c | 580 +++++++++++++++++++++++++++++… | |
A src/cmd/secstore/secstore.h | 30 ++++++++++++++++++++++++++++++ | |
A src/cmd/secstore/util.c | 38 +++++++++++++++++++++++++++++… | |
10 files changed, 1633 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/src/cmd/secstore/SConn.c b/src/cmd/secstore/SConn.c | |
t@@ -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 … | |
+ 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[SHA1dl… | |
+{ | |
+ 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 leng… | |
+ return -1; | |
+ } | |
+ if( (nr = readn(ss->fd, buf, len)) != len){ | |
+ snprint((char*)buf,n,"!SC_read expected %d bytes, but … | |
+ 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/secstore/SConn.h b/src/cmd/secstore/SConn.h | |
t@@ -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/secstore/aescbc.c b/src/cmd/secstore/aescbc.c | |
t@@ -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 = 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, encryptio… | |
+ | |
+ if(encrypt){ | |
+ safewrite(v2hdr, AESbsize); | |
+ genrandom(buf,2*AESbsize); /* CBC is semantically secure if IV… | |
+ setupAESstate(&aes, key, nkey, buf); /* use first AESbsize by… | |
+ aesCBCencrypt(buf+AESbsize, AESbsize, &aes); /* use second AE… | |
+ 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 init… | |
+ setupAESstate(&aes, key, nkey, buf); | |
+ dstate = hmac_sha1(buf+AESbsize, AESbsize, key2, MD5dl… | |
+ aesCBCdecrypt(buf+AESbsize, AESbsize, &aes); | |
+ saferead(buf, SHA1dlen); | |
+ while((n = Bread(&bin, buf+SHA1dlen, BUF)) > 0){ | |
+ dstate = hmac_sha1(buf, n, key2, MD5dlen, 0, d… | |
+ aesCBCdecrypt(buf, n, &aes); | |
+ safewrite(buf, n); | |
+ memmove(buf, buf+n, SHA1dlen); /* these bytes… | |
+ } | |
+ hmac_sha1(0, 0, key2, MD5dlen, buf+SHA1dlen, dstate); | |
+ if(memcmp(buf, buf+SHA1dlen, SHA1dlen) != 0){ | |
+ fprint(2,"decrypted file failed to authenticat… | |
+ 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 authenticat… | |
+ exits("decrypted file failed to authenticate"); | |
+ } | |
+ } | |
+ } | |
+ exits(""); | |
+ return 1; /* gcc */ | |
+} | |
diff --git a/src/cmd/secstore/dirls.c b/src/cmd/secstore/dirls.c | |
t@@ -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 (*)(void *, 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].le… | |
+ n += enc64(dig, sizeof dig, sha1file(path, dirbuf[i].name), SH… | |
+ 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… | |
+ } | |
+ free(dirbuf); | |
+ return list; | |
+} | |
+ | |
diff --git a/src/cmd/secstore/mkfile b/src/cmd/secstore/mkfile | |
t@@ -0,0 +1,23 @@ | |
+<$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 | |
+SHORTLIB=sec mp regexp9 thread bio 9 | |
+ | |
+<$PLAN9/src/mkmany | |
+ | |
+$O.aescbc: aescbc.$O util.$O $LIB ${SHORTLIB:%=$LIBDIR/lib%.a} | |
+ $LD -o $target $prereq $LDFLAGS | |
+ | |
diff --git a/src/cmd/secstore/pak.c b/src/cmd/secstore/pak.c | |
t@@ -0,0 +1,344 @@ | |
+// PAK is an encrypted key exchange protocol designed by Philip MacKenzie et a… | |
+// 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",… | |
+ | |
+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, … | |
+ pak->p = strtomp("C41CFBE4D4846F67A3DF7DE9921A49D3B42DC33728427AB159CE… | |
+ "DB12B5F0C244F1A734AEB9840804EA3C25036AD1B61AFF3ABBC247CD4B384… | |
+ "3A6F020E7EE9795554BCD08ABAD7321AF27E1E92E3DB1C6E7E94FAAE590AE… | |
+ "3D178E809401ABE8A534A1EC44359733475A36A70C7B425125062B1142D", | |
+ nil, 16, nil); | |
+ pak->r = strtomp("DF310F4E54A5FEC5D86D3E14863921E834113E060F90052AD332… | |
+ "CEF2497EFA0303D6344F7C819691A0F9C4A773815AF8EAECFB7EC1D98F039… | |
+ "887D97251A927D093F44A55577F4D70444AEBD06B9B45695EC23962B175F2… | |
+ "21C4656848614D888A4", nil, 16, nil); | |
+ pak->g = strtomp("2F1C308DC46B9A44B52DF7DACCE1208CCEF72F69C743ADD4D232… | |
+ "44ED6E65E074694246E07F9FD4AE26E0FDDD9F54F813C40CB9BCD4338EA6F… | |
+ "410E676C290368A16B1A3594877437E516C53A6EEE5493A038A017E955E21… | |
+ "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 *… | |
+{ | |
+ 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 itsel… | |
+int | |
+PAKclient(SConn *conn, char *C, char *pass, char **pS) | |
+{ | |
+ char *mess, *mess2, *eol, *S, *hexmu, *ks, *hexm, *hexsigma = nil, *he… | |
+ 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… | |
+ | |
+ // 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) != … | |
+ 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 = n… | |
+ 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/secstore/password.c b/src/cmd/secstore/password.c | |
t@@ -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 … | |
+ 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/secstore/secstore.c b/src/cmd/secstore/secstore.c | |
t@@ -0,0 +1,580 @@ | |
+/* network login client */ | |
+#include <u.h> | |
+#include <libc.h> | |
+#include <mp.h> | |
+#include <libsec.h> | |
+#include <authsrv.h> | |
+#include "SConn.h" | |
+#include "secstore.h" | |
+enum{ CHK = 16, MAXFILES = 100 }; | |
+ | |
+typedef struct AuthConn{ | |
+ SConn *conn; | |
+ char pass[64]; | |
+ int passlen; | |
+} AuthConn; | |
+ | |
+int verbose; | |
+Nvrsafe nvr; | |
+ | |
+void | |
+usage(void) | |
+{ | |
+ fprint(2, "usage: secstore [-cin] [-g getfile] [-p putfile] [-r rmfile… | |
+ exits("usage"); | |
+} | |
+ | |
+static int | |
+getfile(SConn *conn, char *gf, uchar **buf, ulong *buflen, uchar *key, int nke… | |
+{ | |
+ 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", … | |
+ 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… | |
+ goto Out; | |
+ if(*Gflag){ | |
+ // write one line at a time, as required by /mnt/facto… | |
+ 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) … | |
+ 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->… | |
+ fprint(2, "getfile of '%s' failed\n", f[0]); | |
+ continue; | |
+ } | |
+ if(putfile(c->conn, f[0], memfile, len, (uchar*)newpass, newpa… | |
+ fprint(2, "putfile of '%s' failed\n", f[0]); | |
+ free(memfile); | |
+ } | |
+ free(list); | |
+ c->conn->write(c->conn, (uchar*)"BYE", 3); | |
+ rv = 0; | |
+ | |
+Out: | |
+ if(newpass != nil){ | |
+ memset(newpass, 0, newpasslen); | |
+ free(newpass); | |
+ } | |
+ c->conn->free(c->conn); | |
+ return rv; | |
+} | |
+ | |
+static AuthConn* | |
+login(char *id, char *dest, int pass_stdin, int pass_nvram) | |
+{ | |
+ AuthConn *c; | |
+ int fd, n, ntry = 0; | |
+ char *S, *PINSTA = nil, *nl, s[Maxmsg+1], *pass; | |
+ | |
+ if(dest == nil){ | |
+ fprint(2, "tried to login with nil dest\n"); | |
+ exits("nil dest"); | |
+ } | |
+ c = emalloc(sizeof(*c)); | |
+ if(pass_nvram){ | |
+ /* if(readnvram(&nvr, 0) < 0) */ | |
+ exits("readnvram: %r"); | |
+ strecpy(c->pass, c->pass+sizeof c->pass, nvr.config); | |
+ } | |
+ if(pass_stdin){ | |
+ n = readn(0, s, Maxmsg-2); // so len(PINSTA)<Maxmsg-3 | |
+ if(n < 1) | |
+ exits("no password on standard input"); | |
+ s[n] = 0; | |
+ nl = strchr(s, '\n'); | |
+ if(nl){ | |
+ *nl++ = 0; | |
+ PINSTA = estrdup(nl); | |
+ nl = strchr(PINSTA, '\n'); | |
+ if(nl) | |
+ *nl = 0; | |
+ } | |
+ strecpy(c->pass, c->pass+sizeof c->pass, s); | |
+ } | |
+ while(1){ | |
+ if(verbose) | |
+ fprint(2, "dialing %s\n", dest); | |
+ if((fd = dial(dest, nil, nil, nil)) < 0){ | |
+ fprint(2, "can't dial %s\n", dest); | |
+ free(c); | |
+ return nil; | |
+ } | |
+ if((c->conn = newSConn(fd)) == nil){ | |
+ free(c); | |
+ return nil; | |
+ } | |
+ ntry++; | |
+ if(!pass_stdin && !pass_nvram){ | |
+ pass = getpassm("secstore password: "); | |
+ if(strlen(pass) >= sizeof c->pass){ | |
+ fprint(2, "password too long, skipping secstor… | |
+ 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.\… | |
+ 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/secstore/secstore.h b/src/cmd/secstore/secstore.h | |
t@@ -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" | |
diff --git a/src/cmd/secstore/util.c b/src/cmd/secstore/util.c | |
t@@ -0,0 +1,38 @@ | |
+#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; | |
+} | |
+ | |
+char * | |
+getpassm(char *prompt) | |
+{ | |
+ char *p = getpass(prompt); | |
+ | |
+ if(p == nil || (p = strdup(p)) == nil) | |
+ sysfatal("getpassm"); | |
+ return p; | |
+} |