diff --git a/src/common/crypto.c b/src/common/crypto.c index 2608c22a29..6b5c952be5 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -1575,6 +1575,47 @@ base32_encode(char *dest, size_t destlen, const char *src, size_t srclen) dest[i] = '\0'; } +/** Implement RFC2440-style iterated-salted S2K conversion: convert the + * secret_len-byte secret into a key_out_len byte + * key_out. As in RFC2440, the first 8 bytes of s2k_specifier + * are a salt; the 9th byte describes how much iteration to do. + * Does not support key_out_len > DIGEST_LEN. + */ +void +secret_to_key(char *key_out, size_t key_out_len, const char *secret, + size_t secret_len, const char *s2k_specifier) +{ + crypto_digest_env_t *d; + uint8_t c; + size_t count; + char *tmp; + +#define EXPBIAS 6 + c = s2k_specifier[8]; + count = ((uint32_t)16 + (c & 15)) << ((c >> 4) + EXPBIAS); +#undef EXPBIAS + + tor_assert(key_out_len <= DIGEST_LEN); + + d = crypto_new_digest_env(); + tmp = tor_malloc(8+secret_len); + memcpy(tmp,s2k_specifier,8); + memcpy(tmp+8,secret,secret_len); + secret_len += 8; + while (count) { + if (count >= secret_len) { + crypto_digest_add_bytes(d, tmp, secret_len); + count -= secret_len; + } else { + crypto_digest_add_bytes(d, tmp, count); + count = 0; + } + } + crypto_digest_get_digest(d, key_out, key_out_len); + tor_free(tmp); + crypto_free_digest_env(d); +} + /* Local Variables: mode:c diff --git a/src/common/crypto.h b/src/common/crypto.h index a969d744fd..7042da3841 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -153,6 +153,10 @@ int base64_decode(char *dest, size_t destlen, const char *src, size_t srclen); #define BASE32_CHARS "abcdefghijklmnopqrstuvwxyz234567" void base32_encode(char *dest, size_t destlen, const char *src, size_t srclen); +#define S2K_SPECIFIER_LEN 9 +void secret_to_key(char *key_out, size_t key_out_len, const char *secret, + size_t secret_len, const char *s2k_specifier); + #endif /* diff --git a/src/or/config.c b/src/or/config.c index 446966994e..7af93ff65a 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -101,6 +101,7 @@ static config_var_t config_vars[] = { VAR("MyFamily", STRING, MyFamily, NULL), VAR("NodeFamily", LINELIST, NodeFamilies, NULL), VAR("Group", STRING, Group, NULL), + VAR("HashedControlPassword",STRING, HashedControlPassword, NULL), VAR("HttpProxy", STRING, HttpProxy, NULL), VAR("HiddenServiceDir", LINELIST, RendConfigLines, NULL), VAR("HiddenServicePort", LINELIST, RendConfigLines, NULL), @@ -183,12 +184,13 @@ config_get_commandlines(int argc, char **argv) int i = 1; while (i < argc-1) { - if (!strcmp(argv[i],"-f")) { -// log(LOG_DEBUG,"Commandline: skipping over -f."); - i += 2; /* this is the config file option. ignore it. */ + if (!strcmp(argv[i],"-f") || + !strcmp(argv[i],"--hash-password")) { + i += 2; /* command-line option with argument. ignore them. */ continue; } else if (!strcmp(argv[i],"--list-fingerprint")) { i += 1; /* command-line option. ignore it. */ + continue; } new = tor_malloc(sizeof(struct config_line_t)); @@ -803,7 +805,12 @@ getconfig(int argc, char **argv, or_options_t *options) ++i; } else if (!strcmp(argv[i],"--list-fingerprint")) { options->command = CMD_LIST_FINGERPRINT; + } else if (!strcmp(argv[i],"--hash-password")) { + options->command = CMD_HASH_PASSWORD; + options->command_arg = tor_strdup(argv[i+1]); + ++i; } + } if (using_default_torrc) { diff --git a/src/or/control.c b/src/or/control.c index 5ffcd28072..b7e68dfa1d 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -47,6 +47,10 @@ extern or_options_t options; static uint32_t global_event_mask = 0; +#define AUTHENTICATION_COOKIE_LEN 32 +static int authentication_cookie_is_set = 0; +static char authentication_cookie[AUTHENTICATION_COOKIE_LEN]; + static void update_global_event_mask(void); static void send_control_message(connection_t *conn, uint16_t type, uint16_t len, const char *body); @@ -216,13 +220,34 @@ static int handle_control_setevents(connection_t *conn, uint16_t len, static int handle_control_authenticate(connection_t *conn, uint16_t len, const char *body) { - if (0/* XXXX009 NM */) { - send_control_done(conn); - conn->state = CONTROL_CONN_STATE_OPEN; - } else { - send_control_error(conn, ERR_FAILED_AUTHENTICATION,"Authentication failed"); + if (len == AUTHENTICATION_COOKIE_LEN && + authentication_cookie_is_set && + !memcmp(authentication_cookie, body, len)) { + goto ok; + } else if (options.HashedControlPassword) { + char expected[S2K_SPECIFIER_LEN+DIGEST_LEN]; + char received[DIGEST_LEN]; + if (base64_decode(expected,sizeof(expected), + options.HashedControlPassword, + strlen(options.HashedControlPassword))<0) { + /* XXXX009 NM we should warn sooner. */ + log_fn(LOG_WARN,"Couldn't decode HashedControlPassword: invalid base64"); + goto err; + } + secret_to_key(received,DIGEST_LEN,body,len,expected); + if (!memcmp(expected+S2K_SPECIFIER_LEN, received, DIGEST_LEN)) + goto ok; } + + err: + send_control_error(conn, ERR_FAILED_AUTHENTICATION,"Authentication failed"); return 0; + ok: + log_fn(LOG_INFO, "Authenticated control connection (%d)", conn->s); + send_control_done(conn); + conn->state = CONTROL_CONN_STATE_OPEN; + return 0; + } int connection_control_finished_flushing(connection_t *conn) { @@ -391,6 +416,25 @@ void control_event_logmsg(int severity, const char *msg) send_control_event(EVENT_WARNING, (uint16_t)(len+1), msg); } +int init_cookie_authentication(void) +{ + char fname[512]; + + /* XXXX009 NM add config option to disable this. */ + + tor_snprintf(fname, sizeof(fname), "%s/control_auth_cookie", + get_data_directory(&options)); + crypto_rand(authentication_cookie, AUTHENTICATION_COOKIE_LEN); + authentication_cookie_is_set = 1; + if (write_bytes_to_file(fname, authentication_cookie, + AUTHENTICATION_COOKIE_LEN, 1)) { + log_fn(LOG_WARN,"Error writing authentication cookie."); + return -1; + } + + return 0; +} + /* Local Variabls: mode:c diff --git a/src/or/main.c b/src/or/main.c index 812f8c96a7..87e729beac 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -1373,6 +1373,25 @@ static void do_list_fingerprint(void) } } +/** DOCDOC **/ +static void do_hash_password(void) +{ + + char output[256]; + char key[S2K_SPECIFIER_LEN+DIGEST_LEN]; + + crypto_rand(key, S2K_SPECIFIER_LEN-1); + key[S2K_SPECIFIER_LEN-1] = (uint8_t)96; /* Hash 64 K of data. */ + secret_to_key(key+S2K_SPECIFIER_LEN, DIGEST_LEN, + options.command_arg, strlen(options.command_arg), + key); + if (base64_encode(output, sizeof(output), key, sizeof(key))<0) { + log_fn(LOG_ERR, "Unable to compute base64"); + } else { + printf("%s",output); + } +} + #ifdef MS_WINDOWS_SERVICE void nt_service_control(DWORD request) { @@ -1449,6 +1468,9 @@ int tor_main(int argc, char *argv[]) { case CMD_LIST_FINGERPRINT: do_list_fingerprint(); break; + case CMD_HASH_PASSWORD: + do_hash_password(); + break; default: log_fn(LOG_ERR, "Illegal command number %d: internal error.", options.command); diff --git a/src/or/or.h b/src/or/or.h index a25a909c80..e69ff558d5 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -852,8 +852,10 @@ typedef struct exit_redirect_t { typedef struct { /** What should the tor process actually do? */ enum { - CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT + CMD_RUN_TOR=0, CMD_LIST_FINGERPRINT, CMD_HASH_PASSWORD, } command; + const char *command_arg; /**< Argument for command-line option. */ + struct config_line_t *LogOptions; /**< List of configuration lines * for logfiles */ @@ -949,6 +951,8 @@ typedef struct { int AccountingMaxKB; /**< How many KB do we allow per accounting * interval before hibernation? 0 for "never * hibernate." */ + char *HashedControlPassword; /**< Base64-encoded hash of a password for + * the control system. */ } or_options_t; /* XXX are these good enough defaults? */ @@ -1241,6 +1245,8 @@ int control_event_or_conn_status(connection_t *conn, or_conn_status_event_t e); int control_event_bandwidth_used(uint32_t n_read, uint32_t n_written); void control_event_logmsg(int severity, const char *msg); +int init_cookie_authentication(void); + /********************************* cpuworker.c *****************************/ void cpu_init(void);