diff --git a/changes/ticket23420 b/changes/ticket23420 new file mode 100644 index 0000000000..6516466c19 --- /dev/null +++ b/changes/ticket23420 @@ -0,0 +1,4 @@ + o Minor bugfixes (hidden service v3): + - Pad RENDEZVOUS cell up to the size of the legacy cell which is much + bigger so the rendezvous point can't distinguish which hidden service + protocol is being used. Fixes ticket 23420.; bugfix on 0.3.2.1-alpha. diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c index 2bfeac747c..e1e513c5f4 100644 --- a/src/or/hs_circuit.c +++ b/src/or/hs_circuit.c @@ -5,6 +5,8 @@ * \file hs_circuit.c **/ +#define HS_CIRCUIT_PRIVATE + #include "or.h" #include "circpathbias.h" #include "circuitbuild.h" @@ -18,10 +20,10 @@ #include "router.h" #include "hs_cell.h" -#include "hs_circuit.h" #include "hs_ident.h" #include "hs_ntor.h" #include "hs_service.h" +#include "hs_circuit.h" /* Trunnel. */ #include "ed25519_cert.h" @@ -235,7 +237,7 @@ count_opened_desc_intro_point_circuits(const hs_service_t *service, /* From a given service, rendezvous cookie and handshake info, create a * rendezvous point circuit identifier. This can't fail. */ -static hs_ident_circuit_t * +STATIC hs_ident_circuit_t * create_rp_circuit_identifier(const hs_service_t *service, const uint8_t *rendezvous_cookie, const curve25519_public_key_t *server_pk, @@ -820,6 +822,15 @@ hs_circ_service_rp_has_opened(const hs_service_t *service, sizeof(circ->hs_ident->rendezvous_handshake_info), payload); + /* Pad the payload with random bytes so it matches the size of a legacy cell + * which is normally always bigger. Also, the size of a legacy cell is + * always smaller than the RELAY_PAYLOAD_SIZE so this is safe. */ + if (payload_len < HS_LEGACY_RENDEZVOUS_CELL_SIZE) { + crypto_rand((char *) payload + payload_len, + HS_LEGACY_RENDEZVOUS_CELL_SIZE - payload_len); + payload_len = HS_LEGACY_RENDEZVOUS_CELL_SIZE; + } + if (relay_send_command_from_edge(CONTROL_CELL_ID, TO_CIRCUIT(circ), RELAY_COMMAND_RENDEZVOUS1, (const char *) payload, payload_len, diff --git a/src/or/hs_circuit.h b/src/or/hs_circuit.h index 9085e3fb0b..0a1186dbaa 100644 --- a/src/or/hs_circuit.h +++ b/src/or/hs_circuit.h @@ -59,5 +59,15 @@ int hs_circuit_setup_e2e_rend_circ(origin_circuit_t *circ, int hs_circuit_setup_e2e_rend_circ_legacy_client(origin_circuit_t *circ, const uint8_t *rend_cell_body); +#ifdef HS_CIRCUIT_PRIVATE + +STATIC hs_ident_circuit_t * +create_rp_circuit_identifier(const hs_service_t *service, + const uint8_t *rendezvous_cookie, + const curve25519_public_key_t *server_pk, + const hs_ntor_rend_cell_keys_t *keys); + +#endif + #endif /* !defined(TOR_HS_CIRCUIT_H) */ diff --git a/src/or/hs_client.c b/src/or/hs_client.c index a02175929d..942e332f0c 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -845,7 +845,7 @@ handle_introduce_ack(origin_circuit_t *circ, const uint8_t *payload, /* Called when we get a RENDEZVOUS2 cell on the rendezvous circuit circ. The * encoded cell is in payload of length payload_len. Return 0 on success or a * negative value on error. On error, the circuit is marked for close. */ -static int +STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, size_t payload_len) { diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 4eee1174bd..08ab7736b6 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -77,6 +77,9 @@ client_get_random_intro(const ed25519_public_key_t *service_pk); STATIC extend_info_t * desc_intro_point_to_extend_info(const hs_desc_intro_point_t *ip); +STATIC int handle_rendezvous2(origin_circuit_t *circ, const uint8_t *payload, + size_t payload_len); + #endif /* defined(HS_CLIENT_PRIVATE) */ #endif /* !defined(TOR_HS_CLIENT_H) */ diff --git a/src/or/hs_common.h b/src/or/hs_common.h index e28ffe1adb..c95e59a6f8 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -118,6 +118,12 @@ /* Default value of hsdir spread fetch (hsdir_spread_fetch). */ #define HS_DEFAULT_HSDIR_SPREAD_FETCH 3 +/* The size of a legacy RENDEZVOUS1 cell which adds up to 168 bytes. It is + * bigger than the 84 bytes needed for version 3 so we need to pad up to that + * length so it is indistinguishable between versions. */ +#define HS_LEGACY_RENDEZVOUS_CELL_SIZE \ + (REND_COOKIE_LEN + DH_KEY_LEN + DIGEST_LEN) + /* Type of authentication key used by an introduction point. */ typedef enum { HS_AUTH_KEY_TYPE_LEGACY = 1, diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 1e89ab6a7f..74646c78d1 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -3398,7 +3398,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) /* Send the cell */ if (relay_send_command_from_edge(0, TO_CIRCUIT(circuit), RELAY_COMMAND_RENDEZVOUS1, - buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN, + buf, HS_LEGACY_RENDEZVOUS_CELL_SIZE, circuit->cpath->prev)<0) { log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell."); goto done; diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index ca6af43ba7..859476b703 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -14,10 +14,12 @@ #define HS_COMMON_PRIVATE #define HS_SERVICE_PRIVATE #define HS_INTROPOINT_PRIVATE +#define HS_CIRCUIT_PRIVATE #define MAIN_PRIVATE #define NETWORKSTATUS_PRIVATE #define STATEFILE_PRIVATE #define TOR_CHANNEL_INTERNAL_ +#define HS_CLIENT_PRIVATE #include "test.h" #include "test_helpers.h" @@ -37,11 +39,12 @@ #include "hs_common.h" #include "hs_config.h" -#include "hs_circuit.h" #include "hs_ident.h" #include "hs_intropoint.h" #include "hs_ntor.h" +#include "hs_circuit.h" #include "hs_service.h" +#include "hs_client.h" #include "main.h" #include "rendservice.h" #include "statefile.h" @@ -1366,6 +1369,130 @@ test_revision_counter_state(void *arg) service_descriptor_free(desc_two); } +/** Global vars used by test_rendezvous1_parsing() */ +char rend1_payload[RELAY_PAYLOAD_SIZE]; +size_t rend1_payload_len = 0; + +/** Mock for relay_send_command_from_edge() to send a RENDEZVOUS1 cell. Instead + * of sending it to the network, instead save it to the global `rend1_payload` + * variable so that we can inspect it in the test_rendezvous1_parsing() + * test. */ +static int +mock_relay_send_rendezvous1(streamid_t stream_id, circuit_t *circ, + uint8_t relay_command, const char *payload, + size_t payload_len, + crypt_path_t *cpath_layer, + const char *filename, int lineno) +{ + (void) stream_id; + (void) circ; + (void) relay_command; + (void) cpath_layer; + (void) filename; + (void) lineno; + + memcpy(rend1_payload, payload, payload_len); + rend1_payload_len = payload_len; + + return 0; +} + +/** Send a RENDEZVOUS1 as a service, and parse it as a client. */ +static void +test_rendezvous1_parsing(void *arg) +{ + int retval; + static const char *test_addr = + "4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion"; + hs_service_t *service = NULL; + origin_circuit_t *service_circ = NULL; + origin_circuit_t *client_circ = NULL; + ed25519_keypair_t ip_auth_kp; + curve25519_keypair_t ephemeral_kp; + curve25519_keypair_t client_kp; + curve25519_keypair_t ip_enc_kp; + int flags = CIRCLAUNCH_NEED_UPTIME | CIRCLAUNCH_IS_INTERNAL; + + (void) arg; + + MOCK(relay_send_command_from_edge_, mock_relay_send_rendezvous1); + + { + /* Let's start by setting up the service that will start the rend */ + service = tor_malloc_zero(sizeof(hs_service_t)); + ed25519_secret_key_generate(&service->keys.identity_sk, 0); + ed25519_public_key_generate(&service->keys.identity_pk, + &service->keys.identity_sk); + memcpy(service->onion_address, test_addr, sizeof(service->onion_address)); + tt_assert(service); + } + + { + /* Now let's set up the service rendezvous circuit and its keys. */ + service_circ = helper_create_origin_circuit(CIRCUIT_PURPOSE_S_CONNECT_REND, + flags); + tor_free(service_circ->hs_ident); + hs_ntor_rend_cell_keys_t hs_ntor_rend_cell_keys; + uint8_t rendezvous_cookie[HS_REND_COOKIE_LEN]; + curve25519_keypair_generate(&ip_enc_kp, 0); + curve25519_keypair_generate(&ephemeral_kp, 0); + curve25519_keypair_generate(&client_kp, 0); + ed25519_keypair_generate(&ip_auth_kp, 0); + retval = hs_ntor_service_get_rendezvous1_keys(&ip_auth_kp.pubkey, + &ip_enc_kp, + &ephemeral_kp, + &client_kp.pubkey, + &hs_ntor_rend_cell_keys); + tt_int_op(retval, OP_EQ, 0); + + memset(rendezvous_cookie, 2, sizeof(rendezvous_cookie)); + service_circ->hs_ident = + create_rp_circuit_identifier(service, rendezvous_cookie, + &ephemeral_kp.pubkey, + &hs_ntor_rend_cell_keys); + } + + /* Send out the RENDEZVOUS1 and make sure that our mock func worked */ + tt_assert(tor_mem_is_zero(rend1_payload, 32)); + hs_circ_service_rp_has_opened(service, service_circ); + tt_assert(!tor_mem_is_zero(rend1_payload, 32)); + tt_int_op(rend1_payload_len, OP_EQ, HS_LEGACY_RENDEZVOUS_CELL_SIZE); + + /******************************/ + + /** Now let's create the client rendezvous circuit */ + client_circ = + helper_create_origin_circuit(CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED, + flags); + /* fix up its circ ident */ + ed25519_pubkey_copy(&client_circ->hs_ident->intro_auth_pk, + &ip_auth_kp.pubkey); + memcpy(&client_circ->hs_ident->rendezvous_client_kp, + &client_kp, sizeof(client_circ->hs_ident->rendezvous_client_kp)); + memcpy(&client_circ->hs_ident->intro_enc_pk.public_key, + &ip_enc_kp.pubkey.public_key, + sizeof(client_circ->hs_ident->intro_enc_pk.public_key)); + + /* Now parse the rendezvous2 circuit and make sure it was fine. We are + * skipping 20 bytes off its payload, since that's the rendezvous cookie + * which is only present in REND1. */ + retval = handle_rendezvous2(client_circ, + (uint8_t*)rend1_payload+20, + rend1_payload_len-20); + tt_int_op(retval, OP_EQ, 0); + + /* TODO: We are only simulating client/service here. We could also simulate + * the rendezvous point by plugging in rend_mid_establish_rendezvous(). We + * would need an extra circuit and some more stuff but it's doable. */ + + done: + circuit_free(TO_CIRCUIT(service_circ)); + circuit_free(TO_CIRCUIT(client_circ)); + hs_service_free(service); + hs_free_all(); + UNMOCK(relay_send_command_from_edge_); +} + struct testcase_t hs_service_tests[] = { { "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK, NULL, NULL }, @@ -1395,6 +1522,8 @@ struct testcase_t hs_service_tests[] = { NULL, NULL }, { "revision_counter_state", test_revision_counter_state, TT_FORK, NULL, NULL }, + { "rendezvous1_parsing", test_rendezvous1_parsing, TT_FORK, + NULL, NULL }, END_OF_TESTCASES };