Browse Source

routerkeys: Add cmdline option for learning signing key expiration.

 * CLOSES #17639.
 * ADDS new --key-expiration commandline option which prints when the
   signing key expires.
Isis Lovecruft 6 years ago
parent
commit
b2a7e8df90
9 changed files with 259 additions and 1 deletions
  1. 4 0
      changes/bug17639
  2. 10 0
      doc/tor.1.txt
  3. 4 0
      src/or/config.c
  4. 5 0
      src/or/main.c
  5. 2 1
      src/or/or.h
  6. 102 0
      src/or/routerkeys.c
  7. 1 0
      src/or/routerkeys.h
  8. 2 0
      src/test/include.am
  9. 129 0
      src/test/test_key_expiration.sh

+ 4 - 0
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.

+ 10 - 0
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

+ 4 - 0
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")) {

+ 5 - 0
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;

+ 2 - 1
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. */
 

+ 102 - 0
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 <b>expiration</b> for a certificate with
+ * some <b>description</b> 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, <b>cert</b>, with some <b>description</b> and
+ * stored in a file named <b>fname</b>, 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)
 {

+ 1 - 0
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);
 

+ 2 - 0
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 \

+ 129 - 0
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