/* Copyright (c) 2012, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file ext_orport.c * \brief Code implementing the Extended ORPort. */ #define EXT_ORPORT_PRIVATE #include "or.h" #include "connection.h" #include "connection_or.h" #include "ext_orport.h" #include "control.h" #include "config.h" #include "util.h" #include "main.h" /** Allocate and return a structure capable of holding an Extended * ORPort message of body length len. */ ext_or_cmd_t * ext_or_cmd_new(uint16_t len) { size_t size = STRUCT_OFFSET(ext_or_cmd_t, body) + len; ext_or_cmd_t *cmd = tor_malloc(size); cmd->len = len; return cmd; } /** Deallocate the Extended ORPort message in cmd. */ void ext_or_cmd_free(ext_or_cmd_t *cmd) { tor_free(cmd); } /** Get an Extended ORPort message from conn, and place it in * out. Return -1 on fail, 0 if we need more data, and 1 if we * successfully extracted an Extended ORPort command from the * buffer. */ static int connection_fetch_ext_or_cmd_from_buf(connection_t *conn, ext_or_cmd_t **out) { IF_HAS_BUFFEREVENT(conn, { struct evbuffer *input = bufferevent_get_input(conn->bufev); return fetch_ext_or_command_from_evbuffer(input, out); }) ELSE_IF_NO_BUFFEREVENT { return fetch_ext_or_command_from_buf(conn->inbuf, out); } } /** Write an Extended ORPort message to conn. Use * command as the command type, bodylen as the body * length, and body, if it's present, as the body of the * message. */ STATIC int connection_write_ext_or_command(connection_t *conn, uint16_t command, const char *body, size_t bodylen) { char header[4]; if (bodylen > UINT16_MAX) return -1; set_uint16(header, htons(command)); set_uint16(header+2, htons(bodylen)); connection_write_to_buf(header, 4, conn); if (bodylen) { tor_assert(body); connection_write_to_buf(body, bodylen, conn); } return 0; } /** Transition from an Extended ORPort which accepts Extended ORPort * messages, to an Extended ORport which accepts OR traffic. */ static void connection_ext_or_transition(or_connection_t *conn) { tor_assert(conn->base_.type == CONN_TYPE_EXT_OR); conn->base_.type = CONN_TYPE_OR; control_event_or_conn_status(conn, OR_CONN_EVENT_NEW, 0); connection_tls_start_handshake(conn, 1); } /** Length of authentication cookie. */ #define EXT_OR_PORT_AUTH_COOKIE_LEN 32 /** Length of the header of the cookie file. */ #define EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN 32 /** Total length of the cookie file. */ #define EXT_OR_PORT_AUTH_COOKIE_FILE_LEN \ EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN /** Static cookie file header. */ #define EXT_OR_PORT_AUTH_COOKIE_HEADER "! Extended ORPort Auth Cookie !\x0a" /** Length of safe-cookie protocol hashes. */ #define EXT_OR_PORT_AUTH_HASH_LEN DIGEST256_LEN /** Length of safe-cookie protocol nonces. */ #define EXT_OR_PORT_AUTH_NONCE_LEN 32 /** Safe-cookie protocol constants. */ #define EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST \ "ExtORPort authentication server-to-client hash" #define EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST \ "ExtORPort authentication client-to-server hash" /** If true, we've set ext_or_auth_cookie to a secret code and stored * it to disk. */ static int ext_or_auth_cookie_is_set = 0; /** If ext_or_auth_cookie_is_set, a secret cookie that we've stored to disk * and which we're using to authenticate controllers. (If the controller can * read it off disk, it has permission to connect.) */ static char ext_or_auth_cookie[EXT_OR_PORT_AUTH_COOKIE_LEN] = {0}; /** Helper: Return a newly allocated string containing a path to the * file where we store our authentication cookie. */ char * get_ext_or_auth_cookie_file_name(void) { const or_options_t *options = get_options(); if (options->ExtORPortCookieAuthFile && strlen(options->ExtORPortCookieAuthFile)) { return tor_strdup(options->ExtORPortCookieAuthFile); } else { return get_datadir_fname("extended_orport_auth_cookie"); } } /** Choose a random authentication cookie and write it to disk. * Anybody who can read the cookie from disk will be considered * authorized to use the control connection. Return -1 if we can't * write the file, or 0 on success. */ int init_ext_or_cookie_authentication(int is_enabled) { char *fname; char cookie_file_string[EXT_OR_PORT_AUTH_COOKIE_FILE_LEN]; if (!is_enabled) { ext_or_auth_cookie_is_set = 0; return 0; } /* We don't want to generate a new cookie every time we call * options_act(). One should be enough. */ if (ext_or_auth_cookie_is_set) return 0; /* all set */ if (crypto_rand(ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN) < 0) return -1; ext_or_auth_cookie_is_set = 1; memcpy(cookie_file_string, EXT_OR_PORT_AUTH_COOKIE_HEADER, EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN); memcpy(cookie_file_string+EXT_OR_PORT_AUTH_COOKIE_HEADER_LEN, ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN); fname = get_ext_or_auth_cookie_file_name(); if (write_bytes_to_file(fname, cookie_file_string, EXT_OR_PORT_AUTH_COOKIE_FILE_LEN, 1)) { log_warn(LD_FS,"Error writing authentication cookie to %s.", escaped(fname)); tor_free(fname); return -1; } log_info(LD_GENERAL, "Generated Extended ORPort cookie file in '%s'.", fname); memwipe(cookie_file_string, 0, sizeof(cookie_file_string)); tor_free(fname); return 0; } /** Read data from conn and see if the client sent us the * authentication type that she prefers to use in this session. * * Return -1 if we received corrupted data or if we don't support the * authentication type. Return 0 if we need more data in * conn. Return 1 if the authentication type negotiation was * successful. */ static int connection_ext_or_auth_neg_auth_type(connection_t *conn) { char authtype[1] = {0}; if (connection_get_inbuf_len(conn) < 1) return 0; if (connection_fetch_from_buf(authtype, 1, conn) < 0) return -1; log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); if (authtype[0] != 1) /* '1' is the only auth type supported atm */ return -1; conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE; return 1; } /** Read the client's nonce out of conn, setup the safe-cookie * crypto, and then send our own hash and nonce to the client * * Return -1 if there was an error; return 0 if we need more data in * conn, and return 1 if we successfully retrieved the * client's nonce and sent our own. */ static int connection_ext_or_auth_handle_client_nonce(connection_t *conn) { char server_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; char client_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; char server_nonce[EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; char reply[EXT_OR_PORT_AUTH_COOKIE_LEN+EXT_OR_PORT_AUTH_NONCE_LEN] = {0}; if (!ext_or_auth_cookie_is_set) { /* this should not happen */ log_warn(LD_BUG, "Extended ORPort authentication cookie was not set. " "That's weird since we should have done that on startup. " "This might be a Tor bug, please file a bug report. "); return -1; } if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) return 0; if (connection_fetch_from_buf(client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) return -1; /* DOCDOC comment this function more, with comments about what the * protocol is. */ /* Get our nonce */ if (crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN) < 0) return -1; { /* set up macs */ size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + 2*EXT_OR_PORT_AUTH_NONCE_LEN; size_t hmac_c_msg_len = strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + 2*EXT_OR_PORT_AUTH_NONCE_LEN; char *hmac_s_msg = tor_malloc_zero(hmac_s_msg_len); char *hmac_c_msg = tor_malloc_zero(hmac_c_msg_len); char *correct_client_hash = tor_malloc_zero(EXT_OR_PORT_AUTH_HASH_LEN); memcpy(hmac_s_msg, EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST, strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST)); memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST), client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); memcpy(hmac_s_msg + strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + EXT_OR_PORT_AUTH_NONCE_LEN, server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); memcpy(hmac_c_msg, EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST, strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST)); memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST), client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); memcpy(hmac_c_msg + strlen(EXT_OR_PORT_AUTH_CLIENT_TO_SERVER_CONST) + EXT_OR_PORT_AUTH_NONCE_LEN, server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); crypto_hmac_sha256(server_hash, ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN, hmac_s_msg, hmac_s_msg_len); crypto_hmac_sha256(correct_client_hash, ext_or_auth_cookie, EXT_OR_PORT_AUTH_COOKIE_LEN, hmac_c_msg, hmac_c_msg_len); /* Store the client hash we generated. We will need to compare it with the hash sent by the client. */ TO_OR_CONN(conn)->ext_or_auth_correct_client_hash = correct_client_hash; memwipe(hmac_s_msg, 0, hmac_s_msg_len); memwipe(hmac_c_msg, 0, hmac_c_msg_len); tor_free(hmac_s_msg); tor_free(hmac_c_msg); } { /* debug logging */ /* XXX disable this codepath if not logging on debug?*/ char server_hash_encoded[(2*EXT_OR_PORT_AUTH_HASH_LEN) + 1]; char server_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; char client_nonce_encoded[(2*EXT_OR_PORT_AUTH_NONCE_LEN) + 1]; base16_encode(server_hash_encoded, sizeof(server_hash_encoded), server_hash, sizeof(server_hash)); base16_encode(server_nonce_encoded, sizeof(server_nonce_encoded), server_nonce, sizeof(server_nonce)); base16_encode(client_nonce_encoded, sizeof(client_nonce_encoded), client_nonce, sizeof(client_nonce)); log_debug(LD_GENERAL, "server_hash: '%s'\nserver_nonce: '%s'\nclient_nonce: '%s'", server_hash_encoded, server_nonce_encoded, client_nonce_encoded); memwipe(server_hash_encoded, 0, sizeof(server_hash_encoded)); memwipe(server_nonce_encoded, 0, sizeof(server_nonce_encoded)); memwipe(client_nonce_encoded, 0, sizeof(client_nonce_encoded)); } { /* write reply: (server_hash, server_nonce) */ memcpy(reply, server_hash, EXT_OR_PORT_AUTH_HASH_LEN); memcpy(reply + EXT_OR_PORT_AUTH_HASH_LEN, server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); connection_write_to_buf(reply, sizeof(reply), conn); memwipe(reply, 0, sizeof(reply)); } log_debug(LD_GENERAL, "Got client nonce, and sent our own nonce and hash."); conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH; return 1; } #define connection_ext_or_auth_send_result_success(c) \ connection_ext_or_auth_send_result(c, 1) #define connection_ext_or_auth_send_result_fail(c) \ connection_ext_or_auth_send_result(c, 0) /** Send authentication results to conn. Successful results if * success is set; failure results otherwise. */ static void connection_ext_or_auth_send_result(connection_t *conn, int success) { if (success) connection_write_to_buf("\x01", 1, conn); else connection_write_to_buf("\x00", 1, conn); } /** Receive the client's hash from conn, validate that it's * correct, and then send the authentication results to the client. * * Return -1 if there was an error during validation; return 0 if we * need more data in conn, and return 1 if we successfully * validated the client's hash and sent a happy authentication * result. */ static int connection_ext_or_auth_handle_client_hash(connection_t *conn) { char provided_client_hash[EXT_OR_PORT_AUTH_HASH_LEN] = {0}; if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) return 0; if (connection_fetch_from_buf(provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) return -1; if (tor_memneq(TO_OR_CONN(conn)->ext_or_auth_correct_client_hash, provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN)) { log_warn(LD_GENERAL, "Incorrect client hash. Authentication failed."); connection_ext_or_auth_send_result_fail(conn); return -1; } log_debug(LD_GENERAL, "Got client's hash and it was legit."); /* send positive auth result */ connection_ext_or_auth_send_result_success(conn); conn->state = EXT_OR_CONN_STATE_OPEN; return 1; } /** Handle data from or_conn received on Extended ORPort. * Return -1 on error. 0 on unsufficient data. 1 on correct. */ static int connection_ext_or_auth_process_inbuf(or_connection_t *or_conn) { connection_t *conn = TO_CONN(or_conn); /* DOCDOC Document the state machine here! */ switch (conn->state) { /* Functionify */ case EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE: return connection_ext_or_auth_neg_auth_type(conn); case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_NONCE: return connection_ext_or_auth_handle_client_nonce(conn); case EXT_OR_CONN_STATE_AUTH_WAIT_CLIENT_HASH: return connection_ext_or_auth_handle_client_hash(conn); default: log_warn(LD_BUG, "Encountered unexpected connection state %d while trying " "to process Extended ORPort authentication data.", conn->state); return -1; } } /** Extended ORPort commands (Transport-to-Bridge) */ #define EXT_OR_CMD_TB_DONE 0x0000 #define EXT_OR_CMD_TB_USERADDR 0x0001 #define EXT_OR_CMD_TB_TRANSPORT 0x0002 /** Extended ORPort commands (Bridge-to-Transport) */ #define EXT_OR_CMD_BT_OKAY 0x1000 #define EXT_OR_CMD_BT_DENY 0x1001 #define EXT_OR_CMD_BT_CONTROL 0x1002 /** Process a USERADDR command from the Extended * ORPort. payload is a payload of size len. * * If the USERADDR command was well formed, change the address of * conn to the address on the USERADDR command. * * Return 0 on success and -1 on error. */ static int connection_ext_or_handle_cmd_useraddr(connection_t *conn, const char *payload, uint16_t len) { /* Copy address string. */ tor_addr_t addr; uint16_t port; char *addr_str; char *address_part=NULL; int res; if (memchr(payload, '\0', len)) { log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort UserAddr"); return -1; } addr_str = tor_memdup_nulterm(payload, len); res = tor_addr_port_split(LOG_INFO, addr_str, &address_part, &port); tor_free(addr_str); if (res<0) return -1; res = tor_addr_parse(&addr, address_part); tor_free(address_part); if (res<0) return -1; { /* do some logging */ char *old_address = tor_dup_addr(&conn->addr); char *new_address = tor_dup_addr(&addr); log_debug(LD_NET, "Received USERADDR." "We rewrite our address from '%s:%u' to '%s:%u'.", safe_str(old_address), conn->port, safe_str(new_address), port); tor_free(old_address); tor_free(new_address); } /* record the address */ tor_addr_copy(&conn->addr, &addr); conn->port = port; return 0; } /** Process a TRANSPORT command from the Extended * ORPort. payload is a payload of size len. * * If the TRANSPORT command was well formed, register the name of the * transport on conn. * * Return 0 on success and -1 on error. */ static int connection_ext_or_handle_cmd_transport(or_connection_t *conn, const char *payload, uint16_t len) { char *transport_str; if (memchr(payload, '\0', len)) { log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unexpected NUL in ExtORPort Transport"); return -1; } transport_str = tor_memdup_nulterm(payload, len); /* Transport names MUST be C-identifiers. */ if (!string_is_C_identifier(transport_str)) { tor_free(transport_str); return -1; } /* If ext_or_transport is already occupied (because the PT sent two * TRANSPORT commands), deallocate the old name and keep the new * one */ if (conn->ext_or_transport) tor_free(conn->ext_or_transport); conn->ext_or_transport = transport_str; return 0; } #define EXT_OR_CONN_STATE_IS_AUTHENTICATING(st) \ ((st) <= EXT_OR_CONN_STATE_AUTH_MAX) /** Process Extended ORPort messages from or_conn. */ int connection_ext_or_process_inbuf(or_connection_t *or_conn) { connection_t *conn = TO_CONN(or_conn); ext_or_cmd_t *command; int r; /* DOCDOC Document the state machine and transitions in this function */ /* If we are still in the authentication stage, process traffic as authentication data: */ while (EXT_OR_CONN_STATE_IS_AUTHENTICATING(conn->state)) { log_debug(LD_GENERAL, "Got Extended ORPort authentication data (%u).", (unsigned int) connection_get_inbuf_len(conn)); r = connection_ext_or_auth_process_inbuf(or_conn); if (r < 0) { connection_mark_for_close(conn); return -1; } else if (r == 0) { return 0; } /* if r > 0, loop and process more data (if any). */ } while (1) { log_debug(LD_GENERAL, "Got Extended ORPort data."); command = NULL; r = connection_fetch_ext_or_cmd_from_buf(conn, &command); if (r < 0) goto err; else if (r == 0) return 0; /* need to wait for more data */ /* Got a command! */ tor_assert(command); if (command->cmd == EXT_OR_CMD_TB_DONE) { if (connection_get_inbuf_len(conn)) { /* The inbuf isn't empty; the client is misbehaving. */ goto err; } log_debug(LD_NET, "Received DONE."); /* If the transport proxy did not use the TRANSPORT command to * specify the transport name, mark this as unknown transport. */ if (!or_conn->ext_or_transport) { /* We write this string this way to avoid ??>, which is a C * trigraph. */ or_conn->ext_or_transport = tor_strdup(""); } connection_write_ext_or_command(conn, EXT_OR_CMD_BT_OKAY, NULL, 0); /* can't transition immediately; need to flush first. */ conn->state = EXT_OR_CONN_STATE_FLUSHING; connection_stop_reading(conn); } else if (command->cmd == EXT_OR_CMD_TB_USERADDR) { if (connection_ext_or_handle_cmd_useraddr(conn, command->body, command->len) < 0) goto err; } else if (command->cmd == EXT_OR_CMD_TB_TRANSPORT) { if (connection_ext_or_handle_cmd_transport(or_conn, command->body, command->len) < 0) goto err; } else { log_notice(LD_NET,"Got Extended ORPort command we don't regognize (%u).", command->cmd); } ext_or_cmd_free(command); } return 0; err: ext_or_cmd_free(command); connection_mark_for_close(conn); return -1; } /** conn finished flushing Extended ORPort messages to the * network, and is now ready to accept OR traffic. This function * does the transition. */ int connection_ext_or_finished_flushing(or_connection_t *conn) { if (conn->base_.state == EXT_OR_CONN_STATE_FLUSHING) { connection_start_reading(TO_CONN(conn)); connection_ext_or_transition(conn); } return 0; } /** Initiate Extended ORPort authentication, by sending the list of * supported authentication types to the client. */ int connection_ext_or_start_auth(or_connection_t *or_conn) { connection_t *conn = TO_CONN(or_conn); char authtypes[2] = "\x01\x00"; /* We only support authtype '1' for now. */ log_debug(LD_GENERAL, "ExtORPort authentication: Sending supported authentication types"); connection_write_to_buf(authtypes, sizeof(authtypes), conn); conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; return 0; }