[tor-commits] [tor/master] Checkpoint work on ed25519 keygen improvements.

nickm at torproject.org nickm at torproject.org
Wed Aug 19 17:37:40 UTC 2015


commit f362e7a873f4a582e01371f001665eaaf0382434
Author: Nick Mathewson <nickm at torproject.org>
Date:   Wed Aug 5 21:09:21 2015 -0400

    Checkpoint work on ed25519 keygen improvements.
    
    Needs changes file, documentation, test integration, more tests.
---
 src/or/config.c         |   39 ++++++
 src/or/or.h             |    7 +
 src/or/routerkeys.c     |  104 ++++++++++++---
 src/test/test_keygen.sh |  332 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 467 insertions(+), 15 deletions(-)

diff --git a/src/or/config.c b/src/or/config.c
index 618c941..d22452b 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 f6aee13..96e69bc 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 47a6c89..b15d31b 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 (do_getpass("Enter new passphrase:", pwbuf0, sizeof(pwbuf0), 1,
+                 get_options()) < 0) {
+    log_warn(LD_OR, "NO/failed passphrase");
+    return -1;
+  }
 
-    if (!strcmp(pwbuf0, pwbuf1))
-      break;
-    fprintf(stderr, "That didn't match.\n");
+  if (strlen(pwbuf0) == 0) {
+    if (get_options()->keygen_force_passphrase == FORCE_PASSPHRASE_ON)
+      return -1;
+    else
+      return 0;
   }
-  if (0 == strlen(pwbuf0))
-    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 0000000..7c97a7d
--- /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.
+





More information about the tor-commits mailing list