From b2a7e8df900eabe41d6e866f8b66aadd8a0d31d7 Mon Sep 17 00:00:00 2001 From: Isis Lovecruft Date: Fri, 14 Jul 2017 01:25:01 +0000 Subject: [PATCH] routerkeys: Add cmdline option for learning signing key expiration. * CLOSES #17639. * ADDS new --key-expiration commandline option which prints when the signing key expires. --- changes/bug17639 | 4 + doc/tor.1.txt | 10 +++ src/or/config.c | 4 + src/or/main.c | 5 ++ src/or/or.h | 3 +- src/or/routerkeys.c | 102 +++++++++++++++++++++++++ src/or/routerkeys.h | 1 + src/test/include.am | 2 + src/test/test_key_expiration.sh | 129 ++++++++++++++++++++++++++++++++ 9 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 changes/bug17639 create mode 100755 src/test/test_key_expiration.sh diff --git a/changes/bug17639 b/changes/bug17639 new file mode 100644 index 0000000000..4073514fd4 --- /dev/null +++ b/changes/bug17639 @@ -0,0 +1,4 @@ + o Minor features: + - Add a new commandline option, --key-expiration, which prints when + the current signing key is going to expire. Implements ticket + 17639; patch by Isis Lovecruft. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index c8d7688a93..b4a3cc5f75 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -128,6 +128,16 @@ COMMAND-LINE OPTIONS the passphrase, including any trailing newlines. Default: read from the terminal. +[[opt-key-expiration]] **--key-expiration** [**purpose**]:: + The **purpose** specifies which type of key certificate to determine + the expiration of. The only currently recognised **purpose** is + "sign". + + + + Running "tor --key-expiration sign" will attempt to find your signing + key certificate and will output, both in the logs as well as to stdout, + the signing key certificate's expiration time in ISO-8601 format. + For example, the output sent to stdout will be of the form: + "signing-cert-expiry: 2017-07-25 08:30:15 UTC" Other options can be specified on the command-line in the format "--option value", in the format "option value", or in a configuration file. For diff --git a/src/or/config.c b/src/or/config.c index 53fc2795c6..9b6bf40ebf 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -2137,6 +2137,7 @@ static const struct { { "--dump-config", ARGUMENT_OPTIONAL }, { "--list-fingerprint", TAKES_NO_ARGUMENT }, { "--keygen", TAKES_NO_ARGUMENT }, + { "--key-expiration", ARGUMENT_OPTIONAL }, { "--newpass", TAKES_NO_ARGUMENT }, { "--no-passphrase", TAKES_NO_ARGUMENT }, { "--passphrase-fd", ARGUMENT_NECESSARY }, @@ -4932,6 +4933,9 @@ options_init_from_torrc(int argc, char **argv) for (p_index = cmdline_only_options; p_index; p_index = p_index->next) { if (!strcmp(p_index->key,"--keygen")) { command = CMD_KEYGEN; + } else if (!strcmp(p_index->key, "--key-expiration")) { + command = CMD_KEY_EXPIRATION; + command_arg = p_index->value; } else if (!strcmp(p_index->key,"--list-fingerprint")) { command = CMD_LIST_FINGERPRINT; } else if (!strcmp(p_index->key, "--hash-password")) { diff --git a/src/or/main.c b/src/or/main.c index dc23184961..0267f4daec 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -3758,6 +3758,11 @@ tor_main(int argc, char *argv[]) case CMD_KEYGEN: result = load_ed_keys(get_options(), time(NULL)) < 0; break; + case CMD_KEY_EXPIRATION: + init_keys(); + result = log_cert_expiration(); + result = 0; + break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); break; diff --git a/src/or/or.h b/src/or/or.h index f6c42b7a99..32b4cd1b7e 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3588,7 +3588,8 @@ typedef struct { enum { CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG, - CMD_KEYGEN + CMD_KEYGEN, + CMD_KEY_EXPIRATION, } command; char *command_arg; /**< Argument for command-line option. */ diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index 71889d2721..2f20758b5b 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -1136,6 +1136,108 @@ init_mock_ed_keys(const crypto_pk_t *rsa_identity_key) #undef MAKECERT #endif +/** + * Print the ISO8601-formated expiration for a certificate with + * some description to stdout. + * + * For example, for a signing certificate, this might print out: + * signing-cert-expiry: 2017-07-25 08:30:15 UTC + */ +static void +print_cert_expiration(const char *expiration, + const char *description) +{ + fprintf(stderr, "%s-cert-expiry: %s\n", description, expiration); +} + +/** + * Log when a certificate, cert, with some description and + * stored in a file named fname, is going to expire. + */ +static void +log_ed_cert_expiration(const tor_cert_t *cert, + const char *description, + const char *fname) { + char expiration[ISO_TIME_LEN+1]; + + if (BUG(!cert)) { /* If the specified key hasn't been loaded */ + log_warn(LD_OR, "No %s key loaded; can't get certificate expiration.", + description); + } else { + format_local_iso_time(expiration, cert->valid_until); + log_notice(LD_OR, "The %s certificate stored in %s is valid until %s.", + description, fname, expiration); + print_cert_expiration(expiration, description); + } +} + +/** + * Log when our master signing key certificate expires. Used when tor is given + * the --key-expiration command-line option. + * + * Returns 0 on success and 1 on failure. + */ +static int +log_master_signing_key_cert_expiration(const or_options_t *options) +{ + const tor_cert_t *signing_key; + char *fn = NULL; + int failed = 0; + time_t now = approx_time(); + + fn = options_get_datadir_fname2(options, "keys", "ed25519_signing_cert"); + + /* Try to grab our cached copy of the key. */ + signing_key = get_master_signing_key_cert(); + + tor_assert(server_identity_key_is_set()); + + /* Load our keys from disk, if necessary. */ + if (!signing_key) { + failed = load_ed_keys(options, now) < 0; + signing_key = get_master_signing_key_cert(); + } + + /* If we do have a signing key, log the expiration time. */ + if (signing_key) { + log_ed_cert_expiration(signing_key, "signing", fn); + } else { + log_warn(LD_OR, "Could not load signing key certificate from %s, so " \ + "we couldn't learn anything about certificate expiration.", fn); + } + + tor_free(fn); + + return failed; +} + +/** + * Log when a key certificate expires. Used when tor is given the + * --key-expiration command-line option. + * + * If an command argument is given, which should specify the type of + * key to get expiry information about (currently supported arguments + * are "sign"), get info about that type of certificate. Otherwise, + * print info about the supported arguments. + * + * Returns 0 on success and -1 on failure. + */ +int +log_cert_expiration(void) +{ + const or_options_t *options = get_options(); + const char *arg = options->command_arg; + + if (!strcmp(arg, "sign")) { + return log_master_signing_key_cert_expiration(options); + } else { + fprintf(stderr, "No valid argument to --key-expiration found!\n"); + fprintf(stderr, "Currently recognised arguments are: 'sign'\n"); + + return -1; + } +} + const ed25519_public_key_t * get_master_identity_key(void) { diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index c10cf32a71..0cf13e7600 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -63,6 +63,7 @@ MOCK_DECL(int, check_tap_onion_key_crosscert,(const uint8_t *crosscert, const ed25519_public_key_t *master_id_pkey, const uint8_t *rsa_id_digest)); +int log_cert_expiration(void); int load_ed_keys(const or_options_t *options, time_t now); int should_make_new_ed_keys(const or_options_t *options, const time_t now); diff --git a/src/test/include.am b/src/test/include.am index 2e448c8b39..230a722017 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -34,6 +34,7 @@ endif TESTS += src/test/test src/test/test-slow src/test/test-memwipe \ src/test/test_workqueue \ src/test/test_keygen.sh \ + src/test/test_key_expiration.sh \ src/test/test-timers \ $(TESTSCRIPTS) @@ -325,6 +326,7 @@ EXTRA_DIST += \ src/test/slownacl_curve25519.py \ src/test/zero_length_keys.sh \ src/test/test_keygen.sh \ + src/test/test_key_expiration.sh \ src/test/test_zero_length_keys.sh \ src/test/test_ntor.sh src/test/test_hs_ntor.sh src/test/test_bt.sh \ src/test/test-network.sh \ diff --git a/src/test/test_key_expiration.sh b/src/test/test_key_expiration.sh new file mode 100755 index 0000000000..95d7911f04 --- /dev/null +++ b/src/test/test_key_expiration.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +# Note: some of this code is lifted from zero_length_keys.sh and +# test_keygen.sh, and could be unified. + +umask 077 +set -e + +if [ $# -eq 0 ] || [ ! -f ${1} ] || [ ! -x ${1} ]; then + if [ "$TESTING_TOR_BINARY" = "" ] ; then + echo "Usage: ${0} PATH_TO_TOR [case-number]" + exit 1 + fi +fi + +if [ $# -ge 1 ]; then + TOR_BINARY="${1}" + shift +else + TOR_BINARY="${TESTING_TOR_BINARY}" +fi + +if [ $# -ge 1 ]; then + dflt=0 +else + dflt=1 +fi + +CASE1=$dflt +CASE2=$dflt +CASE3=$dflt + +if [ $# -ge 1 ]; then + eval "CASE${1}"=1 +fi + + +dump() { xxd -p "$1" | tr -d '\n '; } +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: `dump $1` vs `dump $2`"; } +check_keys_eq() { check_files_eq "${SRC}/keys/${1}" "${ME}/keys/${1}"; } + +DATA_DIR=`mktemp -d -t tor_key_expiration_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 + +# Use an absolute path for this or Tor will complain +DATA_DIR=`cd "${DATA_DIR}" && pwd` + +touch "${DATA_DIR}/empty_torrc" + +QUIETLY="--hush" +SILENTLY="--quiet" +TOR="${TOR_BINARY} --DisableNetwork 1 --ShutdownWaitLength 0 --ORPort 12345 --ExitRelay 0 -f ${DATA_DIR}/empty_torrc --DataDirectory ${DATA_DIR}" + +##### SETUP +# +# Here we create a set of keys. + +# Step 1: Start Tor with --list-fingerprint --quiet. Make sure everything is there. +echo "Setup step #1" +${TOR} --list-fingerprint ${SILENTLY} > /dev/null + +check_dir "${DATA_DIR}/keys" +check_file "${DATA_DIR}/keys/ed25519_master_id_public_key" +check_file "${DATA_DIR}/keys/ed25519_master_id_secret_key" +check_file "${DATA_DIR}/keys/ed25519_signing_cert" +check_file "${DATA_DIR}/keys/ed25519_signing_secret_key" +check_file "${DATA_DIR}/keys/secret_id_key" +check_file "${DATA_DIR}/keys/secret_onion_key" +check_file "${DATA_DIR}/keys/secret_onion_key_ntor" + +##### TEST CASES + +echo "=== Starting key expiration tests." + +FN="${DATA_DIR}/stderr" + +if [ "$CASE1" = 1 ]; then + echo "==== Case 1: Test --key-expiration without argument and ensure usage" + echo " instructions are printed." + + ${TOR} ${QUIETLY} --key-expiration 2>"$FN" + grep "No valid argument to --key-expiration found!" "$FN" >/dev/null || \ + die "Tor didn't mention supported --key-expiration argmuents" + + echo "==== Case 1: ok" +fi + +if [ "$CASE2" = 1 ]; then + echo "==== Case 2: Start Tor with --key-expiration 'sign' and make sure it prints an expiration." + + ${TOR} ${QUIETLY} --key-expiration sign 2>"$FN" + grep "signing-cert-expiry:" "$FN" >/dev/null || \ + die "Tor didn't print an expiration" + + echo "==== Case 2: ok" +fi + +if [ "$CASE3" = 1 ]; then + echo "==== Case 3: Start Tor with --key-expiration 'sign', when there is no" + echo " signing key, and make sure that Tor generates a new key" + echo " and prints its certificate's expiration." + + mv "${DATA_DIR}/keys/ed25519_signing_cert" \ + "${DATA_DIR}/keys/ed25519_signing_cert.bak" + + ${TOR} --key-expiration sign > "$FN" 2>&1 + grep "It looks like I need to generate and sign a new medium-term signing key" "$FN" >/dev/null || \ + die "Tor didn't create a new signing key" + check_file "${DATA_DIR}/keys/ed25519_signing_cert" + grep "signing-cert-expiry:" "$FN" >/dev/null || \ + die "Tor didn't print an expiration" + + mv "${DATA_DIR}/keys/ed25519_signing_cert.bak" \ + "${DATA_DIR}/keys/ed25519_signing_cert" + + echo "==== Case 3: ok" +fi