dedup

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

commit eba08d1ff6618f1b3277dea6990a0afde7a0bc11
parent 042e687f46e7c334def143a387c332e6884cd078
Author: sin <sin@2f30.org>
Date:   Sun, 19 May 2019 19:26:12 +0300

Add support for storing/retrieving an encrypted seed

The seed is XOR-ed with the initial state of the chunker to mitigate
against fingerprinting attacks.

Diffstat:
Mdup-gc.c | 1+
Mdup-init.c | 34++++++++++++++++++++++------------
Mdup-keygen.c | 1+
Mdup-pack.c | 3++-
Mdup-rm.c | 1+
Mdup-unpack.c | 1+
Mstate.c | 63++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mstate.h | 9+++++----
8 files changed, 93 insertions(+), 20 deletions(-)

diff --git a/dup-gc.c b/dup-gc.c @@ -4,6 +4,7 @@ #include <err.h> #include <fcntl.h> #include <limits.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> diff --git a/dup-init.c b/dup-init.c @@ -4,11 +4,14 @@ #include <err.h> #include <fcntl.h> #include <limits.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> +#include <sodium.h> + #include "arg.h" #include "block.h" #include "config.h" @@ -40,21 +43,21 @@ savestate(char *repo) } static void -loadkey(char *keyfile) +loadkey(char *keyf) { int fd; - if (keyfile == NULL) + if (keyf == NULL) return; - fd = open(keyfile, O_RDONLY); + fd = open(keyf, O_RDONLY); if (fd < 0) - err(1, "open: %s", keyfile); + err(1, "open: %s", keyf); if (readkey(fd, param.key, sizeof(param.key)) < 0) - printerr("readkey: %s", keyfile); + printerr("readkey: %s", keyf); param.keyloaded = 1; if (close(fd) < 0) - err(1, "close: %s", keyfile); + err(1, "close: %s", keyf); } static void @@ -70,7 +73,7 @@ main(int argc, char *argv[]) char spath[PATH_MAX]; char bpath[PATH_MAX]; struct bctx *bctx; - char *keyfile = NULL; + char *keyf = NULL; char *repo; int lfd; @@ -79,7 +82,7 @@ main(int argc, char *argv[]) ARGBEGIN { case 'k': - keyfile = EARGF(usage()); + keyf = EARGF(usage()); break; case 'E': param.ealgo = EARGF(usage()); @@ -105,9 +108,16 @@ main(int argc, char *argv[]) usage(); }; - if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0 && - keyfile == NULL) - errx(1, "expected encryption key"); + if (strcasecmp(param.ealgo, "none") == 0) { + param.seed = 0; + } else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) { + if (keyf == NULL) + errx(1, "expected encryption key"); + param.seed = randombytes_uniform(0xffffffff); + } + + if (sodium_init() < 0) + errx(1, "sodium_init: failed"); if (snprintf(spath, sizeof(spath), "%s/%s", repo, ARCHIVEPATH) >= sizeof(spath)) @@ -125,7 +135,7 @@ main(int argc, char *argv[]) if (mkdir(spath, 0700) < 0) err(1, "mkdir: %s", spath); - loadkey(keyfile); + loadkey(keyf); savestate(repo); if (bcreat(bpath, 0600, &bctx) < 0) diff --git a/dup-keygen.c b/dup-keygen.c @@ -3,6 +3,7 @@ #include <err.h> #include <fcntl.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> diff --git a/dup-pack.c b/dup-pack.c @@ -63,7 +63,8 @@ pack(struct sctx *sctx, struct bctx *bctx) { struct chunker *c; - if ((c = copen(0, BSIZEMIN, BSIZEMAX, HMASKBITS, WINSIZE, 0)) == NULL) + if ((c = copen(0, BSIZEMIN, BSIZEMAX, HMASKBITS, WINSIZE, + param.seed)) == NULL) printerr("copen"); while (cfill(c) > 0) { diff --git a/dup-rm.c b/dup-rm.c @@ -4,6 +4,7 @@ #include <err.h> #include <fcntl.h> #include <limits.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> diff --git a/dup-unpack.c b/dup-unpack.c @@ -4,6 +4,7 @@ #include <err.h> #include <fcntl.h> #include <limits.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> diff --git a/state.c b/state.c @@ -6,6 +6,8 @@ #include <string.h> #include <strings.h> +#include <sodium.h> + #include "config.h" #include "misc.h" #include "state.h" @@ -27,7 +29,9 @@ #define ENONETYPE 0 #define ECHACHATYPE 1 -#define SHDRSIZE 8 +#define MSEEDSIZE 4 +#define CSEEDSIZE (MSEEDSIZE + 16) +#define SHDRSIZE (8 + 24 + CSEEDSIZE) /* misc helpers */ extern int pack(unsigned char *, char *, ...); @@ -35,6 +39,8 @@ extern int unpack(unsigned char *, char *, ...); struct shdr { uint64_t flags; + unsigned char nonce[crypto_aead_xchacha20poly1305_ietf_NPUBBYTES]; + unsigned char seed[CSEEDSIZE]; }; /* Unpack state header */ @@ -43,7 +49,10 @@ unpackshdr(unsigned char *buf, struct shdr *shdr) { int n; - n = unpack(buf, "q", &shdr->flags); + n = unpack(buf, "q'24'20", + &shdr->flags, + shdr->nonce, + shdr->seed); assert(n == SHDRSIZE); return n; } @@ -54,7 +63,10 @@ packshdr(unsigned char *buf, struct shdr *shdr) { int n; - n = pack(buf, "q", shdr->flags); + n = pack(buf, "q'24'20", + shdr->flags, + shdr->nonce, + shdr->seed); assert(n == SHDRSIZE); return n; } @@ -65,6 +77,11 @@ writestate(int fd, struct param *par) unsigned char buf[SHDRSIZE]; struct shdr shdr; + if (sodium_init() < 0) { + seterr("sodium_init: failed"); + return -1; + } + /* Set version */ shdr.flags = (VMAJ << VMAJSHIFT) | VMIN; @@ -80,11 +97,29 @@ writestate(int fd, struct param *par) return -1; } + /* Clear seed + authentication tag */ + memset(shdr.seed, 0, sizeof(shdr.seed)); + + /* Pack seed */ + shdr.seed[0] = par->seed; + shdr.seed[1] = par->seed >> 8; + shdr.seed[2] = par->seed >> 16; + shdr.seed[3] = par->seed >> 24; + /* Set encryption type */ if (strcasecmp(par->ealgo, "none") == 0) { shdr.flags |= ENONETYPE << EALGOSHIFT; + memset(shdr.nonce, 0, sizeof(shdr.nonce)); } else if (strcasecmp(par->ealgo, "XChaCha20-Poly1305") == 0) { + unsigned long long elen; + shdr.flags |= ECHACHATYPE << EALGOSHIFT; + randombytes_buf(shdr.nonce, sizeof(shdr.nonce)); + crypto_aead_xchacha20poly1305_ietf_encrypt(shdr.seed, &elen, + shdr.seed, MSEEDSIZE, + NULL, 0, NULL, + shdr.nonce, par->key); + assert(elen == CSEEDSIZE); } else { seterr("invalid encryption type: %s", par->ealgo); return -1; @@ -103,8 +138,14 @@ readstate(int fd, struct param *par) { unsigned char buf[SHDRSIZE]; struct shdr shdr; + unsigned long long dlen; int algo; + if (sodium_init() < 0) { + seterr("sodium_init: failed"); + return -1; + } + if (xread(fd, buf, SHDRSIZE) != SHDRSIZE) { seterr("failed to read state header: %s", strerror(errno)); return -1; @@ -142,10 +183,26 @@ readstate(int fd, struct param *par) break; case ECHACHATYPE: par->ealgo = "XChaCha20-Poly1305"; + if (crypto_aead_xchacha20poly1305_ietf_decrypt(shdr.seed, &dlen, + NULL, + shdr.seed, CSEEDSIZE, + NULL, 0, + shdr.nonce, par->key) < 0) { + seterr("authentication failed"); + return -1; + } + assert(dlen == MSEEDSIZE); break; default: seterr("invalid encryption type: %d", algo); return -1; } + + /* Unpack seed */ + par->seed = (uint32_t)shdr.seed[0]; + par->seed |= (uint32_t)shdr.seed[1] << 8; + par->seed |= (uint32_t)shdr.seed[2] << 16; + par->seed |= (uint32_t)shdr.seed[3] << 24; + return 0; } diff --git a/state.h b/state.h @@ -1,8 +1,9 @@ struct param { - char *calgo; - char *ealgo; - unsigned char key[KEYSIZE]; - int keyloaded; + char *calgo; /* compression algorithm */ + char *ealgo; /* encryption algorithm */ + unsigned char key[KEYSIZE]; /* secret key */ + int keyloaded; /* 1 if key is loaded, 0 otherwise */ + uint32_t seed; /* XOR-ed with initial state of chunker */ }; extern int writestate(int, struct param *);