From ca84cf97d13dd4536aee247aa755ed7c3aa819de Mon Sep 17 00:00:00 2001 From: Colin Percival Date: Thu, 7 Aug 2008 12:00:00 -0700 Subject: [PATCH] Tarsnap 1.0.10 Changes since 1.0.9: * Restricted keyfiles can be generated using a new "tarsnap-keymgmt" utility containing the keys needed to write backups but not read or delete them, to read backups but not write or delete them, etc. * Several minor bugfixes. --- Makefile.am | 39 +++++- crypto/crypto.h | 16 +++ crypto/crypto_keys.c | 73 +++++++++++ keygen/keygen.c | 2 +- keymgmt/keymgmt.c | 211 ++++++++++++++++++++++++++++++++ keymgmt/tarsnap-keymgmt.1 | 35 ++++++ multitape/multitape_chunkiter.c | 3 +- tar-version | 2 +- tar/bsdtar.c | 31 +++++ util/hexify.c | 6 +- 10 files changed, 411 insertions(+), 7 deletions(-) create mode 100644 keymgmt/keymgmt.c create mode 100644 keymgmt/tarsnap-keymgmt.1 diff --git a/Makefile.am b/Makefile.am index 70bfda48..5a638886 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,7 +11,7 @@ distclean-local: -rm -f *~ noinst_LIBRARIES= libarchive.a -bin_PROGRAMS= tarsnap tarsnap-keygen +bin_PROGRAMS= tarsnap tarsnap-keygen tarsnap-keymgmt dist_man_MANS=$(tarsnap_dist_man_MANS) $(tarsnap_keygen_dist_man_MANS) # @@ -233,5 +233,42 @@ tarsnap_keygen_LDADD= -lcrypto tarsnap_keygen_dist_man_MANS= keygen/tarsnap-keygen.1 +# +# +# tarsnap-keymgmt source, docs, etc. +# +# + +tarsnap_keymgmt_SOURCES= \ + keymgmt/keymgmt.c \ + datastruct/rwhashtab.c \ + crypto/sha256.c \ + crypto/crypto_entropy.c \ + crypto/crypto_hash.c \ + crypto/crypto_keys.c \ + crypto/crypto_keys_subr.c \ + crypto/crypto_session.c \ + crypto/crypto_file.c \ + crypto/crypto_rsa.c \ + crypto/crypto_passwd_to_dh.c \ + crypto/crypto_dh.c \ + crypto/crypto_verify_bytes.c \ + crypto/crypto_keys_server.c \ + crypto/crypto_dh_group14.c \ + util/entropy.c + +tarsnap_keymgmt_CFLAGS= \ + -Werror \ + -I$(top_builddir)/tar \ + -I${top_builddir}/libarchive \ + -I$(top_builddir)/datastruct \ + -I${top_builddir}/crypto \ + -I$(top_builddir)/util \ + -DUSERAGENT=\"tarsnap-keygen-${VERSION}\" + +tarsnap_keymgmt_LDADD= -lcrypto + +tarsnap_keymgmt_dist_man_MANS= keymgmt/tarsnap-keymgmt.1 + # Tarsnap sample configuration file sysconf_DATA = tar/tarsnap.conf.sample diff --git a/crypto/crypto.h b/crypto/crypto.h index 7a9cf116..3fb6a8e3 100644 --- a/crypto/crypto.h +++ b/crypto/crypto.h @@ -37,6 +37,15 @@ #define CRYPTO_KEYMASK_HMAC_CHUNK (1 << CRYPTO_KEY_HMAC_CHUNK) #define CRYPTO_KEYMASK_HMAC_NAME (1 << CRYPTO_KEY_HMAC_NAME) #define CRYPTO_KEYMASK_HMAC_CPARAMS (1 << CRYPTO_KEY_HMAC_CPARAMS) +#define CRYPTO_KEYMASK_READ \ + (CRYPTO_KEYMASK_ENCR_PRIV | CRYPTO_KEYMASK_SIGN_PUB | \ + CRYPTO_KEYMASK_HMAC_FILE | CRYPTO_KEYMASK_HMAC_CHUNK | \ + CRYPTO_KEYMASK_HMAC_NAME | CRYPTO_KEYMASK_AUTH_GET ) +#define CRYPTO_KEYMASK_WRITE \ + (CRYPTO_KEYMASK_SIGN_PRIV | CRYPTO_KEYMASK_ENCR_PUB | \ + CRYPTO_KEYMASK_HMAC_FILE | CRYPTO_KEYMASK_HMAC_CHUNK | \ + CRYPTO_KEYMASK_HMAC_NAME | CRYPTO_KEYMASK_HMAC_CPARAMS | \ + CRYPTO_KEYMASK_AUTH_PUT ) #define CRYPTO_KEYMASK_ROOT_PUB (1 << CRYPTO_KEY_ROOT_PUB) #define CRYPTO_KEYMASK_AUTH_PUT (1 << CRYPTO_KEY_AUTH_PUT) @@ -90,6 +99,13 @@ int crypto_keys_init(void); */ int crypto_keys_import(uint8_t * buf, size_t buflen); +/** + * crypto_keys_missing(keys): + * Look for the specified keys. If they are all present, return NULL; if + * not, return a pointer to the name of one of the keys. + */ +const char * crypto_keys_missing(int); + /** * crypto_keys_export(keys, buf, buflen): * Export the keys specified to a buffer allocated using malloc. diff --git a/crypto/crypto_keys.c b/crypto/crypto_keys.c index a92762d3..d5f5a4bf 100644 --- a/crypto/crypto_keys.c +++ b/crypto/crypto_keys.c @@ -258,6 +258,79 @@ crypto_keys_import(uint8_t * buf, size_t buflen) return (-1); } +/** + * crypto_keys_missing(keys): + * Look for the specified keys. If they are all present, return NULL; if + * not, return a pointer to the name of one of the keys. + */ +const char * +crypto_keys_missing(int keys) +{ + const char * keyname = NULL; + int key; + + /* + * Go through all the keys we know about and determine if (a) the key + * is in the provided mask; and (b) if we do not have it. + */ + for (key = 0; key < (int)(sizeof(int) * 8); key++) + if ((keys >> key) & 1) { + switch (key) { + case CRYPTO_KEY_SIGN_PRIV: + if (keycache.sign_priv == NULL) + keyname = "archive signing"; + break; + case CRYPTO_KEY_SIGN_PUB: + if (keycache.sign_pub == NULL) + keyname = "archive signature verification"; + break; + case CRYPTO_KEY_ENCR_PRIV: + if (keycache.encr_priv == NULL) + keyname = "archive decryption"; + break; + case CRYPTO_KEY_ENCR_PUB: + if (keycache.encr_pub == NULL) + keyname = "archive encryption"; + break; + case CRYPTO_KEY_HMAC_FILE: + if (keycache.hmac_file == NULL) + keyname = "file HMAC"; + break; + case CRYPTO_KEY_HMAC_CHUNK: + if (keycache.hmac_chunk == NULL) + keyname = "chunk HMAC"; + break; + case CRYPTO_KEY_HMAC_NAME: + if (keycache.hmac_name == NULL) + keyname = "archive name HMAC"; + break; + case CRYPTO_KEY_HMAC_CPARAMS: + if (keycache.hmac_cparams == NULL) + keyname = "chunk randomization"; + break; + case CRYPTO_KEY_ROOT_PUB: + if (keycache.root_pub == NULL) + keyname = "server root"; + break; + case CRYPTO_KEY_AUTH_PUT: + if (keycache.auth_put == NULL) + keyname = "write authorization"; + break; + case CRYPTO_KEY_AUTH_GET: + if (keycache.auth_get == NULL) + keyname = "read authorization"; + break; + case CRYPTO_KEY_AUTH_DELETE: + if (keycache.auth_delete == NULL) + keyname = "delete authorization"; + break; + } + } + + /* Return the key name or NULL if we have everything. */ + return (keyname); +} + /** * crypto_keys_export(keys, buf, buflen): * Export the keys specified to a buffer allocated using malloc. diff --git a/keygen/keygen.c b/keygen/keygen.c index 38456ed9..f63fcb46 100644 --- a/keygen/keygen.c +++ b/keygen/keygen.c @@ -76,7 +76,7 @@ main(int argc, char **argv) C.passwd = passbuf; /* Parse arguments. */ - while (--argc) { + while (--argc > 0) { argv++; if (strcmp(argv[0], "--user") == 0) { diff --git a/keymgmt/keymgmt.c b/keymgmt/keymgmt.c new file mode 100644 index 00000000..581c85de --- /dev/null +++ b/keymgmt/keymgmt.c @@ -0,0 +1,211 @@ +#include "bsdtar_platform.h" + +#include + +#include +#include +#include +#include + +#include "crypto.h" +#include "sysendian.h" +#include "warnp.h" + +static void usage(void); + +static void +usage(void) +{ + + fprintf(stderr, "usage: tarsnap-keymgmt %s %s %s %s key-file ...\n", + "--outkeyfile new-key-file", "[-r]", "[-w]", "[-d]"); + exit(1); + + /* NOTREACHED */ +} + +int +main(int argc, char **argv) +{ + const char * newkeyfile = NULL; + int keyswanted = 0; + char * tok, * brkb = NULL, * eptr; + long keynum; + struct stat sb; + uint8_t * keybuf; + size_t keybuflen; + FILE * f; + uint64_t machinenum = (uint64_t)(-1); + uint8_t machinenumvec[8]; + const char * missingkey; + + /* Initialize entropy subsystem. */ + if (crypto_entropy_init()) { + warnp("Entropy subsystem initialization failed"); + exit(1); + } + + /* Initialize key cache. */ + if (crypto_keys_init()) { + warnp("Key cache initialization failed"); + exit(1); + } + + /* Look for command-line options. */ + while (--argc > 0) { + argv++; + + if (strcmp(argv[0], "--outkeyfile") == 0) { + if ((newkeyfile != NULL) || (argc < 2)) + usage(); + newkeyfile = argv[1]; + argv++; argc--; + } else if (strcmp(argv[0], "-r") == 0) { + keyswanted |= CRYPTO_KEYMASK_READ; + } else if (strcmp(argv[0], "-w") == 0) { + keyswanted |= CRYPTO_KEYMASK_WRITE; + } else if (strcmp(argv[0], "-d") == 0) { + /* + * Deleting data requires both delete authorization + * and being able to read archives -- we need to be + * able to figure out which bits are part of the + * archive. + */ + keyswanted |= CRYPTO_KEYMASK_READ; + keyswanted |= CRYPTO_KEYMASK_AUTH_DELETE; + } else if (strcmp(argv[0], "--keylist") == 0) { + /* + * This is a deliberately undocumented option used + * mostly for testing purposes; it allows a list of + * keys to be specified according to their numbers in + * crypto/crypto.h instead of using the predefined + * sets of "read", "write" and "delete" keys. + */ + if (argc < 2) + usage(); + for (tok = strtok_r(argv[1], ",", &brkb); + tok; + tok = strtok_r(NULL, ",", &brkb)) { + keynum = strtol(tok, &eptr, 0); + if ((eptr == tok) || + (keynum < 0) || (keynum > 32)) { + warn0("Not a valid key number: %s", + tok); + exit(1); + } + keyswanted |= 1 << keynum; + } + argv++; argc--; + } else { + /* Key files follow. */ + break; + } + } + + /* We should have an output key file. */ + if (newkeyfile == NULL) + usage(); + + /* Read the specified key files. */ + while (argc-- > 0) { + /* Stat the key file. */ + if (stat(argv[0], &sb)) { + warnp("stat(%s)", argv[0]); + exit(1); + } + + /* Allocate memory to hold the keyfile contents. */ + if ((sb.st_size < 8) || (sb.st_size > 1000000)) { + warn0("Key file has unreasonable size: %s", argv[0]); + exit(1); + } + if ((keybuf = malloc(sb.st_size)) == NULL) { + warn0("Out of memory"); + exit(1); + } + + /* Read the file. */ + if ((f = fopen(argv[0], "r")) == NULL) { + warnp("fopen(%s)", argv[0]); + exit(1); + } + if (fread(keybuf, sb.st_size, 1, f) != 1) { + warnp("fread(%s)", argv[0]); + exit(1); + } + if (fclose(f)) { + warnp("fclose(%s)", argv[0]); + exit(1); + } + + /* + * Parse the machine number from the key file or check that + * the machine number from the key file matches the number + * we already have. + */ + if (machinenum == (uint64_t)(-1)) { + machinenum = be64dec(keybuf); + } else if (machinenum != be64dec(keybuf)) { + warn0("Keys from %s do not belong to the " + "same machine as earlier keys", argv[0]); + exit(1); + } + + /* Parse keys. */ + if (crypto_keys_import(&keybuf[8], sb.st_size - 8)) { + warn0("Error parsing keys in %s", argv[0]); + exit(1); + } + + /* Free memory. */ + free(keybuf); + + /* Move on to the next file. */ + argv++; + } + + /* Make sure that we have the necessary keys. */ + if ((missingkey = crypto_keys_missing(keyswanted)) != NULL) { + warn0("The %s key is required but not in any input key files", + missingkey); + exit(1); + } + + /* Create key file. */ + if ((f = fopen(newkeyfile, "w")) == NULL) { + warnp("Cannot create %s", newkeyfile); + exit(1); + } + + /* Set the permissions on the key file to 0600. */ + if (fchmod(fileno(f), S_IRUSR | S_IWUSR)) { + warnp("Cannot set permissions on key file: %s", newkeyfile); + exit(1); + } + + /* Export keys. */ + if (crypto_keys_export(keyswanted, &keybuf, &keybuflen)) { + warnp("Error exporting keys"); + exit(1); + } + + /* Write keys. */ + be64enc(machinenumvec, machinenum); + if (fwrite(machinenumvec, 8, 1, f) != 1) { + warnp("Error writing keys"); + exit(1); + } + if (fwrite(keybuf, keybuflen, 1, f) != 1) { + warnp("Error writing keys"); + exit(1); + } + + /* Close the key file. */ + if (fclose(f)) { + warnp("Error closing key file"); + exit(1); + } + + /* Success! */ + return (0); +} diff --git a/keymgmt/tarsnap-keymgmt.1 b/keymgmt/tarsnap-keymgmt.1 new file mode 100644 index 00000000..7805c4a3 --- /dev/null +++ b/keymgmt/tarsnap-keymgmt.1 @@ -0,0 +1,35 @@ +.\" Copyright 2008 Colin Percival +.\" All rights reserved. +.\" +.Dd July 13, 2008 +.Dt TARSNAP-KEYMGMT 1 +.Os +.Sh NAME +.Nm tarsnap-keymgmt +.Nd generate subsets of +.Xr tarsnap 1 +key files +.Sh SYNOPSIS +.Nm +.Fl -outkeyfile Ar new-key-file +.Op Fl r +.Op Fl w +.Op Fl d +.Ar key-file ... +.Sh DESCRIPTION +.Nm +reads the provided key files and writes a new key file +containing only the keys required for the operations +specified via the +.Fl r +(list and extract archives), +.Fl w +(write archives), and +.Fl d +(delete archives) +flags. Note that +.Fl d +implies +.Fl r +since it is impossible to delete an archive without +being able to read it. diff --git a/multitape/multitape_chunkiter.c b/multitape/multitape_chunkiter.c index f974b3eb..789547cc 100644 --- a/multitape/multitape_chunkiter.c +++ b/multitape/multitape_chunkiter.c @@ -59,7 +59,7 @@ multitape_chunkiter_tmd(STORAGE_R * S, CHUNKS_S * C, rc = -1; goto err2; } - ibufpos = ibuflen = 0; + ibuflen = 0; /* Iterate through the header stream index. */ for (hindexpos = 0; @@ -110,7 +110,6 @@ multitape_chunkiter_tmd(STORAGE_R * S, CHUNKS_S * C, /* Move buffered data to the start of the buffer. */ memmove(ibuf, ibuf + ibufpos, ibuflen - ibufpos); ibuflen -= ibufpos; - ibufpos -= ibufpos; } /* Iterate through the trailer stream index. */ diff --git a/tar-version b/tar-version index e5a4a5e7..437d26b1 100644 --- a/tar-version +++ b/tar-version @@ -1 +1 @@ -1.0.9 \ No newline at end of file +1.0.10 \ No newline at end of file diff --git a/tar/bsdtar.c b/tar/bsdtar.c index 03bb7954..549d0f60 100644 --- a/tar/bsdtar.c +++ b/tar/bsdtar.c @@ -245,6 +245,7 @@ main(int argc, char **argv) char cachedir[PATH_MAX + 1]; char *homedir; char *conffile; + const char *missingkey; /* * Use a pointer for consistency, but stack-allocated storage @@ -698,6 +699,36 @@ main(int argc, char **argv) bsdtar->cachedir = cachedir; } + /* Make sure we have whatever keys we're going to need. */ + missingkey = NULL; + switch (bsdtar->mode) { + case 'c': + missingkey = crypto_keys_missing(CRYPTO_KEYMASK_WRITE); + break; + case 'd': + case OPTION_FSCK: + missingkey = crypto_keys_missing(CRYPTO_KEYMASK_READ | + CRYPTO_KEYMASK_AUTH_DELETE); + break; + case OPTION_PRINT_STATS: + /* We don't need keys for printing global stats. */ + if (bsdtar->tapename == NULL) + break; + + /* FALLTHROUGH */ + case OPTION_LIST_ARCHIVES: + case 'r': + case 't': + case 'x': + missingkey = crypto_keys_missing(CRYPTO_KEYMASK_READ); + break; + } + if (missingkey != NULL) + bsdtar_errc(bsdtar, 1, 0, + "The %s key is required for %s but is not available", + missingkey, bsdtar->modestr); + + /* Perform the requested operation. */ switch(bsdtar->mode) { case 'c': tarsnap_mode_c(bsdtar); diff --git a/util/hexify.c b/util/hexify.c index 34a261a3..b3cd2767 100644 --- a/util/hexify.c +++ b/util/hexify.c @@ -37,8 +37,10 @@ unhexify(const char * in, uint8_t * out, size_t len) size_t i; /* Make sure we have at least 2 * ${len} hex characters. */ - if (strspn(in, hexchars) < 2 * len) - goto err0; + for (i = 0; i < 2 * len; i++) { + if ((in[i] == '\0') || (strchr(hexchars, in[i]) == NULL)) + goto err0; + } for (i = 0; i < len; i++) { out[i] = (strchr(hexchars, in[2 * i]) - hexchars) & 0x0f;