Checkpoint work on ed25519 keygen improvements.

Needs changes file, documentation, test integration, more tests.
This commit is contained in:
Nick Mathewson 2015-08-05 21:09:21 -04:00
parent 1ccba302f7
commit f362e7a873
4 changed files with 468 additions and 16 deletions

View File

@ -1912,6 +1912,8 @@ static const struct {
{ "--dump-config", ARGUMENT_OPTIONAL }, { "--dump-config", ARGUMENT_OPTIONAL },
{ "--list-fingerprint", TAKES_NO_ARGUMENT }, { "--list-fingerprint", TAKES_NO_ARGUMENT },
{ "--keygen", TAKES_NO_ARGUMENT }, { "--keygen", TAKES_NO_ARGUMENT },
{ "--no-passphrase", TAKES_NO_ARGUMENT },
{ "--passphrase-fd", ARGUMENT_NECESSARY },
{ "--verify-config", TAKES_NO_ARGUMENT }, { "--verify-config", TAKES_NO_ARGUMENT },
{ "--ignore-missing-torrc", TAKES_NO_ARGUMENT }, { "--ignore-missing-torrc", TAKES_NO_ARGUMENT },
{ "--quiet", 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, retval = options_init_from_string(cf_defaults, cf, command, command_arg,
&errmsg); &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: err:
tor_free(cf); tor_free(cf);

View File

@ -4292,6 +4292,13 @@ typedef struct {
/** How long before auth keys expire will we try to make a new one? */ /** How long before auth keys expire will we try to make a new one? */
int TestingAuthKeySlop; 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; } or_options_t;
/** Persistent state for an onion router, as saved to disk. */ /** Persistent state for an onion router, as saved to disk. */

View File

@ -11,6 +11,72 @@
#define ENC_KEY_HEADER "Boxed Ed25519 key" #define ENC_KEY_HEADER "Boxed Ed25519 key"
#define ENC_KEY_TAG "master" #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 int
read_encrypted_secret_key(ed25519_secret_key_t *out, read_encrypted_secret_key(ed25519_secret_key_t *out,
const char *fname) const char *fname)
@ -41,22 +107,24 @@ read_encrypted_secret_key(ed25519_secret_key_t *out,
while (1) { while (1) {
ssize_t pwlen = 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) { if (pwlen < 0) {
saved_errno = EINVAL; saved_errno = EINVAL;
goto done; goto done;
} }
const int r = crypto_unpwbox(&secret, &secret_len, const int r = crypto_unpwbox(&secret, &secret_len,
encrypted_key, encrypted_len, encrypted_key, encrypted_len,
pwbuf, pwlen); pwbuf, pwlen);
if (r == UNPWBOX_CORRUPTED) { if (r == UNPWBOX_CORRUPTED) {
log_err(LD_OR, "%s is corrupted.", fname); log_err(LD_OR, "%s is corrupted.", fname);
puts("E");
saved_errno = EINVAL; saved_errno = EINVAL;
goto done; goto done;
} else if (r == UNPWBOX_OKAY) { } else if (r == UNPWBOX_OKAY) {
break; break;
} }
/* Otherwise, passphrase is bad, so try again till user does ctrl-c or gets /* Otherwise, passphrase is bad, so try again till user does ctrl-c or gets
* it right. */ * it right. */
} }
@ -87,22 +155,23 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key,
const char *fname) const char *fname)
{ {
int r = -1; int r = -1;
char pwbuf0[256], pwbuf1[256]; char pwbuf0[256];
uint8_t *encrypted_key = NULL; uint8_t *encrypted_key = NULL;
size_t encrypted_len = 0; size_t encrypted_len = 0;
while (1) { if (do_getpass("Enter new passphrase:", pwbuf0, sizeof(pwbuf0), 1,
if (tor_getpass("Enter passphrase:", pwbuf0, sizeof(pwbuf0)) < 0) get_options()) < 0) {
return -1; log_warn(LD_OR, "NO/failed passphrase");
if (tor_getpass(" One more time:", pwbuf1, sizeof(pwbuf1)) < 0) return -1;
return -1;
if (!strcmp(pwbuf0, pwbuf1))
break;
fprintf(stderr, "That didn't match.\n");
} }
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, if (crypto_pwbox(&encrypted_key, &encrypted_len,
key->seckey, sizeof(key->seckey), key->seckey, sizeof(key->seckey),
pwbuf0, strlen(pwbuf0), 0) < 0) { pwbuf0, strlen(pwbuf0), 0) < 0) {
@ -121,7 +190,6 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key,
tor_free(encrypted_key); tor_free(encrypted_key);
} }
memwipe(pwbuf0, 0, sizeof(pwbuf0)); memwipe(pwbuf0, 0, sizeof(pwbuf0));
memwipe(pwbuf1, 0, sizeof(pwbuf1));
return r; return r;
} }
@ -134,7 +202,9 @@ write_secret_key(const ed25519_secret_key_t *key, int encrypted,
if (encrypted) { if (encrypted) {
int r = write_encrypted_secret_key(key, encrypted_fname); int r = write_encrypted_secret_key(key, encrypted_fname);
if (r != 0) 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); 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. */ * it, if we loaded it in the first place. */
memwipe(id->seckey.seckey, 0, sizeof(id->seckey)); memwipe(id->seckey.seckey, 0, sizeof(id->seckey));
if (options->command == CMD_KEYGEN)
goto end;
if (!rsa_ed_crosscert && server_mode(options)) { if (!rsa_ed_crosscert && server_mode(options)) {
uint8_t *crosscert; uint8_t *crosscert;
ssize_t crosscert_len = tor_make_rsa_ed25519_crosscert(&id->pubkey, 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. */ /* We've generated or loaded everything. Put them in memory. */
end:
if (! master_identity_key) { if (! master_identity_key) {
SET_KEY(master_identity_key, id); SET_KEY(master_identity_key, id);
} else { } else {

332
src/test/test_keygen.sh Executable file
View File

@ -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.