dedup

deduplicating backup program
git clone git://git.2f30.org/dedup
Log | Files | Refs | README | LICENSE

commit c047a4a58e9abda33685cce531c0e7386cf35290
parent 847cf8d109bd0e6ac51fa6a76b14e5e08ccd9c39
Author: sin <sin@2f30.org>
Date:   Thu,  2 May 2019 14:55:03 +0100

Add initial encryption support

Diffstat:
MMakefile | 15+++++++++++----
Mbcompress.c | 18+++++++++---------
Abencrypt.c | 330+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mblock.c | 2+-
Mblock.h | 6++++++
Mbstorage.c | 36+++++++++++++++++++++++++++++++++---
Mconfig.h | 1+
Mdup-check.1 | 7+++++--
Mdup-check.c | 25++++++++++++++++++++++++-
Mdup-gc.1 | 7+++++--
Mdup-gc.c | 26++++++++++++++++++++++++--
Mdup-init.1 | 19+++++++++++++------
Mdup-init.c | 6+++++-
Adup-keygen.1 | 22++++++++++++++++++++++
Adup-keygen.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdup-pack.1 | 7+++++--
Mdup-pack.c | 25++++++++++++++++++++++++-
Mdup-rm.1 | 7+++++--
Mdup-rm.c | 24+++++++++++++++++++++++-
Mdup-unpack.1 | 7+++++--
Mdup-unpack.c | 24+++++++++++++++++++++++-
Akey.c | 39+++++++++++++++++++++++++++++++++++++++
Akey.h | 3+++
Atest006 | 18++++++++++++++++++
24 files changed, 686 insertions(+), 40 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,13 +1,14 @@ include config.mk -BIN = dup-check dup-gc dup-init dup-pack dup-rm dup-unpack -MAN = dup-check.1 dup-gc.1 dup-init.1 dup-pack.1 dup-rm.1 dup-unpack.1 +BIN = dup-check dup-gc dup-init dup-keygen dup-pack dup-rm dup-unpack +MAN = dup-check.1 dup-gc.1 dup-init.1 dup-keygen.1 dup-pack.1 dup-rm.1 dup-unpack.1 HDR = \ arg.h \ block.h \ chunker.h \ config.h \ + key.h \ queue.h \ snap.h \ tree.h \ @@ -15,9 +16,11 @@ HDR = \ COMMOBJ = \ bcompat.o \ bcompress.o \ + bencrypt.o \ block.o \ bstorage.o \ chunker.o \ + key.o \ misc.o \ pack.o \ snap.o \ @@ -26,6 +29,7 @@ COMMOBJ = \ DCHECKOBJ = $(COMMOBJ) dup-check.o DGCOBJ = $(COMMOBJ) dup-gc.o DINITOBJ = $(COMMOBJ) dup-init.o +DKEYGENOBJ = $(COMMOBJ) dup-keygen.o DPACKOBJ = $(COMMOBJ) dup-pack.o DRMOBJ = $(COMMOBJ) dup-rm.o DUNPACKOBJ = $(COMMOBJ) dup-unpack.o @@ -34,10 +38,10 @@ LDLIBS = -lsnappy -lsodium all: $(BIN) -$(DCHECKOBJ) $(DGCOBJ) $(DINITOBJ) $(DPACKOBJ) $(DRMOBJ) $(DUNPACKOBJ): $(HDR) +$(DCHECKOBJ) $(DGCOBJ) $(DINITOBJ) $(DKEYGENOBJ) $(DPACKOBJ) $(DRMOBJ) $(DUNPACKOBJ): $(HDR) clean: - rm -f $(DCHECKOBJ) $(DGCOBJ) $(DINITOBJ) $(DPACKOBJ) $(DRMOBJ) $(DUNPACKOBJ) $(BIN) + rm -f $(DCHECKOBJ) $(DGCOBJ) $(DINITOBJ) $(DKEYGENOBJ) $(DPACKOBJ) $(DRMOBJ) $(DUNPACKOBJ) $(BIN) rm -rf dedup-$(VERSION) dedup-$(VERSION).tar.gz install: all @@ -72,6 +76,9 @@ dup-gc: $(DGCOBJ) dup-init: $(DINITOBJ) $(CC) -o $@ $(DINITOBJ) $(LDFLAGS) $(LDLIBS) +dup-keygen: $(DKEYGENOBJ) + $(CC) -o $@ $(DKEYGENOBJ) $(LDFLAGS) $(LDLIBS) + dup-pack: $(DPACKOBJ) $(CC) -o $@ $(DPACKOBJ) $(LDFLAGS) $(LDLIBS) diff --git a/bcompress.c b/bcompress.c @@ -105,7 +105,7 @@ bccreat(struct bctx *bctx, char *path, int mode, struct bparam *bpar) cctx = bctx->cctx; cctx->type = type; - bops = bstorageops(); + bops = bencryptops(); if (bops->creat(bctx, path, mode, bpar) < 0) { free(cctx); return -1; @@ -124,7 +124,7 @@ bcopen(struct bctx *bctx, char *path, int flags, int mode, struct bparam *bpar) return -1; cctx = bctx->cctx; - bops = bstorageops(); + bops = bencryptops(); if (bops->open(bctx, path, flags, mode, bpar) < 0) { free(cctx); return -1; @@ -184,7 +184,7 @@ bcput(struct bctx *bctx, void *buf, size_t n, unsigned char *md) cd.size = cn; packcd(cbuf, &cd); - bops = bstorageops(); + bops = bencryptops(); if (bops->put(bctx, cbuf, CDSIZE + cn, md) < 0) { free(cbuf); return -1; @@ -215,7 +215,7 @@ bcget(struct bctx *bctx, unsigned char *md, void *buf, size_t *n) return -1; /* Read compressed block */ - bops = bstorageops(); + bops = bencryptops(); if (bops->get(bctx, md, cbuf, &size) < 0) { free(cbuf); return -1; @@ -262,7 +262,7 @@ bcget(struct bctx *bctx, unsigned char *md, void *buf, size_t *n) static int bcrm(struct bctx *bctx, unsigned char *md) { - struct bops *bops = bstorageops(); + struct bops *bops = bencryptops(); return bops->rm(bctx, md); } @@ -270,7 +270,7 @@ bcrm(struct bctx *bctx, unsigned char *md) static int bcgc(struct bctx *bctx) { - struct bops *bops = bstorageops(); + struct bops *bops = bencryptops(); return bops->gc(bctx); } @@ -278,7 +278,7 @@ bcgc(struct bctx *bctx) static int bccheck(struct bctx *bctx, unsigned char *md) { - struct bops *bops = bstorageops(); + struct bops *bops = bencryptops(); return bops->check(bctx, md); @@ -287,7 +287,7 @@ bccheck(struct bctx *bctx, unsigned char *md) static int bcsync(struct bctx *bctx) { - struct bops *bops = bstorageops(); + struct bops *bops = bencryptops(); return bops->sync(bctx); } @@ -299,7 +299,7 @@ bcclose(struct bctx *bctx) struct bops *bops; free(cctx); - bops = bstorageops(); + bops = bencryptops(); return bops->close(bctx); } diff --git a/bencrypt.c b/bencrypt.c @@ -0,0 +1,330 @@ +/* Encryption layer implementation */ +#include <sys/types.h> +#include <sys/stat.h> + +#include <assert.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sodium.h> + +#include "block.h" +#include "config.h" + +#define EDNONETYPE 0x300 +#define EDCHACHATYPE 0x301 +#define EDSIZE (8 + 8 + 24) + +extern int pack(unsigned char *, char *, ...); +extern int unpack(unsigned char *, char *, ...); + +static int becreat(struct bctx *bctx, char *path, int mode, struct bparam *bpar); +static int beopen(struct bctx *bctx, char *path, int flags, int mode, struct bparam *bpar); +static int beput(struct bctx *bctx, void *buf, size_t n, unsigned char *md); +static int beget(struct bctx *bctx, unsigned char *md, void *buf, size_t *n); +static int berm(struct bctx *bctx, unsigned char *md); +static int begc(struct bctx *bctx); +static int becheck(struct bctx *bctx, unsigned char *md); +static int besync(struct bctx *bctx); +static int beclose(struct bctx *bctx); + +static struct bops bops = { + .creat = becreat, + .open = beopen, + .put = beput, + .get = beget, + .rm = berm, + .gc = begc, + .check = becheck, + .sync = besync, + .close = beclose, +}; + +/* Encryption layer context */ +struct ectx { + int type; /* encryption algorithm type for new blocks */ + unsigned char key[KEYSIZE]; /* secret key */ +}; + +/* Encryption descriptor */ +struct ed { + uint16_t type; /* encryption algorithm type */ + uint8_t reserved[6]; + uint64_t size; + unsigned char nonce[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES]; +}; + +/* Read encryption descriptor */ +static int +unpacked(void *buf, struct ed *ed) +{ + int n; + + n = unpack(buf, "s'6q'24", + &ed->type, + ed->reserved, + &ed->size, + ed->nonce); + + assert(n == EDSIZE); + return n; +} + +/* Write encryption descriptor */ +static int +packed(void *buf, struct ed *ed) +{ + int n; + + n = pack(buf, "s'6q'24", + ed->type, + ed->reserved, + ed->size, + ed->nonce); + + assert(n == EDSIZE); + return n; +} + +static int +becreat(struct bctx *bctx, char *path, int mode, struct bparam *bpar) +{ + struct ectx *ectx; + struct bops *bops; + int type; + + if (strcmp(bpar->ealgo, "none") == 0) + type = EDNONETYPE; + else if (strcmp(bpar->ealgo, "XChaCha20-Poly1305") == 0) + type = EDCHACHATYPE; + else + return -1; + + if (type != EDNONETYPE && bpar->key == NULL) + return -1; + + if (sodium_init() < 0) + return -1; + + bctx->ectx = calloc(1, sizeof(struct ectx)); + if (bctx->ectx == NULL) + return -1; + ectx = bctx->ectx; + ectx->type = type; + if (bpar->key != NULL) + memcpy(ectx->key, bpar->key, KEYSIZE); + + bops = bstorageops(); + if (bops->creat(bctx, path, mode, bpar) < 0) { + free(ectx); + return -1; + } + return 0; +} + +static int +beopen(struct bctx *bctx, char *path, int flags, int mode, struct bparam *bpar) +{ + struct ectx *ectx; + struct bops *bops; + + bctx->ectx = calloc(1, sizeof(struct ectx)); + if (bctx->ectx == NULL) + return -1; + ectx = bctx->ectx; + if (bpar->key != NULL) + memcpy(ectx->key, bpar->key, KEYSIZE); + + bops = bstorageops(); + if (bops->open(bctx, path, flags, mode, bpar) < 0) { + free(ectx); + return -1; + } + + if (strcmp(bpar->ealgo, "none") == 0) + ectx->type = EDNONETYPE; + else if (strcmp(bpar->ealgo, "XChaCha20-Poly1305") == 0) + ectx->type = EDCHACHATYPE; + else { + bops->close(bctx); + free(ectx); + return -1; + } + + if (ectx->type != EDNONETYPE && bpar->key == NULL) { + bops->close(bctx); + free(ectx); + return -1; + } + + return 0; +} + +static int +beput(struct bctx *bctx, void *buf, size_t n, unsigned char *md) +{ + struct ectx *ectx; + struct bops *bops; + struct ed ed; + char *ebuf; + size_t en; + + ectx = bctx->ectx; + if (ectx->type == EDNONETYPE) + en = n; + else if (ectx->type == EDCHACHATYPE) + en = n + crypto_aead_xchacha20poly1305_ietf_ABYTES; + else + return -1; + + ebuf = malloc(EDSIZE + en); + if (ebuf == NULL) + return -1; + + ed.type = ectx->type; + ed.size = en; + if (ectx->type == EDNONETYPE) { + memset(ed.nonce, 0, sizeof(ed.nonce)); + } else if (ectx->type == EDCHACHATYPE) { + randombytes_buf(ed.nonce, sizeof(ed.nonce)); + } else { + free(ebuf); + return -1; + } + packed(ebuf, &ed); + + if (ectx->type == EDNONETYPE) { + memcpy(&ebuf[EDSIZE], buf, en); + } else if (ectx->type == EDCHACHATYPE) { + unsigned long long elen; + + crypto_aead_xchacha20poly1305_ietf_encrypt(&ebuf[EDSIZE], &elen, + buf, n, ebuf, EDSIZE, NULL, + ed.nonce, ectx->key); + assert(elen == en); + } else { + free(ebuf); + return -1; + } + + bops = bstorageops(); + if (bops->put(bctx, ebuf, EDSIZE + en, md) < 0) { + free(ebuf); + return -1; + } + + free(ebuf); + return ed.size; +} + +static int +beget(struct bctx *bctx, unsigned char *md, void *buf, size_t *n) +{ + struct bops *bops; + struct ed ed; + char *ebuf; + size_t dn, size; + + size = EDSIZE + *n + crypto_aead_xchacha20poly1305_ietf_ABYTES; + ebuf = malloc(size); + if (ebuf == NULL) + return -1; + + bops = bstorageops(); + if (bops->get(bctx, md, ebuf, &size) < 0) { + free(ebuf); + return -1; + } + + unpacked(ebuf, &ed); + if (ed.type == EDNONETYPE) { + dn = ed.size; + if (*n < dn) { + free(ebuf); + return -1; + } + memcpy(buf, &ebuf[EDSIZE], dn); + } else if (ed.type == EDCHACHATYPE) { + struct ectx *ectx; + unsigned long long dlen; + + dn = ed.size - crypto_aead_xchacha20poly1305_ietf_ABYTES; + if (*n < dn) { + free(ebuf); + return -1; + } + + ectx = bctx->ectx; + if (crypto_aead_xchacha20poly1305_ietf_decrypt(buf, &dlen, + NULL, + &ebuf[EDSIZE], ed.size, + ebuf, EDSIZE, + ed.nonce, ectx->key) != 0) { + free(ebuf); + return -1; + } + + assert(dn == dlen); + } else { + free(ebuf); + return -1; + } + + free(ebuf); + *n = dn; + return 0; +} + +static int +berm(struct bctx *bctx, unsigned char *md) +{ + struct bops *bops = bstorageops(); + + return bops->rm(bctx, md); +} + +static int +begc(struct bctx *bctx) +{ + struct bops *bops = bstorageops(); + + return bops->gc(bctx); +} + +static int +becheck(struct bctx *bctx, unsigned char *md) +{ + struct bops *bops = bstorageops(); + + return bops->check(bctx, md); + +} + +static int +besync(struct bctx *bctx) +{ + struct bops *bops = bstorageops(); + + return bops->sync(bctx); +} + +static int +beclose(struct bctx *bctx) +{ + struct ectx *ectx = bctx->ectx; + struct bops *bops; + + free(ectx); + bops = bstorageops(); + return bops->close(bctx); +} + +struct bops * +bencryptops(void) +{ + return &bops; +} diff --git a/block.c b/block.c @@ -145,7 +145,7 @@ bclose(struct bctx *bctx) struct bparam * bparamdef(void) { - static struct bparam bpar = { .calgo = "snappy" }; + static struct bparam bpar = { .calgo = "snappy", .ealgo = "none" }; return &bpar; } diff --git a/block.h b/block.h @@ -6,11 +6,14 @@ enum { struct bctx { void *gctx; /* generic layer context (unused) */ void *cctx; /* compression layer context */ + void *ectx; /* encryption layer context */ void *sctx; /* storage layer context */ }; struct bparam { char *calgo; + char *ealgo; + unsigned char *key; }; /* @@ -46,5 +49,8 @@ extern int punchhole(int, off_t, off_t); /* bcompress.c */ extern struct bops *bcompressops(void); +/* bencrypt.c */ +struct bops *bencryptops(void); + /* bstorage.c */ extern struct bops *bstorageops(void); diff --git a/bstorage.c b/bstorage.c @@ -35,6 +35,10 @@ #define VMINMASK 0xff #define VMAJSHIFT 8 #define VMAJMASK 0xff +#define EALGOSHIFT 19 +#define EALGOMASK 0x7 +#define ENONETYPE 0 +#define ECHACHATYPE 1 #define CALGOSHIFT 16 #define CALGOMASK 0x7 #define CNONETYPE 0 @@ -320,6 +324,17 @@ bscreat(struct bctx *bctx, char *path, int mode, struct bparam *bpar) return -1; } + /* Set encryption type */ + if (strcmp(bpar->ealgo, "none") == 0) { + bhdr->flags |= ENONETYPE << EALGOSHIFT; + } else if (strcmp(bpar->ealgo, "XChaCha20-Poly1305") == 0) { + bhdr->flags |= ECHACHATYPE << EALGOSHIFT; + } else { + free(sctx); + close(fd); + return -1; + } + bhdr->nbd = 0; sctx->fd = fd; @@ -338,7 +353,7 @@ bsopen(struct bctx *bctx, char *path, int flags, int mode, struct bparam *bpar) { struct sctx *sctx; struct bhdr *bhdr; - int fd, calgo; + int fd, algo; switch (flags) { case B_READ: @@ -391,8 +406,8 @@ bsopen(struct bctx *bctx, char *path, int flags, int mode, struct bparam *bpar) } /* Populate bparam compression algo */ - calgo = (bhdr->flags >> CALGOSHIFT) & CALGOMASK; - switch (calgo) { + algo = (bhdr->flags >> CALGOSHIFT) & CALGOMASK; + switch (algo) { case CNONETYPE: bpar->calgo = "none"; break; @@ -405,6 +420,21 @@ bsopen(struct bctx *bctx, char *path, int flags, int mode, struct bparam *bpar) return -1; } + /* Populate bparam encryption algo */ + algo = (bhdr->flags >> EALGOSHIFT) & EALGOMASK; + switch (algo) { + case ENONETYPE: + bpar->ealgo = "none"; + break; + case ECHACHATYPE: + bpar->ealgo = "XChaCha20-Poly1305"; + break; + default: + free(sctx); + close(fd); + return -1; + } + sctx->fd = fd; sctx->rdonly = flags == O_RDONLY; diff --git a/config.h b/config.h @@ -1,6 +1,7 @@ #define ARCHIVEPATH "archive" #define STORAGEPATH "storage" #define MDSIZE 32 +#define KEYSIZE 32 #define BSIZEAVG ((size_t)(1ul << 21)) #define BSIZEMIN ((size_t)524288) #define BSIZEMAX ((size_t)8388608) diff --git a/dup-check.1 b/dup-check.1 @@ -1,4 +1,4 @@ -.Dd April 25, 2019 +.Dd May 2, 2019 .Dt DUP-CHECK 1 .Os .Sh NAME @@ -7,13 +7,16 @@ .Sh SYNOPSIS .Nm dup-check .Op Fl v +.Op Fl k Ar keyfile .Op Fl r Ar repo .Ar name .Sh DESCRIPTION .Nm checks that a snapshot is internally consistent. .Sh OPTIONS -.Bl -tag -width "-r repo" +.Bl -tag -width "-k keyfile" +.It Fl k Ar keyfile +Path to encryption key. .It Fl r Ar repo Repository directory. By default the current working directory is used. diff --git a/dup-check.c b/dup-check.c @@ -2,13 +2,16 @@ #include <sys/stat.h> #include <err.h> +#include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> +#include <unistd.h> #include "arg.h" #include "block.h" #include "config.h" +#include "key.h" #include "snap.h" int verbose; @@ -32,7 +35,7 @@ check(struct sctx *sctx, struct bctx *bctx) static void usage(void) { - fprintf(stderr, "usage: %s [-v] [-r repo] name\n", argv0); + fprintf(stderr, "usage: %s [-v] [-k keyfile] [-r repo] name\n", argv0); exit(1); } @@ -41,12 +44,17 @@ main(int argc, char *argv[]) { char spath[PATH_MAX]; char bpath[PATH_MAX]; + unsigned char key[KEYSIZE]; struct sctx *sctx; struct bctx *bctx; struct bparam bpar; + char *keyfile = NULL; char *repo = "."; ARGBEGIN { + case 'k': + keyfile = EARGF(usage()); + break; case 'r': repo = EARGF(usage()); break; @@ -60,6 +68,21 @@ main(int argc, char *argv[]) if (argc != 1) usage(); + if (keyfile != NULL) { + int fd; + + fd = open(keyfile, O_RDONLY); + if (fd < 0) + err(1, "open: %s", keyfile); + if (loadkey(fd, key, sizeof(key)) < 0) + errx(1, "loadkey: failed"); + bpar.key = key; + if (close(fd) < 0) + err(1, "close: %s", keyfile); + } else { + bpar.key = NULL; + } + if (snprintf(spath, sizeof(spath), "%s/archive/%s", repo, argv[0]) >= sizeof(spath)) errx(1, "snprintf: %s: path too long", spath); diff --git a/dup-gc.1 b/dup-gc.1 @@ -1,4 +1,4 @@ -.Dd April 26, 2019 +.Dd May 2, 2019 .Dt DUP-GC 1 .Os .Sh NAME @@ -7,6 +7,7 @@ .Sh SYNOPSIS .Nm dup-gc .Op Fl v +.Op Fl k Ar keyfile .Op repo .Sh DESCRIPTION .Nm @@ -15,7 +16,9 @@ If no .Ar repo is specified the current working directory is used. .Sh OPTIONS -.Bl -tag -width "-v" +.Bl -tag -width "-k keyfile" +.It Fl k Ar keyfile +Path to encryption key. .It Fl v Enable verbose mode. .El diff --git a/dup-gc.c b/dup-gc.c @@ -2,13 +2,15 @@ #include <sys/stat.h> #include <err.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "arg.h" -#include "config.h" #include "block.h" +#include "config.h" +#include "key.h" #include "snap.h" int verbose; @@ -17,18 +19,23 @@ char *argv0; static void usage(void) { - fprintf(stderr, "usage: %s [repo]\n", argv0); + fprintf(stderr, "usage: %s [-v] [-k keyfile] [repo]\n", argv0); exit(1); } int main(int argc, char *argv[]) { + unsigned char key[KEYSIZE]; struct bctx *bctx; /* block context */ struct bparam bpar; + char *keyfile = NULL; char *repo; ARGBEGIN { + case 'k': + keyfile = EARGF(usage()); + break; case 'v': verbose++; break; @@ -47,6 +54,21 @@ main(int argc, char *argv[]) usage(); }; + if (keyfile != NULL) { + int fd; + + fd = open(keyfile, O_RDONLY); + if (fd < 0) + err(1, "open: %s", keyfile); + if (loadkey(fd, key, sizeof(key)) < 0) + errx(1, "loadkey: failed"); + bpar.key = key; + if (close(fd) < 0) + err(1, "close: %s", keyfile); + } else { + bpar.key = NULL; + } + if (chdir(repo) < 0) err(1, "chdir: %s", repo); diff --git a/dup-init.1 b/dup-init.1 @@ -1,4 +1,4 @@ -.Dd May 1, 2019 +.Dd May 2, 2019 .Dt DUP-INIT 1 .Os .Sh NAME @@ -7,7 +7,8 @@ .Sh SYNOPSIS .Nm dup-init .Op Fl v -.Op Fl Z Ar compressor +.Op Fl E Ar algo +.Op Fl Z Ar algo .Op repo .Sh DESCRIPTION .Nm @@ -16,11 +17,17 @@ If no .Ar repo is specified the current working directory is used. .Sh OPTIONS -.Bl -tag -width "-Z compressor" -.It Fl Z Ar compressor -The compressor function used to compress the blocks +.Bl -tag -width "-Z algo" +.It Fl E Ar algo +The encryption algorithm used to encrypt the blocks in the store. -The supported compressor functions are none and snappy. +The supported encryption algorithms are none and XChaCha20-Poly1305. +This flag only has an effect when initializing the repository. +By default none is used. +.It Fl Z Ar algo +The compressor algorithm used to compress the blocks +in the store. +The supported compressor algorithms are none and snappy. This flag only has an effect when initializing the repository. By default snappy is used. .It Fl v diff --git a/dup-init.c b/dup-init.c @@ -17,7 +17,7 @@ char *argv0; static void usage(void) { - fprintf(stderr, "usage: %s [-v] [-Z compressor] [repo]\n", argv0); + fprintf(stderr, "usage: %s [-v] [-E algo] [-Z algo] [repo]\n", argv0); exit(1); } @@ -29,8 +29,12 @@ main(int argc, char *argv[]) char *repo; bpar.calgo = bparamdef()->calgo; + bpar.ealgo = bparamdef()->ealgo; ARGBEGIN { + case 'E': + bpar.ealgo = EARGF(usage()); + break; case 'Z': bpar.calgo = EARGF(usage()); break; diff --git a/dup-keygen.1 b/dup-keygen.1 @@ -0,0 +1,22 @@ +.Dd May 2, 2019 +.Dt DUP-KEYGEN 1 +.Os +.Sh NAME +.Nm dup-keygen +.Nd Generate dedup encryption key +.Sh SYNOPSIS +.Nm dup-keygen +.Op Fl v +.Ar keyfile +.Sh DESCRIPTION +.Nm +generates a 256-bit encryption key file. +This key is used when operating on an encrypted dedup repository. +.Sh OPTIONS +.Bl -tag -width "keyfile" +.It Fl v +Enable verbose mode. +.El +.Sh AUTHORS +.An Dimitris Papastamos Aq Mt sin@2f30.org , +.An z3bra Aq Mt contactatz3bradotorg . diff --git a/dup-keygen.c b/dup-keygen.c @@ -0,0 +1,52 @@ +#include <sys/types.h> +#include <sys/stat.h> + +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "arg.h" +#include "config.h" +#include "key.h" + +int verbose; +char *argv0; + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-v] keyfile\n", argv0); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int fd; + unsigned char key[KEYSIZE]; + + ARGBEGIN { + case 'v': + verbose++; + break; + default: + usage(); + } ARGEND + + if (argc != 1) + usage(); + + fd = open(argv[0], O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd < 0) + err(1, "open: %s", argv[0]); + if (keygen(key, sizeof(key)) < 0) + errx(1, "keygen: failed"); + if (savekey(fd, key, sizeof(key)) < 0) + errx(1, "savekey: failed"); + fsync(fd); + if (close(fd) < 0) + err(1, "close: %s", argv[0]); + return 0; +} diff --git a/dup-pack.1 b/dup-pack.1 @@ -1,4 +1,4 @@ -.Dd April 25, 2019 +.Dd May 2, 2019 .Dt DUP-PACK 1 .Os .Sh NAME @@ -7,6 +7,7 @@ .Sh SYNOPSIS .Nm dup-pack .Op Fl v +.Op Fl k Ar keyfile .Op Fl r Ar repo .Ar name .Sh DESCRIPTION @@ -23,7 +24,9 @@ a directory tree, should be used and piped into .Nm . .Sh OPTIONS -.Bl -tag -width "-r repo" +.Bl -tag -width "-k keyfile" +.It Fl k Ar keyfile +Path to encryption key. .It Fl r Ar repo Repository directory. By default the current working directory is used. diff --git a/dup-pack.c b/dup-pack.c @@ -2,14 +2,17 @@ #include <sys/stat.h> #include <err.h> +#include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> +#include <unistd.h> #include "arg.h" #include "block.h" #include "chunker.h" #include "config.h" +#include "key.h" #include "snap.h" int verbose; @@ -50,7 +53,7 @@ pack(struct sctx *sctx, struct bctx *bctx) static void usage(void) { - fprintf(stderr, "usage: %s [-v] [-r repo] name\n", argv0); + fprintf(stderr, "usage: %s [-v] [-k keyfile] [-r repo] name\n", argv0); exit(1); } @@ -59,12 +62,17 @@ main(int argc, char *argv[]) { char spath[PATH_MAX]; char bpath[PATH_MAX]; + unsigned char key[KEYSIZE]; struct sctx *sctx; struct bctx *bctx; struct bparam bpar; + char *keyfile = NULL; char *repo = "."; ARGBEGIN { + case 'k': + keyfile = EARGF(usage()); + break; case 'r': repo = EARGF(usage()); break; @@ -78,6 +86,21 @@ main(int argc, char *argv[]) if (argc != 1) usage(); + if (keyfile != NULL) { + int fd; + + fd = open(keyfile, O_RDONLY); + if (fd < 0) + err(1, "open: %s", keyfile); + if (loadkey(fd, key, sizeof(key)) < 0) + errx(1, "loadkey: failed"); + bpar.key = key; + if (close(fd) < 0) + err(1, "close: %s", keyfile); + } else { + bpar.key = NULL; + } + if (snprintf(spath, sizeof(spath), "%s/archive/%s", repo, argv[0]) >= sizeof(spath)) errx(1, "snprintf: %s: path too long", spath); diff --git a/dup-rm.1 b/dup-rm.1 @@ -1,4 +1,4 @@ -.Dd April 27, 2019 +.Dd May 2, 2019 .Dt DUP-RM 1 .Os .Sh NAME @@ -7,6 +7,7 @@ .Sh SYNOPSIS .Nm dup-rm .Op Fl v +.Op Fl k Ar keyfile .Op Fl r Ar repo .Ar name .Sh DESCRIPTION @@ -14,7 +15,9 @@ removes the snapshot specified by .Ar name . .Sh OPTIONS -.Bl -tag -width "-r repo" +.Bl -tag -width "-k keyfile" +.It Fl k Ar keyfile +Path to encryption key. .It Fl r Ar repo Repository directory. By default the current working directory is used. diff --git a/dup-rm.c b/dup-rm.c @@ -2,6 +2,7 @@ #include <sys/stat.h> #include <err.h> +#include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> @@ -10,6 +11,7 @@ #include "arg.h" #include "block.h" #include "config.h" +#include "key.h" #include "snap.h" int verbose; @@ -33,7 +35,7 @@ rm(struct sctx *sctx, struct bctx *bctx) static void usage(void) { - fprintf(stderr, "usage: %s [-v] [-r repo] name\n", argv0); + fprintf(stderr, "usage: %s [-v] [-k keyfile] [-r repo] name\n", argv0); exit(1); } @@ -42,12 +44,17 @@ main(int argc, char *argv[]) { char spath[PATH_MAX]; char bpath[PATH_MAX]; + unsigned char key[KEYSIZE]; struct sctx *sctx; struct bctx *bctx; struct bparam bpar; + char *keyfile = NULL; char *repo = "."; ARGBEGIN { + case 'k': + keyfile = EARGF(usage()); + break; case 'r': repo = EARGF(usage()); break; @@ -61,6 +68,21 @@ main(int argc, char *argv[]) if (argc != 1) usage(); + if (keyfile != NULL) { + int fd; + + fd = open(keyfile, O_RDONLY); + if (fd < 0) + err(1, "open: %s", keyfile); + if (loadkey(fd, key, sizeof(key)) < 0) + errx(1, "loadkey: failed"); + bpar.key = key; + if (close(fd) < 0) + err(1, "close: %s", keyfile); + } else { + bpar.key = NULL; + } + if (snprintf(spath, sizeof(spath), "%s/archive/%s", repo, argv[0]) >= sizeof(spath)) errx(1, "snprintf: %s: path too long", spath); diff --git a/dup-unpack.1 b/dup-unpack.1 @@ -1,4 +1,4 @@ -.Dd April 25, 2019 +.Dd May 2, 2019 .Dt DUP-UNPACK 1 .Os .Sh NAME @@ -7,6 +7,7 @@ .Sh SYNOPSIS .Nm dup-unpack .Op Fl v +.Op Fl k Ar keyfile .Op Fl r Ar repo .Ar name .Sh DESCRIPTION @@ -15,7 +16,9 @@ extracts the snapshot specified by .Ar name from the dedup repository and writes the data to stdout. .Sh OPTIONS -.Bl -tag -width "-r repo" +.Bl -tag -width "-k keyfile" +.It Fl k Ar keyfile +Path to encryption key. .It Fl r Ar repo Repository directory. By default the current working directory is used. diff --git a/dup-unpack.c b/dup-unpack.c @@ -2,6 +2,7 @@ #include <sys/stat.h> #include <err.h> +#include <fcntl.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> @@ -10,6 +11,7 @@ #include "arg.h" #include "block.h" #include "config.h" +#include "key.h" #include "snap.h" extern ssize_t xwrite(int, void *, size_t); @@ -48,7 +50,7 @@ unpack(struct sctx *sctx, struct bctx *bctx) static void usage(void) { - fprintf(stderr, "usage: %s [-v] [-r repo] name\n", argv0); + fprintf(stderr, "usage: %s [-v] [-k keyfile] [-r repo] name\n", argv0); exit(1); } @@ -57,12 +59,17 @@ main(int argc, char *argv[]) { char spath[PATH_MAX]; char bpath[PATH_MAX]; + unsigned char key[KEYSIZE]; struct sctx *sctx; struct bctx *bctx; struct bparam bpar; + char *keyfile; char *repo = "."; ARGBEGIN { + case 'k': + keyfile = EARGF(usage()); + break; case 'r': repo = EARGF(usage()); break; @@ -76,6 +83,21 @@ main(int argc, char *argv[]) if (argc != 1) usage(); + if (keyfile != NULL) { + int fd; + + fd = open(keyfile, O_RDONLY); + if (fd < 0) + err(1, "open: %s", keyfile); + if (loadkey(fd, key, sizeof(key)) < 0) + errx(1, "loadkey: failed"); + bpar.key = key; + if (close(fd) < 0) + err(1, "close: %s", keyfile); + } else { + bpar.key = NULL; + } + if (snprintf(spath, sizeof(spath), "%s/archive/%s", repo, argv[0]) >= sizeof(spath)) errx(1, "snprintf: %s: path too long", spath); diff --git a/key.c b/key.c @@ -0,0 +1,39 @@ +#include <assert.h> +#include <unistd.h> + +#include <sodium.h> + +#include "config.h" + +int +keygen(unsigned char *key, size_t n) +{ + if (n < crypto_aead_xchacha20poly1305_ietf_KEYBYTES) + return -1; + assert(KEYSIZE == crypto_aead_xchacha20poly1305_ietf_KEYBYTES); + if (sodium_init() < 0) + return -1; + crypto_aead_xchacha20poly1305_ietf_keygen(key); +} + +int +savekey(int fd, unsigned char *key, size_t n) +{ + if (n < crypto_aead_xchacha20poly1305_ietf_KEYBYTES) + return -1; + assert(KEYSIZE == crypto_aead_xchacha20poly1305_ietf_KEYBYTES); + if (write(fd, key, KEYSIZE) != KEYSIZE) + return -1; + return 0; +} + +int +loadkey(int fd, unsigned char *key, size_t n) +{ + if (n < crypto_aead_xchacha20poly1305_ietf_KEYBYTES) + return -1; + assert(KEYSIZE == crypto_aead_xchacha20poly1305_ietf_KEYBYTES); + if (read(fd, key, KEYSIZE) != KEYSIZE) + return -1; + return 0; +} diff --git a/key.h b/key.h @@ -0,0 +1,3 @@ +extern int keygen(unsigned char *key, size_t n); +extern int savekey(int fd, unsigned char *key, size_t n); +extern int loadkey(int fd, unsigned char *key, size_t n); diff --git a/test006 b/test006 @@ -0,0 +1,18 @@ +#!/bin/sh +set -ex + +keyfile=`mktemp -u` +repo=`mktemp -d` +data=`mktemp` +dd if=/dev/urandom of="$data" bs=1M count=64 +./dup-keygen "$keyfile" +./dup-init -E XChaCha20-Poly1305 "$repo" +./dup-pack -k "$keyfile" -r "$repo" snap0 < "$data" +./dup-gc -k "$keyfile" "$repo" +./dup-rm -k "$keyfile" -r "$repo" snap0 < "$data" +./dup-pack -k "$keyfile" -r "$repo" snap0 < "$data" +./dup-gc -k "$keyfile" "$repo" +sum0=`sha1sum "$data" | awk '{print $1}'` +sum1=`./dup-unpack -k "$keyfile" -r "$repo" snap0 | sha1sum | awk '{print $1}'` +[ "$sum0" = "$sum1" ] +rm -rf "$keyfile" "$repo" "$data"