Add initial encryption support - dedup - deduplicating backup program
 (HTM) git clone git://bitreich.org/dedup/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/dedup/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit c047a4a58e9abda33685cce531c0e7386cf35290
 (DIR) parent 847cf8d109bd0e6ac51fa6a76b14e5e08ccd9c39
 (HTM) Author: sin <sin@2f30.org>
       Date:   Thu,  2 May 2019 14:55:03 +0100
       
       Add initial encryption support
       
       Diffstat:
         M Makefile                            |      15 +++++++++++----
         M bcompress.c                         |      18 +++++++++---------
         A bencrypt.c                          |     330 +++++++++++++++++++++++++++++++
         M block.c                             |       2 +-
         M block.h                             |       6 ++++++
         M bstorage.c                          |      36 ++++++++++++++++++++++++++++---
         M config.h                            |       1 +
         M dup-check.1                         |       7 +++++--
         M dup-check.c                         |      25 ++++++++++++++++++++++++-
         M dup-gc.1                            |       7 +++++--
         M dup-gc.c                            |      26 ++++++++++++++++++++++++--
         M dup-init.1                          |      19 +++++++++++++------
         M dup-init.c                          |       6 +++++-
         A dup-keygen.1                        |      22 ++++++++++++++++++++++
         A dup-keygen.c                        |      52 +++++++++++++++++++++++++++++++
         M dup-pack.1                          |       7 +++++--
         M dup-pack.c                          |      25 ++++++++++++++++++++++++-
         M dup-rm.1                            |       7 +++++--
         M dup-rm.c                            |      24 +++++++++++++++++++++++-
         M dup-unpack.1                        |       7 +++++--
         M dup-unpack.c                        |      24 +++++++++++++++++++++++-
         A key.c                               |      39 +++++++++++++++++++++++++++++++
         A key.h                               |       3 +++
         A test006                             |      18 ++++++++++++++++++
       
       24 files changed, 686 insertions(+), 40 deletions(-)
       ---
 (DIR) 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)
        
 (DIR) 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);
        }
        
 (DIR) 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;
       +}
 (DIR) 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;
        }
 (DIR) 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);
 (DIR) 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;
        
 (DIR) 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)
 (DIR) 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.
 (DIR) 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);
 (DIR) 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
 (DIR) 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);
        
 (DIR) 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
 (DIR) 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;
 (DIR) 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 .
 (DIR) 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;
       +}
 (DIR) 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.
 (DIR) 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);
 (DIR) 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.
 (DIR) 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);
 (DIR) 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.
 (DIR) 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);
 (DIR) 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;
       +}
 (DIR) 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);
 (DIR) 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"