commit c047a4a58e9abda33685cce531c0e7386cf35290
parent 847cf8d109bd0e6ac51fa6a76b14e5e08ccd9c39
Author: sin <sin@2f30.org>
Date: Thu, 2 May 2019 14:55:03 +0100
Add initial encryption support
Diffstat:
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"