shithub: stashfs

Download patch

ref: 0a8361b74fc4ac864e2c5fdecdc0831d61d061c7
author: glenda <[email protected]>
date: Thu Jun 3 18:44:09 EDT 2021

init

--- /dev/null
+++ b/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+
+OFILES=stashfs.$O
+
+TARG=stashfs
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/stashfs.c
@@ -1,0 +1,699 @@
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <libsec.h>
+#include <authsrv.h>
+
+/* nonce[24] | poly1305-tag[16] | E(next-offset[8] | data[4096]) */
+enum {
+	Overhead = 24+16+8,
+	Datasize = 4096,
+	Blocksize = Overhead+Datasize,
+};
+
+typedef struct FileHdr FileHdr;
+typedef struct FileKey FileKey;
+typedef struct FileAux FileAux;
+
+typedef struct KeyEntry KeyEntry;
+
+struct FileHdr
+{
+	char	V[21];	/* version signature */
+	uchar	N;	/* log2(computation cost) */
+	uchar	R;	/* log2(scrypt block size) */
+	uchar	P;	/* parallelization factor */
+	uchar	S[32];	/* password salt */
+	uchar	T[32];	/* random file nonce */
+};
+
+struct FileKey
+{
+	DigestState	ds;
+	Salsastate	cs;
+};
+
+struct FileAux
+{
+	int		fd;
+	int		mode;
+	FileHdr		hdr;
+	FileKey		key;
+};
+
+struct KeyEntry
+{
+	KeyEntry	*next;
+
+	uchar		K[32];	/* K = scrypt(pass, S, N, R, P) */
+	uchar		S[32];
+	uchar		N;
+	uchar		R;
+	uchar		P;
+};
+
+int	verbose;
+uchar	buf[Blocksize];
+FileHdr defhdr = { "SCRYPTXSALSAPOLY1305\n", 16, 3, 1 };
+char*	pass;
+KeyEntry *keylist;
+
+char*
+getpass(int confirm)
+{
+Again:
+	if(pass != nil){
+		memset(pass, 0, strlen(pass));
+		free(pass);
+	}
+	pass = readcons("Password", nil, 1);
+	if(pass == nil || pass[0] == 0)
+		sysfatal("no password");
+	if(confirm){
+		char *pass2;
+		int n;
+
+		pass2 = readcons("Confirm", nil, 1);
+		if(pass2 == nil || pass2[0] == 0)
+			sysfatal("no password");
+		n = strcmp(pass2, pass);
+		memset(pass2, 0, strlen(pass2));
+		free(pass2);
+
+		if(n != 0){
+			fprint(2, "mismatch\n");
+			goto Again;
+		}
+	}
+	return pass;
+}
+
+KeyEntry*
+getkey(uchar N, uchar R, uchar P, uchar S[32])
+{
+	int pfd[2], pid, n;
+	KeyEntry *ke;
+	Waitmsg *w;
+	char *err;
+
+	/* see if we did the key derivation already */
+	for(ke = keylist; ke != nil; ke = ke->next)
+		if(ke->N == N && ke->R == R && ke->P == P
+		&& memcmp(ke->S, S, sizeof(ke->S)) == 0)
+			return ke;
+
+	if(verbose)
+		fprint(2, "%s: will require %lldMB of memory\n",
+			argv0, (128LL * (1LL<<R) * (1LL<<N)) >> 20);
+
+	if(pass == nil)
+		getpass(0);
+
+	ke = emalloc9p(sizeof(*ke));
+	ke->N = N;
+	ke->R = R;
+	ke->P = P;
+	memmove(ke->S, S, sizeof(ke->S));
+	if(pipe(pfd) < 0)
+		return nil;
+	pid = fork();
+	if(pid < 0)
+		return nil;
+	if(pid == 0){
+		close(pfd[0]);
+		alarm(60*1000);	/* timeout after a minute */
+		if((err = scrypt((uchar*)pass, strlen(pass),
+			ke->S, sizeof(ke->S),
+			1<<ke->N, 1<<ke->R, ke->P,
+			ke->K, sizeof(ke->K))) != nil)
+			exits(err);
+		write(pfd[1], ke->K, sizeof(ke->K));
+		exits(nil);
+	}
+	close(pfd[1]);
+	n = readn(pfd[0], ke->K, sizeof(ke->K));
+	close(pfd[0]);
+	while((w = wait()) != nil){
+		if(w->pid == pid){
+			if(verbose)
+				fprint(2, "%s: spent %g seconds crunching key...\n",
+					argv0, (double)w->time[2] / 1000.0);
+			if(w->msg[0]){
+				werrstr("%s", w->msg);
+				free(w);
+				n = -1;
+				break;
+			}
+		}
+		free(w);
+	}
+	if(n != sizeof(ke->K)){
+		free(ke);
+		return nil;
+	}
+
+	ke->next = keylist;
+	keylist = ke;
+
+	return ke;
+}
+
+uvlong
+cryptsetup(FileKey *k, uchar nonce[24], uvlong o)
+{
+	uchar otk[SalsaBsize];
+
+	salsa_setiv(&k->cs, nonce);
+	salsa_setblock(&k->cs, (o / Datasize) * ((SalsaBsize+Datasize)/SalsaBsize));
+
+	memset(otk, 0, sizeof(otk));
+	salsa_encrypt(otk, sizeof(otk), &k->cs);
+
+	/* first 256 bits used as one time authenticator key */
+	memset(&k->ds, 0, sizeof(k->ds));
+	poly1305(nil, 0, otk, 32, nil, &k->ds);
+
+	/* last 64 bits used to encrypt next-offset */
+	return GBIT64(otk+SalsaBsize-8);
+}
+
+int
+encryptbuf(FileKey *k, uchar *buf, ulong n, vlong o)
+{
+	uvlong e;
+
+	genrandom(buf, 24);
+	e = cryptsetup(k, buf, o);
+
+	n -= Overhead;
+	o += n;
+	if(o < 0){
+		werrstr("offset too large");
+		return -1;
+	}
+
+	/* random fill block tail */
+	if(n < Datasize)
+		genrandom(buf+Overhead+n, Datasize-n);
+
+	/* encrypt plaintext */
+	salsa_encrypt(buf+Overhead, Datasize, &k->cs);
+
+	e ^= o;	/* encrypt next-block offset */
+	PBIT64(buf+40, e);
+
+	/* authenticate ciphertext */ 
+	poly1305(buf+40, 8+Datasize, nil, 0, buf+24, &k->ds);
+
+	return n;
+}
+
+int
+decryptbuf(FileKey *k, uchar *buf, ulong n, vlong o)
+{
+	uchar tag[16];
+	uvlong e;
+
+	e = cryptsetup(k, buf, o);
+
+	/* authenticate ciphertext */ 
+	poly1305(buf+40, 8+Datasize, nil, 0, tag, &k->ds);
+
+	/* check the tag */
+	if(tsmemcmp(tag, buf+24, 16) != 0){
+		werrstr("bad block tag");
+		return -1;
+	}
+
+	/* decrypt ciphertext */
+	salsa_encrypt(buf+Overhead, Datasize, &k->cs);
+
+	/* decrypt next-block offset */
+	e ^= GBIT64(buf+40);
+
+	/* sanity check offset */
+	n -= Overhead;
+	if(e < o || e > o+n)
+		e = o;
+
+	/* zero fill remainder */
+	n = e - o;
+	if(n < Datasize)
+		memset(buf+Overhead+n, 0, Datasize - n);
+
+	return n;
+}
+
+vlong
+off2block(uvlong off, int *rem)
+{
+	if(rem != nil)
+		*rem = off % Datasize;
+	off /= Datasize;
+	off *= Blocksize;
+	off += sizeof(FileHdr);
+	return off;
+}
+
+int
+cryptread(File *f, void *data, int n, vlong o)
+{
+	FileAux *a;
+	int r, m;
+	uchar *p;
+
+	if(o >= f->length)
+		return 0;
+	if(f->length - o < n)
+		n = f->length - o;
+
+	a = f->aux;
+	for(p = (uchar*)data; n > 0; p += m, n -= m, o += m){
+		if(pread(a->fd, buf, Blocksize, off2block(o, &r)) != Blocksize)
+			return -1;
+		m = decryptbuf(&a->key, buf, Blocksize, o - r);
+		if(m < 0)
+			return -1;
+		m -= r;
+		if(m <= 0)
+			break;
+		if(n < m)
+			m = n;
+		memmove(p, buf+Overhead+r, m);
+	}
+
+	return p - (uchar*)data;
+}
+
+int
+cryptwrite(File *f, void *data, int n, vlong o)
+{
+	FileAux *a;
+	vlong boff;
+	int r, m;
+	uchar *p;
+
+	for(p = (uchar*)data;;p += r, n -= m, o += m){
+		boff = off2block(o, &r);
+		m = Datasize - r;
+		if(n <= m)
+			break;
+		r = cryptwrite(f, p, m, o);
+		if(r < 0)
+			return -1;
+	}
+
+	a = f->aux;
+	if(n < Datasize && boff < off2block(f->length + Datasize-1, nil)){
+		if(pread(a->fd, buf, Blocksize, boff) != Blocksize)
+			return -1;
+		if(decryptbuf(&a->key, buf, Blocksize, o - r) < 0)
+			return -1;
+	}
+
+	if(p == nil)
+		memset(buf+Overhead+r, 0, n);
+	else {
+		memmove(buf+Overhead+r, p, n);
+		p += n;
+	}
+
+	if(f->length - o < m){
+		m = f->length - o;
+		if(m < n)
+			m = n;
+	}
+	if(encryptbuf(&a->key, buf, Overhead+r+m, o - r) < 0)
+		return -1;
+	if(pwrite(a->fd, buf, Blocksize, boff) != Blocksize)
+		return -1;
+
+	o += n;
+	if(o > f->length)
+		f->length = o;
+
+	return p - (uchar*)data;
+}
+
+int
+cryptsize(File *f, vlong size)
+{
+	Dir d;
+
+	if(f->length == size)
+		return 0;
+
+	if(size > f->length){
+		while(size - f->length > 1<<30)
+			if(cryptwrite(f, nil, 1<<30, f->length) < 0)
+				return -1;
+		if(cryptwrite(f, nil, size - f->length, f->length) < 0)
+			return -1;
+		return 0;
+	}
+
+	if(size < 0){
+		werrstr("negative file size");
+		return -1;
+	}
+	nulldir(&d);
+	d.length = off2block(size + Datasize-1, nil);
+	if(dirfwstat(((FileAux*)f->aux)->fd, &d) < 0)
+		return -1;
+
+	f->length = size;
+	if(cryptwrite(f, nil, 0, size) < 0)	/* rewrite last block */
+		return -1;
+
+	return 0;
+}
+
+int
+cryptfilekey(FileAux *a)
+{
+	KeyEntry *ke;
+	FileHdr *h;
+	uchar E[32];
+
+	/* K = scrypt(pass, N, R, P, S); */
+	h = &a->hdr;
+	if((ke = getkey(h->N, h->R, h->P, h->S)) == nil)
+		return -1;
+
+	/* E = hkdf_sha256(T, V, K) */
+	hkdf_x(	h->T, sizeof(h->T),
+		(uchar*)h->V, sizeof(h->V),
+		ke->K, sizeof(ke->K),
+		E, sizeof(E),
+		hmac_sha2_256, SHA2_256dlen);
+
+	/* xsalsa with 192 bit nonces */
+	setupSalsastate(&a->key.cs, E, sizeof(E), nil, 24, 20);
+
+	memset(E, 0, sizeof(E));
+	return 0;
+}
+
+void
+cryptclose(File *f)
+{
+	FileAux *a = f->aux;
+	if(a->fd >= 0){
+		close(a->fd);
+		a->fd = -1;
+		memset(&a->key, 0, sizeof(a->key));
+		memset(&a->hdr, 0, sizeof(a->hdr));
+	}
+	a->mode = 0;
+}
+
+int
+cryptopen(File *f, int mode)
+{
+	FileAux *a = f->aux;
+	vlong o;
+	int n;
+
+	mode &= 3;
+	if(mode >= OWRITE)
+		mode = ORDWR;
+	if(a->fd >= 0){
+		if(a->mode == mode)
+			return 0;
+		close(a->fd);
+	}
+	if((a->fd = open(f->name, mode)) < 0){
+		werrstr("open: %r");
+		goto Error;
+	}
+	a->mode = mode;
+	o = seek(a->fd, 0, 2) - Blocksize;
+	o -= sizeof(FileHdr);
+	o -= (o / Blocksize) * Overhead;
+	if(o < 0 || o % Datasize){
+		werrstr("bad file size");
+		goto Error;
+	}
+
+	/* read header */
+	if(seek(a->fd, 0, 0) != 0
+	|| readn(a->fd, &a->hdr, sizeof(a->hdr)) != sizeof(a->hdr)
+	|| memcmp(a->hdr.V, defhdr.V, sizeof(a->hdr.V)) != 0){
+		werrstr("bad file header");
+		goto Error;
+	}
+
+	/* decrypt last block to determine plaintext size */
+	if(cryptfilekey(a) < 0
+	|| pread(a->fd, buf, Blocksize, off2block(o, nil)) != Blocksize
+	|| (n = decryptbuf(&a->key, buf, Blocksize, o)) < 0)
+		goto Error;
+
+	f->length = o+n;
+	return 0;
+Error:
+	cryptclose(f);
+	return -1;
+}
+
+File*
+cryptcreate(File *root, char *name, int mode)
+{
+	FileAux *a;
+	File *f;
+	Dir *d;
+	int fd;
+
+	if((mode & DMDIR) != 0){
+		werrstr("can't create directory");
+		return nil;
+	}
+	mode = (mode & ~0666) | ((mode & root->mode) & 0666);
+	if((fd = create(name, OEXCL|ORDWR, mode)) < 0)
+		return nil;
+
+	if((d = dirfstat(fd)) == nil
+	|| (f = createfile(root, name, d->uid, d->mode, nil)) == nil){
+		close(fd);
+		remove(name);
+		return nil;
+	}
+	free(d);
+
+	a = emalloc9p(sizeof(FileAux));
+	a->fd = fd;
+	a->mode = ORDWR;
+	a->hdr = defhdr;
+	f->aux = a;
+	f->length = 0;
+	genrandom(a->hdr.T, sizeof(a->hdr.T));
+	if(cryptfilekey(a) < 0
+	|| write(fd, &a->hdr, sizeof(a->hdr)) != sizeof(a->hdr)
+	|| cryptwrite(f, nil, 0, 0) < 0){
+		removefile(f);
+		closefile(f);
+		remove(name);
+		return nil;
+	}
+
+	return f;
+}
+
+void
+fsrpc(Req *req)
+{
+	File *f = req->fid->file;
+	int n = -1;
+
+	if((f->qid.type & QTDIR) != 0 && req->ifcall.type != Tcreate)
+		goto Done;
+	switch(req->ifcall.type){
+	case Tcreate:
+		if((f = cryptcreate(f, req->ifcall.name, req->ifcall.perm)) == nil)
+			break;
+		req->fid->file = f;
+		req->ofcall.qid = f->qid;
+		goto Done;
+	case Topen:
+		if(cryptopen(f, req->ifcall.mode) < 0)
+			break;
+		if((req->ifcall.mode & OTRUNC) != 0 && cryptsize(f, 0) < 0)
+			break;
+		goto Done;
+	case Tremove:
+		if(remove(f->name) < 0)
+			break;
+		goto Done;
+	case Twstat:
+		if(req->d.length != ~0LL && cryptsize(f, req->d.length) < 0)
+			break;
+		if(req->d.name[0] != '\0' && strcmp(req->d.name, f->name) != 0){
+			Dir nd;
+
+			nulldir(&nd);
+			nd.name = req->d.name;
+			if(dirwstat(f->name, &nd) < 0)
+				break;
+			free(f->name);
+			f->name = estrdup9p(nd.name);
+		}
+		goto Done;
+	case Tread:
+		n = cryptread(f, req->ofcall.data, req->ifcall.count, req->ifcall.offset);
+		break;
+	case Twrite:
+		if(req->ifcall.offset > f->length && cryptsize(f, req->ifcall.offset) < 0)
+			break;
+		n = cryptwrite(f, req->ifcall.data, req->ifcall.count, req->ifcall.offset);
+		break;
+	}
+	if(n < 0){
+		responderror(req);
+		return;
+	}
+	req->ofcall.count = n;
+Done:
+	respond(req, nil);
+}
+
+void
+destroyfid(Fid *fid)
+{
+	File *f = fid->file;
+
+	if(fid->omode == -1 || f == nil)
+		return;
+	if(f->ref <= 2)
+		cryptclose(f);
+	if((fid->omode & ORCLOSE) != 0 && f->parent != nil){
+		if(remove(f->name) < 0)
+			return;
+		removefile(f);
+	}
+}
+
+void
+destroyfile(File *f)
+{
+	cryptclose(f);
+	free(f->aux);
+	f->aux = nil;
+}
+
+Srv fs = {
+	.create = fsrpc,
+	.open = fsrpc,
+	.remove = fsrpc,
+	.wstat = fsrpc,
+	.read = fsrpc,
+	.write = fsrpc,
+	.destroyfid = destroyfid,
+};
+
+void
+usage(void)
+{
+	fprint(2, "%s [-v] [-m mtpt] dir\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	int fd, nd;
+	char *s;
+	Dir *d, *dd;
+
+	fmtinstall('H', encodefmt);
+
+	genrandom(defhdr.S, sizeof(defhdr.S));
+
+	ARGBEGIN {
+	case 'N':
+		defhdr.N = atoi(EARGF(usage()));
+		break;
+	case 'R':
+		defhdr.R = atoi(EARGF(usage()));
+		break;
+	case 'P':
+		defhdr.P = atoi(EARGF(usage()));
+		break;
+	case 'v':
+		verbose++;
+		break;
+	case 'D':
+		chatty9p++;
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc != 1)
+		usage();
+
+	if((d = dirstat(argv[0])) == nil)
+		sysfatal("stat: %r");
+
+	if((d->qid.type & QTDIR) != 0){
+		if(chdir(argv[0]) < 0)
+			sysfatal("chdir: %r");
+		if((fd = open(".", OREAD)) < 0)
+			sysfatal("open: %r");
+		dd = d;
+		d = nil;
+		nd = dirreadall(fd, &d);
+		close(fd);
+	} else {
+		if((s = strrchr(argv[0], '/')) != nil){
+			*s = 0;
+			if(argv[0] && chdir(argv[0]) < 0)
+				sysfatal("chdir: %r");
+		}
+		if((dd = dirstat(".")) == nil)
+			sysfatal("stat: %r");
+		nd = 1;
+	}
+	fs.tree = alloctree(dd->uid, dd->gid, dd->mode, destroyfile);
+	free(dd);
+
+	for(; nd > 0; nd--, d++){
+		FileAux *a;
+		File *f;
+
+		if((d->qid.type & QTDIR) != 0)
+			continue;
+
+		if((f = createfile(fs.tree->root, d->name, d->uid, d->mode, nil)) == nil)
+			continue;
+
+		a = emalloc9p(sizeof(FileAux));
+		a->fd = -1;
+		a->mode = 0;
+		f->aux = a;
+		if(cryptopen(f, 0) < 0){
+			fprint(2, "%s: can't decrypt %s: %r\n", argv0, f->name);
+			removefile(f);
+			closefile(f);
+			continue;
+		}
+		cryptclose(f);
+		closefile(f);
+
+		/* try to reuse the salt for the directory */
+		memmove(defhdr.S, a->hdr.S, sizeof(a->hdr.S));
+	}
+
+	if(verbose)
+		fprint(2, "%d files decrypted\n", fs.tree->root->nchild);
+
+	if(pass == nil){
+		getpass(1);
+		getkey(defhdr.N, defhdr.R, defhdr.P, defhdr.S);
+	}
+
+	postmountsrv(&fs, nil, ".", MBEFORE|MCREATE);
+	exits(nil);
+}