ref: d6730b9aeffeb72392c63b75af8694627fda457f
dir: /stashfs.c/
#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, "usage: %s [-Dv] [-N N] [-R R] [-P P] [-m mtpt] dir\n", argv0); exits("usage"); } void main(int argc, char *argv[]) { int fd, nd; char *mtpt, *s; Dir *d, *dd; mtpt = "."; 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 'm': mtpt = 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, mtpt, MBEFORE|MCREATE); exits(nil); }