diff --git a/src/or/config.c b/src/or/config.c index 618c941fb4..d22452bfd4 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1912,6 +1912,8 @@ static const struct { { "--dump-config", ARGUMENT_OPTIONAL }, { "--list-fingerprint", TAKES_NO_ARGUMENT }, { "--keygen", TAKES_NO_ARGUMENT }, + { "--no-passphrase", TAKES_NO_ARGUMENT }, + { "--passphrase-fd", ARGUMENT_NECESSARY }, { "--verify-config", TAKES_NO_ARGUMENT }, { "--ignore-missing-torrc", TAKES_NO_ARGUMENT }, { "--quiet", TAKES_NO_ARGUMENT }, @@ -4492,6 +4494,43 @@ options_init_from_torrc(int argc, char **argv) retval = options_init_from_string(cf_defaults, cf, command, command_arg, &errmsg); + if (retval < 0) + goto err; + + if (config_line_find(cmdline_only_options, "--no-passphrase")) { + if (command == CMD_KEYGEN) { + get_options_mutable()->keygen_force_passphrase = FORCE_PASSPHRASE_OFF; + } else { + log_err(LD_CONFIG, "--no-passphrase specified without --keygen!"); + exit(1); + } + } + + { + const config_line_t *fd_line = config_line_find(cmdline_only_options, + "--passphrase-fd"); + if (fd_line) { + if (get_options()->keygen_force_passphrase == FORCE_PASSPHRASE_OFF) { + log_err(LD_CONFIG, "--no-passphrase specified with --passphrase-fd!"); + exit(1); + } else if (command != CMD_KEYGEN) { + log_err(LD_CONFIG, "--passphrase-fd specified without --keygen!"); + exit(1); + } else { + const char *v = fd_line->value; + int ok = 1; + long fd = tor_parse_long(v, 10, 0, INT_MAX, &ok, NULL); + if (fd < 0 || ok == 0) { + log_err(LD_CONFIG, "Invalid --passphrase-fd value %s", escaped(v)); + exit(1); + } + get_options_mutable()->keygen_passphrase_fd = (int)fd; + get_options_mutable()->use_keygen_passphrase_fd = 1; + get_options_mutable()->keygen_force_passphrase = FORCE_PASSPHRASE_ON; + } + } + } + err: tor_free(cf); diff --git a/src/or/or.h b/src/or/or.h index f6aee13f5b..96e69bc9ba 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4292,6 +4292,13 @@ typedef struct { /** How long before auth keys expire will we try to make a new one? */ int TestingAuthKeySlop; + enum { + FORCE_PASSPHRASE_AUTO=0, + FORCE_PASSPHRASE_ON, + FORCE_PASSPHRASE_OFF + } keygen_force_passphrase; + int use_keygen_passphrase_fd; + int keygen_passphrase_fd; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index 47a6c89534..b15d31bc61 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -11,6 +11,72 @@ #define ENC_KEY_HEADER "Boxed Ed25519 key" #define ENC_KEY_TAG "master" +static ssize_t +do_getpass(const char *prompt, char *buf, size_t buflen, + int twice, const or_options_t *options) +{ + if (options->keygen_force_passphrase == FORCE_PASSPHRASE_OFF) { + tor_assert(buflen); + buf[0] = 0; + return 0; + } + + char *prompt2 = NULL; + char *buf2 = NULL; + int fd = -1; + ssize_t length = -1; + + if (options->use_keygen_passphrase_fd) { + twice = 0; + fd = options->keygen_passphrase_fd; + length = read_all(fd, buf, buflen-1, 0); + if (length >= 0) + buf[length] = 0; + goto done_reading; + } + + if (twice) { + const char msg[] = "One more time:"; + size_t p2len = strlen(prompt) + 1; + if (p2len < sizeof(msg)) + p2len = sizeof(msg); + prompt2 = tor_malloc(strlen(prompt)+1); + memset(prompt2, ' ', p2len); + memcpy(prompt2 + p2len - sizeof(msg), msg, sizeof(msg)); + + buf2 = tor_malloc_zero(buflen); + } + + while (1) { + length = tor_getpass(prompt, buf, buflen); + if (length < 0) + goto done_reading; + + if (! twice) + break; + + ssize_t length2 = tor_getpass(prompt2, buf2, buflen); + + if (length != length2 || tor_memneq(buf, buf2, length)) { + fprintf(stderr, "That didn't match.\n"); + } else { + break; + } + } + + done_reading: + if (twice) { + tor_free(prompt2); + memwipe(buf2, 0, buflen); + tor_free(buf2); + } + + if (options->keygen_force_passphrase == FORCE_PASSPHRASE_ON && length == 0) + return -1; + + return length; +} + int read_encrypted_secret_key(ed25519_secret_key_t *out, const char *fname) @@ -41,22 +107,24 @@ read_encrypted_secret_key(ed25519_secret_key_t *out, while (1) { ssize_t pwlen = - tor_getpass("Enter pasphrase for master key:", pwbuf, sizeof(pwbuf)); + do_getpass("Enter pasphrase for master key:", pwbuf, sizeof(pwbuf), 0, + get_options()); if (pwlen < 0) { saved_errno = EINVAL; goto done; } - const int r = crypto_unpwbox(&secret, &secret_len, encrypted_key, encrypted_len, pwbuf, pwlen); if (r == UNPWBOX_CORRUPTED) { log_err(LD_OR, "%s is corrupted.", fname); + puts("E"); saved_errno = EINVAL; goto done; } else if (r == UNPWBOX_OKAY) { break; } + /* Otherwise, passphrase is bad, so try again till user does ctrl-c or gets * it right. */ } @@ -87,22 +155,23 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key, const char *fname) { int r = -1; - char pwbuf0[256], pwbuf1[256]; + char pwbuf0[256]; uint8_t *encrypted_key = NULL; size_t encrypted_len = 0; - while (1) { - if (tor_getpass("Enter passphrase:", pwbuf0, sizeof(pwbuf0)) < 0) - return -1; - if (tor_getpass(" One more time:", pwbuf1, sizeof(pwbuf1)) < 0) - return -1; - - if (!strcmp(pwbuf0, pwbuf1)) - break; - fprintf(stderr, "That didn't match.\n"); + if (do_getpass("Enter new passphrase:", pwbuf0, sizeof(pwbuf0), 1, + get_options()) < 0) { + log_warn(LD_OR, "NO/failed passphrase"); + return -1; } - if (0 == strlen(pwbuf0)) - return 0; + + if (strlen(pwbuf0) == 0) { + if (get_options()->keygen_force_passphrase == FORCE_PASSPHRASE_ON) + return -1; + else + return 0; + } + if (crypto_pwbox(&encrypted_key, &encrypted_len, key->seckey, sizeof(key->seckey), pwbuf0, strlen(pwbuf0), 0) < 0) { @@ -121,7 +190,6 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key, tor_free(encrypted_key); } memwipe(pwbuf0, 0, sizeof(pwbuf0)); - memwipe(pwbuf1, 0, sizeof(pwbuf1)); return r; } @@ -134,7 +202,9 @@ write_secret_key(const ed25519_secret_key_t *key, int encrypted, if (encrypted) { int r = write_encrypted_secret_key(key, encrypted_fname); if (r != 0) - return r; + return r; /* Either succeeded or failed unrecoverably */ + + fprintf(stderr, "Not encrypting the secret key.\n"); } return ed25519_seckey_write_to_file(key, fname, fname_tag); } @@ -628,6 +698,9 @@ load_ed_keys(const or_options_t *options, time_t now) * it, if we loaded it in the first place. */ memwipe(id->seckey.seckey, 0, sizeof(id->seckey)); + if (options->command == CMD_KEYGEN) + goto end; + if (!rsa_ed_crosscert && server_mode(options)) { uint8_t *crosscert; ssize_t crosscert_len = tor_make_rsa_ed25519_crosscert(&id->pubkey, @@ -651,6 +724,7 @@ load_ed_keys(const or_options_t *options, time_t now) /* We've generated or loaded everything. Put them in memory. */ + end: if (! master_identity_key) { SET_KEY(master_identity_key, id); } else { diff --git a/src/test/test_keygen.sh b/src/test/test_keygen.sh new file mode 100755 index 0000000000..7c97a7d2d3 --- /dev/null +++ b/src/test/test_keygen.sh @@ -0,0 +1,332 @@ +#!/bin/sh + +# Note: some of this code is lifted from zero_length_keys.sh, and could be +# unified. + +umask 077 +set -e + +if [ $# -eq 0 ] || [ ! -f ${1} ] || [ ! -x ${1} ]; then + echo "Usage: ${0} PATH_TO_TOR [case-number]" + exit 1 +elif [ $# -ge 1 ]; then + TOR_BINARY="${1}" + shift + + if [ $# -ge 1 ]; then + dflt=0 + else + dflt=1 + fi + + CASE2A=$dflt + CASE2B=$dflt + CASE3A=$dflt + CASE3B=$dflt + CASE3C=$dflt + CASE4=$dflt + CASE5=$dflt + CASE6=$dflt + CASE7=$dflt + CASE8=$dflt + CASE9=$dflt + CASE10=$dflt + + if [ $# -ge 1 ]; then + eval "CASE${1}"=1 + fi +fi + +die() { echo "$1" >&2 ; exit 5; } +check_dir() { [ -d "$1" ] || die "$1 did not exist"; } +check_file() { [ -e "$1" ] || die "$1 did not exist"; } +check_no_file() { [ -e "$1" ] && die "$1 was not supposed to exist" || true; } +check_files_eq() { cmp "$1" "$2" || die "$1 and $2 did not match"; } +check_keys_eq() { check_files_eq "${SRC}/keys/${1}" "${ME}/keys/${1}"; } + +DATA_DIR=`mktemp -d -t tor_keygen_tests.XXXXXX` +if [ -z "$DATA_DIR" ]; then + echo "Failure: mktemp invocation returned empty string" >&2 + exit 3 +fi +if [ ! -d "$DATA_DIR" ]; then + echo "Failure: mktemp invocation result doesn't point to directory" >&2 + exit 3 +fi +trap "rm -rf '$DATA_DIR'" 0 + +touch "${DATA_DIR}/empty_torrc" + +TOR="${TOR_BINARY} --hush --DisableNetwork 1 --ShutdownWaitLength 0 --ORPort 12345 --ExitRelay 0 -f ${DATA_DIR}/empty_torrc" + +# Step 1: Start Tor with --list-fingerprint. Make sure everything is there. +mkdir "${DATA_DIR}/orig" +${TOR} --DataDirectory "${DATA_DIR}/orig" --list-fingerprint > /dev/null + +check_dir "${DATA_DIR}/orig/keys" +check_file "${DATA_DIR}/orig/keys/ed25519_master_id_public_key" +check_file "${DATA_DIR}/orig/keys/ed25519_master_id_secret_key" +check_file "${DATA_DIR}/orig/keys/ed25519_signing_cert" +check_file "${DATA_DIR}/orig/keys/ed25519_signing_secret_key" + +# Step 2: Start Tor with --keygen. Make sure everything is there. +mkdir "${DATA_DIR}/keygen" +${TOR} --DataDirectory "${DATA_DIR}/keygen" --keygen --no-passphrase + +check_dir "${DATA_DIR}/keygen/keys" +check_file "${DATA_DIR}/keygen/keys/ed25519_master_id_public_key" +check_file "${DATA_DIR}/keygen/keys/ed25519_master_id_secret_key" +check_file "${DATA_DIR}/keygen/keys/ed25519_signing_cert" +check_file "${DATA_DIR}/keygen/keys/ed25519_signing_secret_key" + +# Step 3: Start Tor with --keygen and a passphrase. +# Make sure everything is there. +mkdir "${DATA_DIR}/encrypted" +echo "passphrase" | ${TOR} --DataDirectory "${DATA_DIR}/encrypted" --keygen --passphrase-fd 0 + +check_dir "${DATA_DIR}/encrypted/keys" +check_file "${DATA_DIR}/encrypted/keys/ed25519_master_id_public_key" +check_file "${DATA_DIR}/encrypted/keys/ed25519_master_id_secret_key_encrypted" +check_file "${DATA_DIR}/encrypted/keys/ed25519_signing_cert" +check_file "${DATA_DIR}/encrypted/keys/ed25519_signing_secret_key" + + +echo "KEY GENERATION WAS SUCCESSFUL." + +# +# The "case X" numbers below come from s7r's email on +# https://lists.torproject.org/pipermail/tor-dev/2015-August/009204.html + + +# Case 2a: Missing secret key, public key exists, start tor. + +if [ "$CASE2A" = 1 ]; then + +ME="${DATA_DIR}/case2a" +SRC="${DATA_DIR}/orig" +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --list-fingerprint && die "Somehow succeeded when missing secret key, certs" || true +check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" + +echo "==== Case 2A ok" +fi + +# Case 2b: Encrypted secret key, public key exists, start tor. + +if [ "$CASE2B" = 1 ]; then + +ME="${DATA_DIR}/case2b" +SRC="${DATA_DIR}/encrypted" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" +cp "${SRC}/keys/ed25519_master_id_secret_key_encrypted" "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --list-fingerprint && dir "Somehow succeeded with encrypted secret key, missing certs" + +check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" +check_files_eq "${SRC}/keys/ed25519_master_id_secret_key_encrypted" "${ME}/keys/ed25519_master_id_secret_key_encrypted" + +echo "==== Case 2B ok" + +fi + +# Case 3a: Start Tor with only master key. + +if [ "$CASE3A" = 1 ]; then + +ME="${DATA_DIR}/case3a" +SRC="${DATA_DIR}/orig" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_"* "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Tor failed when starting with only master key" +check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" +check_files_eq "${SRC}/keys/ed25519_master_id_secret_key" "${ME}/keys/ed25519_master_id_secret_key" +check_file "${ME}/keys/ed25519_signing_cert" +check_file "${ME}/keys/ed25519_signing_secret_key" + +echo "==== Case 3A ok" + +fi + +# Case 3b: Call keygen with only unencrypted master key. + +if [ "$CASE3B" = 1 ]; then + +ME="${DATA_DIR}/case3b" +SRC="${DATA_DIR}/orig" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_"* "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --keygen || die "Keygen failed with only master key" +check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" +check_files_eq "${SRC}/keys/ed25519_master_id_secret_key" "${ME}/keys/ed25519_master_id_secret_key" +check_file "${ME}/keys/ed25519_signing_cert" +check_file "${ME}/keys/ed25519_signing_secret_key" + +echo "==== Case 3B ok" + +fi + +# Case 3c: Call keygen with only encrypted master key. + +if [ "$CASE3C" = 1 ]; then + +ME="${DATA_DIR}/case3c" +SRC="${DATA_DIR}/encrypted" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_"* "${ME}/keys/" +echo "passphrase" | ${TOR} --DataDirectory "${ME}" --keygen --passphrase-fd 0 || die "Keygen failed with only encrypted master key" +check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" +check_files_eq "${SRC}/keys/ed25519_master_id_secret_key_encrypted" "${ME}/keys/ed25519_master_id_secret_key_encrypted" +check_file "${ME}/keys/ed25519_signing_cert" +check_file "${ME}/keys/ed25519_signing_secret_key" + +echo "==== Case 3C ok" + +fi + +# Case 4: Make a new data directory with only an unencrypted secret key. +# Then start tor. The rest should become correct. + +if [ "$CASE4" = 1 ]; then + +ME="${DATA_DIR}/case4" +SRC="${DATA_DIR}/orig" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_secret_key" "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Tor wouldn't start with only unencrypted secret key" +check_file "${ME}/keys/ed25519_master_id_public_key" +check_file "${ME}/keys/ed25519_master_id_signing_cert" +check_file "${ME}/keys/ed25519_master_id_signing_secret_key" +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Tor wouldn't start again after starting once with only unencrypted secret key." + +echo "==== Case 4 ok" + +fi + +# Case 5: Make a new data directory with only an encrypted secret key. + +if [ "$CASE5" = 1 ]; then + +ME="${DATA_DIR}/case5" +SRC="${DATA_DIR}/encrypted" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_secret_key_encrypted" "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Tor wouldn't start with only encrypted secret key" +check_files_eq "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/ed25519_master_id_public_key" + +echo "==== Case 5 ok" + +fi + +# Case 6: Make a new data directory with encrypted secret key and public key + +if [ "$CASE6" = 1 ]; then + +ME="${DATA_DIR}/case6" +SRC="${DATA_DIR}/encrypted" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_secret_key_encrypted" "${ME}/keys/" +cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" +${TOR} --DataDirectory "${ME}" --list-fingerprint && die "Tor started with encrypted secret key and no certs" || true +check_no_file "${ME}/keys/ed25519_signing_cert" +check_no_file "${ME}/keys/ed25519_signing_secret_key" + +echo "==== Case 6 ok" + +fi + +# Case 7: Make a new data directory with unencrypted secret key and +# certificates; missing master public. + +if [ "$CASE7" = 1 ]; then + +ME="${DATA_DIR}/case7" +SRC="${DATA_DIR}/keygen" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_secret_key" "${ME}/keys/" +cp "${SRC}/keys/ed25519_signing_cert" "${ME}/keys/" +cp "${SRC}/keys/ed25519_signing_secret_key" "${ME}/keys/" + +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Failed when starting with missing public key" +check_keys_eq ed25519_master_id_secret_key +check_keys_eq ed25519_master_id_public_key +check_keys_eq ed25519_signing_secret_key +check_keys_eq ed25519_signing_cert + +echo "==== Case 7 ok" + +fi + +# Case 8: offline master secret key. + +if [ "$CASE8" = 1 ]; then + +ME="${DATA_DIR}/case8" +SRC="${DATA_DIR}/keygen" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" +cp "${SRC}/keys/ed25519_signing_cert" "${ME}/keys/" +cp "${SRC}/keys/ed25519_signing_secret_key" "${ME}/keys/" + +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Failed when starting with offline secret key" +check_no_file "${ME}/keys/ed25519_master_id_secret_key" +check_keys_eq ed25519_master_id_public_key +check_keys_eq ed25519_signing_secret_key +check_keys_eq ed25519_signing_cert + +echo "==== Case 8 ok" + +fi + +# Case 9: signing cert and secret key provided; could infer master key. + +if [ "$CASE9" = 1 ]; then + +ME="${DATA_DIR}/case9" +SRC="${DATA_DIR}/keygen" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_signing_cert" "${ME}/keys/" +cp "${SRC}/keys/ed25519_signing_secret_key" "${ME}/keys/" + +${TOR} --DataDirectory "${ME}" --list-fingerprint || die "Failed when starting with only signing material" +check_no_file "${ME}/keys/ed25519_master_id_secret_key" +check_keys_eq ed25519_master_id_public_key +check_keys_eq ed25519_signing_secret_key +check_keys_eq ed25519_signing_cert + +echo "==== Case 9 ok" + +fi + + +# Case 10: key mismatch. + +if [ "$CASE10" = 1 ]; then + +ME="${DATA_DIR}/case10" +SRC="${DATA_DIR}/keygen" +OTHER="${DATA_DIR}/orig" + +mkdir -p "${ME}/keys" +cp "${SRC}/keys/ed25519_master_id_public_key" "${ME}/keys/" +cp "${OTHER}/keys/ed25519_master_id_secret_key" "${ME}/keys/" + +${TOR} --DataDirectory "${ME}" --list-fingerprint && die "Successfully started with mismatched keys!?" || true + +echo "==== Case 10 ok" + +fi + + +# Check cert-only. +