diff --git a/configure.ac b/configure.ac index 9f21cfedd..195e56fc9 100644 --- a/configure.ac +++ b/configure.ac @@ -386,6 +386,7 @@ AC_CHECK_FUNCS( ftime \ getaddrinfo \ getifaddrs \ + getpass \ getrlimit \ gettimeofday \ gmtime_r \ @@ -399,6 +400,7 @@ AC_CHECK_FUNCS( pipe \ pipe2 \ prctl \ + readpassphrase \ rint \ sigaction \ socketpair \ @@ -945,6 +947,7 @@ AC_CHECK_HEADERS( netinet/in.h \ netinet/in6.h \ pwd.h \ + readpassphrase.h \ stdint.h \ sys/eventfd.h \ sys/file.h \ diff --git a/src/common/compat.c b/src/common/compat.c index 8da7ef3f6..701027523 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -67,6 +67,9 @@ #ifdef HAVE_CRT_EXTERNS_H #include #endif +#ifdef HAVE_READPASSPHRASE_H +#include +#endif #ifndef HAVE_GETTIMEOFDAY #ifdef HAVE_FTIME @@ -3242,3 +3245,33 @@ tor_sleep_msec(int msec) } #endif +/** Emit the password prompt prompt, then read up to buflen + * characters of passphrase into output. */ +ssize_t +tor_getpass(const char *prompt, char *output, size_t buflen) +{ + tor_assert(buflen <= SSIZE_MAX); +#if defined(HAVE_READPASSPHRASE) + char *pwd = readpassphrase(prompt, output, buflen, RPP_ECHO_OFF); + if (pwd == NULL) + return -1; + return strlen(pwd); +#elif defined(HAVE_GETPASS) + /* XXX We shouldn't actually use this; it's deprecated to hell and back */ + memset(output, 0, buflen); + char *pwd = getpass(prompt); + if (pwd == NULL) + return -1; + ssize_t len = (ssize_t)strlen(pwd); + strlcpy(output, pwd, buflen); + memset(pwd, 0, len); + return len; +#else + /* XXX This is even worse. */ + puts(prompt); + ssize_t n = read(STDIN_FILENO, output, buflen); + if (n < 0) + return -1; + return n; +#endif +} diff --git a/src/common/compat.h b/src/common/compat.h index 5189b7e05..549ed827d 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -708,6 +708,8 @@ STATIC int tor_ersatz_socketpair(int family, int type, int protocol, #endif #endif +ssize_t tor_getpass(const char *prompt, char *output, size_t buflen); + /* This needs some of the declarations above so we include it here. */ #include "compat_threads.h" diff --git a/src/or/config.c b/src/or/config.c index ef249a653..d81bc532b 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1909,6 +1909,7 @@ static const struct { { "--hash-password", ARGUMENT_NECESSARY }, { "--dump-config", ARGUMENT_OPTIONAL }, { "--list-fingerprint", TAKES_NO_ARGUMENT }, + { "--keygen", TAKES_NO_ARGUMENT }, { "--verify-config", TAKES_NO_ARGUMENT }, { "--ignore-missing-torrc", TAKES_NO_ARGUMENT }, { "--quiet", TAKES_NO_ARGUMENT }, @@ -4434,7 +4435,9 @@ options_init_from_torrc(int argc, char **argv) command = CMD_RUN_TOR; for (p_index = cmdline_only_options; p_index; p_index = p_index->next) { - if (!strcmp(p_index->key,"--list-fingerprint")) { + if (!strcmp(p_index->key,"--keygen")) { + command = CMD_KEYGEN; + } else if (!strcmp(p_index->key,"--list-fingerprint")) { command = CMD_LIST_FINGERPRINT; } else if (!strcmp(p_index->key, "--hash-password")) { command = CMD_HASH_PASSWORD; diff --git a/src/or/main.c b/src/or/main.c index 17177b0df..af77f28df 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -3162,6 +3162,9 @@ tor_main(int argc, char *argv[]) #endif result = do_main_loop(); break; + case CMD_KEYGEN: + result = load_ed_keys(get_options(), time(NULL)); + break; case CMD_LIST_FINGERPRINT: result = do_list_fingerprint(); break; diff --git a/src/or/or.h b/src/or/or.h index ec5f2774b..9e7b7534f 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3405,7 +3405,8 @@ typedef struct { /** What should the tor process actually do? */ enum { CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, - CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG + CMD_VERIFY_CONFIG, CMD_RUN_UNITTESTS, CMD_DUMP_CONFIG, + CMD_KEYGEN } command; char *command_arg; /**< Argument for command-line option. */ diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index e79204cf0..277dc6e4a 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -4,9 +4,130 @@ #include "or.h" #include "config.h" #include "router.h" +#include "crypto_pwbox.h" #include "routerkeys.h" #include "torcert.h" +#define ENC_KEY_HEADER "Boxed Ed25519 key" +#define ENC_KEY_TAG "master" + +int +read_encrypted_secret_key(ed25519_secret_key_t *out, + const char *fname) +{ + int r = -1; + uint8_t *secret = NULL; + size_t secret_len = 0; + char pwbuf[256]; + uint8_t encrypted_key[256]; + char *tag = NULL; + + ssize_t encrypted_len = crypto_read_tagged_contents_from_file(fname, + ENC_KEY_HEADER, + &tag, + encrypted_key, + sizeof(encrypted_key)); + if (encrypted_len < 0) { + log_info(LD_OR, "%s is missing", fname); + return 0; + } + if (strcmp(tag, ENC_KEY_TAG)) + return -1; + + while (1) { + ssize_t pwlen = + tor_getpass("Enter pasphrase for master key:", pwbuf, sizeof(pwbuf)); + if (pwlen < 0) + 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); + goto done; + } else if (r == UNPWBOX_OKAY) { + break; + } + /* Otherwise, passphrase is bad, so try again till user does ctrl-c or gets + * it right. */ + } + + + if (secret_len != ED25519_SECKEY_LEN) { + log_err(LD_OR, "%s is corrupted.", fname); + goto done; + } + memcpy(out->seckey, secret, ED25519_SECKEY_LEN); + r = 1; + + done: + memwipe(encrypted_key, 0, encrypted_len); + memwipe(pwbuf, 0, sizeof(pwbuf)); + if (secret) { + memwipe(secret, 0, secret_len); + tor_free(secret); + } + return r; +} + +int +write_encrypted_secret_key(const ed25519_secret_key_t *key, + const char *fname) +{ + int r = -1; + char pwbuf0[256], pwbuf1[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 (0 == strlen(pwbuf0)) + return 0; + if (crypto_pwbox(&encrypted_key, &encrypted_len, + key->seckey, sizeof(key->seckey), + pwbuf0, strlen(pwbuf0), 0) < 0) { + log_warn(LD_OR, "crypto_pwbox failed!?"); + goto done; + } + if (crypto_write_tagged_contents_to_file(fname, + ENC_KEY_HEADER, + ENC_KEY_TAG, + encrypted_key, encrypted_len) < 0) + goto done; + r = 1; + done: + if (encrypted_key) { + memwipe(encrypted_key, 0, encrypted_len); + tor_free(encrypted_key); + } + memwipe(pwbuf0, 0, sizeof(pwbuf0)); + memwipe(pwbuf1, 0, sizeof(pwbuf1)); + return r; +} + +static int +write_secret_key(const ed25519_secret_key_t *key, int encrypted, + const char *fname, + const char *fname_tag, + const char *encrypted_fname) +{ + if (encrypted) { + int r = write_encrypted_secret_key(key, encrypted_fname); + if (r != 0) + return r; + } + return ed25519_seckey_write_to_file(key, fname, fname_tag); +} + /** * Read an ed25519 key and associated certificates from files beginning with * fname, with certificate type cert_type. On failure, return @@ -38,6 +159,9 @@ * * If INIT_ED_KEY_OMIT_SECRET is set in flags, do not even try to * load or return a secret key (but create and save on if needed). + * + * If INIT_ED_KEY_TRY_ENCRYPTED is set, we look for an encrypted secret key + * and consider encrypting any new secret key. */ ed25519_keypair_t * ed_key_init_from_file(const char *fname, uint32_t flags, @@ -49,10 +173,12 @@ ed_key_init_from_file(const char *fname, uint32_t flags, struct tor_cert_st **cert_out) { char *secret_fname = NULL; + char *encrypted_secret_fname = NULL; char *public_fname = NULL; char *cert_fname = NULL; int created_pk = 0, created_sk = 0, created_cert = 0; const int try_to_load = ! (flags & INIT_ED_KEY_REPLACE); + const int encrypt_key = (flags & INIT_ED_KEY_TRY_ENCRYPTED); char tag[8]; tor_snprintf(tag, sizeof(tag), "type%d", (int)cert_type); @@ -62,15 +188,26 @@ ed_key_init_from_file(const char *fname, uint32_t flags, ed25519_keypair_t *keypair = tor_malloc_zero(sizeof(ed25519_keypair_t)); tor_asprintf(&secret_fname, "%s_secret_key", fname); + tor_asprintf(&encrypted_secret_fname, "%s_secret_key_encrypted", fname); tor_asprintf(&public_fname, "%s_public_key", fname); tor_asprintf(&cert_fname, "%s_cert", fname); /* Try to read the secret key. */ - const int have_secret = try_to_load && + int have_secret = try_to_load && !(flags & INIT_ED_KEY_OMIT_SECRET) && ed25519_seckey_read_from_file(&keypair->seckey, &got_tag, secret_fname) == 0; + /* Should we try for an encrypted key? */ + if (!have_secret && try_to_load && encrypt_key) { + int r = read_encrypted_secret_key(&keypair->seckey, + encrypted_secret_fname); + if (r > 0) { + have_secret = 1; + got_tag = tor_strdup(tag); + } + } + if (have_secret) { if (strcmp(got_tag, tag)) { tor_log(severity, LD_OR, "%s has wrong tag", secret_fname); @@ -115,7 +252,9 @@ ed_key_init_from_file(const char *fname, uint32_t flags, } created_pk = created_sk = created_cert = 1; - if (ed25519_seckey_write_to_file(&keypair->seckey, secret_fname, tag) < 0 + if (write_secret_key(&keypair->seckey, + encrypt_key, + secret_fname, tag, encrypted_secret_fname) < 0 || (split && ed25519_pubkey_write_to_file(&keypair->pubkey, public_fname, tag) < 0) @@ -215,6 +354,7 @@ ed_key_init_from_file(const char *fname, uint32_t flags, unlink(cert_fname); cleanup: + tor_free(encrypted_secret_fname); tor_free(secret_fname); tor_free(public_fname); tor_free(cert_fname); @@ -329,7 +469,8 @@ load_ed_keys(const or_options_t *options, time_t now) const int need_new_signing_key = NULL == use_signing || - EXPIRES_SOON(check_signing_cert, 0); + EXPIRES_SOON(check_signing_cert, 0) || + options->command == CMD_KEYGEN; const int want_new_signing_key = need_new_signing_key || EXPIRES_SOON(check_signing_cert, options->TestingSigningKeySlop); @@ -342,6 +483,8 @@ load_ed_keys(const or_options_t *options, time_t now) flags |= INIT_ED_KEY_MISSING_SECRET_OK; if (! want_new_signing_key) flags |= INIT_ED_KEY_OMIT_SECRET; + if (options->command == CMD_KEYGEN) + flags |= INIT_ED_KEY_TRY_ENCRYPTED; char *fname = options_get_datadir_fname2(options, "keys", "ed25519_master_id"); diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index b45a22ac1..1e0199e5e 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -14,6 +14,7 @@ #define INIT_ED_KEY_EXTRA_STRONG (1u<<5) #define INIT_ED_KEY_INCLUDE_SIGNING_KEY_IN_CERT (1u<<6) #define INIT_ED_KEY_OMIT_SECRET (1u<<7) +#define INIT_ED_KEY_TRY_ENCRYPTED (1u<<8) struct tor_cert_st; ed25519_keypair_t *ed_key_init_from_file(const char *fname, uint32_t flags, @@ -61,6 +62,11 @@ int should_make_new_ed_keys(const or_options_t *options, const time_t now); int generate_ed_link_cert(const or_options_t *options, time_t now); +int read_encrypted_secret_key(ed25519_secret_key_t *out, + const char *fname); +int write_encrypted_secret_key(const ed25519_secret_key_t *out, + const char *fname); + void routerkeys_free_all(void); #endif