|
@@ -7,6 +7,8 @@
|
|
|
* \brief The hidden-service side of rendezvous functionality.
|
|
|
**/
|
|
|
|
|
|
+#define RENDSERVICE_PRIVATE
|
|
|
+
|
|
|
#include "or.h"
|
|
|
#include "circuitbuild.h"
|
|
|
#include "circuitlist.h"
|
|
@@ -29,6 +31,10 @@ static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro,
|
|
|
const char *pk_digest);
|
|
|
static rend_intro_point_t *find_intro_point(origin_circuit_t *circ);
|
|
|
|
|
|
+static extend_info_t *find_rp_for_intro(
|
|
|
+ const rend_intro_cell_t *intro,
|
|
|
+ uint8_t *need_free_out, char **err_msg_out);
|
|
|
+
|
|
|
static int intro_point_accepted_intro_count(rend_intro_point_t *intro);
|
|
|
static int intro_point_should_expire_now(rend_intro_point_t *intro,
|
|
|
time_t now);
|
|
@@ -37,6 +43,22 @@ static int rend_service_load_keys(struct rend_service_t *s);
|
|
|
static int rend_service_load_auth_keys(struct rend_service_t *s,
|
|
|
const char *hfname);
|
|
|
|
|
|
+static ssize_t rend_service_parse_intro_for_v0_or_v1(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ const uint8_t *buf,
|
|
|
+ size_t plaintext_len,
|
|
|
+ char **err_msg_out);
|
|
|
+static ssize_t rend_service_parse_intro_for_v2(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ const uint8_t *buf,
|
|
|
+ size_t plaintext_len,
|
|
|
+ char **err_msg_out);
|
|
|
+static ssize_t rend_service_parse_intro_for_v3(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ const uint8_t *buf,
|
|
|
+ size_t plaintext_len,
|
|
|
+ char **err_msg_out);
|
|
|
+
|
|
|
/** Represents the mapping from a virtual port of a rendezvous service to
|
|
|
* a real port on some IP.
|
|
|
*/
|
|
@@ -1061,38 +1083,50 @@ rend_service_note_removing_intro_point(rend_service_t *service,
|
|
|
/** Respond to an INTRODUCE2 cell by launching a circuit to the chosen
|
|
|
* rendezvous point.
|
|
|
*/
|
|
|
- /* XXXX024 this function sure could use some organizing. -RD */
|
|
|
int
|
|
|
rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
size_t request_len)
|
|
|
{
|
|
|
- int status = 0;
|
|
|
- char *ptr, *r_cookie;
|
|
|
- extend_info_t *extend_info = NULL;
|
|
|
+ /* Global status stuff */
|
|
|
+ int status = 0, result;
|
|
|
+ const or_options_t *options = get_options();
|
|
|
+ char *err_msg = NULL;
|
|
|
+ const char *stage_descr = NULL;
|
|
|
+ int reason = END_CIRC_REASON_TORPROTOCOL;
|
|
|
+ /* Service/circuit/key stuff we can learn before parsing */
|
|
|
+ char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
|
|
|
+ rend_service_t *service = NULL;
|
|
|
+ rend_intro_point_t *intro_point = NULL;
|
|
|
+ crypto_pk_t *intro_key = NULL;
|
|
|
+ /* Parsed cell */
|
|
|
+ rend_intro_cell_t *parsed_req = NULL;
|
|
|
+ /* Rendezvous point */
|
|
|
+ extend_info_t *rp = NULL;
|
|
|
+ /*
|
|
|
+ * We need to look up and construct the extend_info_t for v0 and v1,
|
|
|
+ * but all the info is in the cell and it's constructed by the parser
|
|
|
+ * for v2 and v3, so freeing it would be a double-free. Use this to
|
|
|
+ * keep track of whether we should free it.
|
|
|
+ */
|
|
|
+ uint8_t need_rp_free = 0;
|
|
|
+ /* XXX not handled yet */
|
|
|
char buf[RELAY_PAYLOAD_SIZE];
|
|
|
char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
|
|
|
- rend_service_t *service;
|
|
|
- rend_intro_point_t *intro_point;
|
|
|
- int r, i, v3_shift = 0;
|
|
|
- size_t len, keylen;
|
|
|
+ int i;
|
|
|
crypto_dh_t *dh = NULL;
|
|
|
origin_circuit_t *launched = NULL;
|
|
|
crypt_path_t *cpath = NULL;
|
|
|
- char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
|
|
|
char hexcookie[9];
|
|
|
int circ_needs_uptime;
|
|
|
- int reason = END_CIRC_REASON_TORPROTOCOL;
|
|
|
- crypto_pk_t *intro_key;
|
|
|
char intro_key_digest[DIGEST_LEN];
|
|
|
- int auth_type;
|
|
|
size_t auth_len = 0;
|
|
|
char auth_data[REND_DESC_COOKIE_LEN];
|
|
|
time_t now = time(NULL);
|
|
|
char diffie_hellman_hash[DIGEST_LEN];
|
|
|
time_t elapsed;
|
|
|
int replay;
|
|
|
- const or_options_t *options = get_options();
|
|
|
|
|
|
+ /* Do some initial validation and logging before we parse the cell */
|
|
|
if (circuit->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) {
|
|
|
log_warn(LD_PROTOCOL,
|
|
|
"Got an INTRODUCE2 over a non-introduction circuit %d.",
|
|
@@ -1105,57 +1139,60 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
#endif
|
|
|
tor_assert(circuit->rend_data);
|
|
|
|
|
|
+ /* We'll use this in a bazillion log messages */
|
|
|
base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
|
|
|
circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
|
|
|
- log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
|
|
|
- escaped(serviceid), circuit->_base.n_circ_id);
|
|
|
-
|
|
|
- /* min key length plus digest length plus nickname length */
|
|
|
- if (request_len < DIGEST_LEN+REND_COOKIE_LEN+(MAX_NICKNAME_LEN+1)+
|
|
|
- DH_KEY_LEN+42) {
|
|
|
- log_warn(LD_PROTOCOL, "Got a truncated INTRODUCE2 cell on circ %d.",
|
|
|
- circuit->_base.n_circ_id);
|
|
|
- goto err;
|
|
|
- }
|
|
|
|
|
|
/* look up service depending on circuit. */
|
|
|
- service = rend_service_get_by_pk_digest(
|
|
|
- circuit->rend_data->rend_pk_digest);
|
|
|
+ service =
|
|
|
+ rend_service_get_by_pk_digest(circuit->rend_data->rend_pk_digest);
|
|
|
if (!service) {
|
|
|
- log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro "
|
|
|
+ log_warn(LD_BUG,
|
|
|
+ "Internal error: Got an INTRODUCE2 cell on an intro "
|
|
|
"circ for an unrecognized service %s.",
|
|
|
escaped(serviceid));
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- /* use intro key instead of service key. */
|
|
|
- intro_key = circuit->intro_key;
|
|
|
-
|
|
|
- /* first DIGEST_LEN bytes of request is intro or service pk digest */
|
|
|
- crypto_pk_get_digest(intro_key, intro_key_digest);
|
|
|
- if (tor_memneq(intro_key_digest, request, DIGEST_LEN)) {
|
|
|
- base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
|
|
|
- (char*)request, REND_SERVICE_ID_LEN);
|
|
|
- log_warn(LD_REND, "Got an INTRODUCE2 cell for the wrong service (%s).",
|
|
|
- escaped(serviceid));
|
|
|
- goto err;
|
|
|
- }
|
|
|
-
|
|
|
- keylen = crypto_pk_keysize(intro_key);
|
|
|
- if (request_len < keylen+DIGEST_LEN) {
|
|
|
- log_warn(LD_PROTOCOL,
|
|
|
- "PK-encrypted portion of INTRODUCE2 cell was truncated.");
|
|
|
- goto err;
|
|
|
- }
|
|
|
-
|
|
|
intro_point = find_intro_point(circuit);
|
|
|
if (intro_point == NULL) {
|
|
|
- log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro circ "
|
|
|
- "(for service %s) with no corresponding rend_intro_point_t.",
|
|
|
+ log_warn(LD_BUG,
|
|
|
+ "Internal error: Got an INTRODUCE2 cell on an "
|
|
|
+ "intro circ (for service %s) with no corresponding "
|
|
|
+ "rend_intro_point_t.",
|
|
|
escaped(serviceid));
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
+ log_info(LD_REND, "Received INTRODUCE2 cell for service %s on circ %d.",
|
|
|
+ escaped(serviceid), circuit->_base.n_circ_id);
|
|
|
+
|
|
|
+ /* use intro key instead of service key. */
|
|
|
+ intro_key = circuit->intro_key;
|
|
|
+
|
|
|
+ tor_free(err_msg);
|
|
|
+ stage_descr = NULL;
|
|
|
+
|
|
|
+ stage_descr = "early parsing";
|
|
|
+ /* Early parsing pass (get pk, ciphertext); type 2 is INTRODUCE2 */
|
|
|
+ parsed_req =
|
|
|
+ rend_service_begin_parse_intro(request, request_len, 2, &err_msg);
|
|
|
+ if (!parsed_req) goto err;
|
|
|
+ else if (err_msg) {
|
|
|
+ log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
|
|
|
+ tor_free(err_msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ stage_descr = "early validation";
|
|
|
+ /* Early validation of pk/ciphertext part */
|
|
|
+ result = rend_service_validate_intro_early(parsed_req, &err_msg);
|
|
|
+ if (result < 0) goto err;
|
|
|
+ else if (err_msg) {
|
|
|
+ log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
|
|
|
+ tor_free(err_msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* make sure service replay caches are present */
|
|
|
if (!service->accepted_intro_dh_parts) {
|
|
|
service->accepted_intro_dh_parts =
|
|
|
replaycache_new(REND_REPLAY_TIME_INTERVAL,
|
|
@@ -1166,153 +1203,72 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
intro_point->accepted_intro_rsa_parts = replaycache_new(0, 0);
|
|
|
}
|
|
|
|
|
|
- /* Check for replay of PK-encrypted portion. */
|
|
|
+ /* check for replay of PK-encrypted portion. */
|
|
|
replay = replaycache_add_test_and_elapsed(
|
|
|
- intro_point->accepted_intro_rsa_parts,
|
|
|
- ((char*)request)+DIGEST_LEN, keylen,
|
|
|
- &elapsed);
|
|
|
+ intro_point->accepted_intro_rsa_parts,
|
|
|
+ parsed_req->ciphertext, parsed_req->ciphertext_len,
|
|
|
+ &elapsed);
|
|
|
|
|
|
if (replay) {
|
|
|
- log_warn(LD_REND, "Possible replay detected! We received an "
|
|
|
- "INTRODUCE2 cell with same PK-encrypted part %d seconds ago. "
|
|
|
- "Dropping cell.", (int)elapsed);
|
|
|
- goto err;
|
|
|
- }
|
|
|
+ log_warn(LD_REND,
|
|
|
+ "Possible replay detected! We received an "
|
|
|
+ "INTRODUCE2 cell with same PK-encrypted part %d "
|
|
|
+ "seconds ago. Dropping cell.",
|
|
|
+ (int)elapsed);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ stage_descr = "decryption";
|
|
|
+ /* Now try to decrypt it */
|
|
|
+ result = rend_service_decrypt_intro(parsed_req, intro_key, &err_msg);
|
|
|
+ if (result < 0) goto err;
|
|
|
+ else if (err_msg) {
|
|
|
+ log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
|
|
|
+ tor_free(err_msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ stage_descr = "late parsing";
|
|
|
+ /* Parse the plaintext */
|
|
|
+ result = rend_service_parse_intro_plaintext(parsed_req, &err_msg);
|
|
|
+ if (result < 0) goto err;
|
|
|
+ else if (err_msg) {
|
|
|
+ log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
|
|
|
+ tor_free(err_msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ stage_descr = "late validation";
|
|
|
+ /* Validate the parsed plaintext parts */
|
|
|
+ result = rend_service_validate_intro_late(parsed_req, &err_msg);
|
|
|
+ if (result < 0) goto err;
|
|
|
+ else if (err_msg) {
|
|
|
+ log_info(LD_REND, "%s on circ %d.", err_msg, circuit->_base.n_circ_id);
|
|
|
+ tor_free(err_msg);
|
|
|
+ }
|
|
|
+ stage_descr = NULL;
|
|
|
|
|
|
/* Increment INTRODUCE2 counter */
|
|
|
++(intro_point->accepted_introduce2_count);
|
|
|
|
|
|
- /* Next N bytes is encrypted with service key */
|
|
|
- note_crypto_pk_op(REND_SERVER);
|
|
|
- r = crypto_pk_private_hybrid_decrypt(
|
|
|
- intro_key,buf,sizeof(buf),
|
|
|
- (char*)(request+DIGEST_LEN),request_len-DIGEST_LEN,
|
|
|
- PK_PKCS1_OAEP_PADDING,1);
|
|
|
- if (r<0) {
|
|
|
- log_warn(LD_PROTOCOL, "Couldn't decrypt INTRODUCE2 cell.");
|
|
|
- goto err;
|
|
|
- }
|
|
|
- len = r;
|
|
|
- if (*buf == 3) {
|
|
|
- /* Version 3 INTRODUCE2 cell. */
|
|
|
- v3_shift = 1;
|
|
|
- auth_type = buf[1];
|
|
|
- switch (auth_type) {
|
|
|
- case REND_BASIC_AUTH:
|
|
|
- /* fall through */
|
|
|
- case REND_STEALTH_AUTH:
|
|
|
- auth_len = ntohs(get_uint16(buf+2));
|
|
|
- if (auth_len != REND_DESC_COOKIE_LEN) {
|
|
|
- log_info(LD_REND, "Wrong auth data size %d, should be %d.",
|
|
|
- (int)auth_len, REND_DESC_COOKIE_LEN);
|
|
|
- goto err;
|
|
|
- }
|
|
|
- memcpy(auth_data, buf+4, sizeof(auth_data));
|
|
|
- v3_shift += 2+REND_DESC_COOKIE_LEN;
|
|
|
- break;
|
|
|
- case REND_NO_AUTH:
|
|
|
- break;
|
|
|
- default:
|
|
|
- log_info(LD_REND, "Unknown authorization type '%d'", auth_type);
|
|
|
- }
|
|
|
-
|
|
|
- /* Skip the timestamp field. We no longer use it. */
|
|
|
- v3_shift += 4;
|
|
|
- }
|
|
|
- if (*buf == 2 || *buf == 3) {
|
|
|
- /* Version 2 INTRODUCE2 cell. */
|
|
|
- int klen;
|
|
|
- extend_info = tor_malloc_zero(sizeof(extend_info_t));
|
|
|
- tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf+v3_shift+1));
|
|
|
- extend_info->port = ntohs(get_uint16(buf+v3_shift+5));
|
|
|
- memcpy(extend_info->identity_digest, buf+v3_shift+7,
|
|
|
- DIGEST_LEN);
|
|
|
- extend_info->nickname[0] = '$';
|
|
|
- base16_encode(extend_info->nickname+1, sizeof(extend_info->nickname)-1,
|
|
|
- extend_info->identity_digest, DIGEST_LEN);
|
|
|
-
|
|
|
- klen = ntohs(get_uint16(buf+v3_shift+7+DIGEST_LEN));
|
|
|
- if ((int)len != v3_shift+7+DIGEST_LEN+2+klen+20+128) {
|
|
|
- log_warn(LD_PROTOCOL, "Bad length %u for version %d INTRODUCE2 cell.",
|
|
|
- (int)len, *buf);
|
|
|
- reason = END_CIRC_REASON_TORPROTOCOL;
|
|
|
- goto err;
|
|
|
- }
|
|
|
- extend_info->onion_key =
|
|
|
- crypto_pk_asn1_decode(buf+v3_shift+7+DIGEST_LEN+2, klen);
|
|
|
- if (!extend_info->onion_key) {
|
|
|
- log_warn(LD_PROTOCOL, "Error decoding onion key in version %d "
|
|
|
- "INTRODUCE2 cell.", *buf);
|
|
|
- reason = END_CIRC_REASON_TORPROTOCOL;
|
|
|
- goto err;
|
|
|
- }
|
|
|
- ptr = buf+v3_shift+7+DIGEST_LEN+2+klen;
|
|
|
- len -= v3_shift+7+DIGEST_LEN+2+klen;
|
|
|
- } else {
|
|
|
- char *rp_nickname;
|
|
|
- size_t nickname_field_len;
|
|
|
- const node_t *node;
|
|
|
- int version;
|
|
|
- if (*buf == 1) {
|
|
|
- rp_nickname = buf+1;
|
|
|
- nickname_field_len = MAX_HEX_NICKNAME_LEN+1;
|
|
|
- version = 1;
|
|
|
- } else {
|
|
|
- nickname_field_len = MAX_NICKNAME_LEN+1;
|
|
|
- rp_nickname = buf;
|
|
|
- version = 0;
|
|
|
- }
|
|
|
- ptr=memchr(rp_nickname,0,nickname_field_len);
|
|
|
- if (!ptr || ptr == rp_nickname) {
|
|
|
- log_warn(LD_PROTOCOL,
|
|
|
- "Couldn't find a nul-padded nickname in INTRODUCE2 cell.");
|
|
|
- goto err;
|
|
|
- }
|
|
|
- if ((version == 0 && !is_legal_nickname(rp_nickname)) ||
|
|
|
- (version == 1 && !is_legal_nickname_or_hexdigest(rp_nickname))) {
|
|
|
- log_warn(LD_PROTOCOL, "Bad nickname in INTRODUCE2 cell.");
|
|
|
- goto err;
|
|
|
- }
|
|
|
- /* Okay, now we know that a nickname is at the start of the buffer. */
|
|
|
- ptr = rp_nickname+nickname_field_len;
|
|
|
- len -= nickname_field_len;
|
|
|
- len -= rp_nickname - buf; /* also remove header space used by version, if
|
|
|
- * any */
|
|
|
- node = node_get_by_nickname(rp_nickname, 0);
|
|
|
- if (!node) {
|
|
|
- log_info(LD_REND, "Couldn't find router %s named in introduce2 cell.",
|
|
|
- escaped_safe_str_client(rp_nickname));
|
|
|
- /* XXXX Add a no-such-router reason? */
|
|
|
- reason = END_CIRC_REASON_TORPROTOCOL;
|
|
|
- goto err;
|
|
|
- }
|
|
|
-
|
|
|
- extend_info = extend_info_from_node(node, 0);
|
|
|
- }
|
|
|
-
|
|
|
- if (len != REND_COOKIE_LEN+DH_KEY_LEN) {
|
|
|
- log_warn(LD_PROTOCOL, "Bad length %u for INTRODUCE2 cell.", (int)len);
|
|
|
- reason = END_CIRC_REASON_TORPROTOCOL;
|
|
|
- goto err;
|
|
|
- }
|
|
|
+ /* Find the rendezvous point */
|
|
|
+ rp = find_rp_for_intro(parsed_req, &need_rp_free, &err_msg);
|
|
|
+ if (!rp) goto err;
|
|
|
|
|
|
/* Check if we'd refuse to talk to this router */
|
|
|
if (options->StrictNodes &&
|
|
|
- routerset_contains_extendinfo(options->ExcludeNodes, extend_info)) {
|
|
|
+ routerset_contains_extendinfo(options->ExcludeNodes, rp)) {
|
|
|
log_warn(LD_REND, "Client asked to rendezvous at a relay that we "
|
|
|
"exclude, and StrictNodes is set. Refusing service.");
|
|
|
reason = END_CIRC_REASON_INTERNAL; /* XXX might leak why we refused */
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- r_cookie = ptr;
|
|
|
- base16_encode(hexcookie,9,r_cookie,4);
|
|
|
+ base16_encode(hexcookie, 9, (const char *)(parsed_req->rc), 4);
|
|
|
|
|
|
/* Check whether there is a past request with the same Diffie-Hellman,
|
|
|
* part 1. */
|
|
|
replay = replaycache_add_test_and_elapsed(
|
|
|
service->accepted_intro_dh_parts,
|
|
|
- ptr+REND_COOKIE_LEN, DH_KEY_LEN,
|
|
|
+ parsed_req->dh, DH_KEY_LEN,
|
|
|
&elapsed);
|
|
|
|
|
|
if (replay) {
|
|
@@ -1358,7 +1314,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
reason = END_CIRC_REASON_INTERNAL;
|
|
|
goto err;
|
|
|
}
|
|
|
- if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh, ptr+REND_COOKIE_LEN,
|
|
|
+ if (crypto_dh_compute_secret(LOG_PROTOCOL_WARN, dh,
|
|
|
+ (char *)(parsed_req->dh),
|
|
|
DH_KEY_LEN, keys,
|
|
|
DIGEST_LEN+CPATH_KEY_MATERIAL_LEN)<0) {
|
|
|
log_warn(LD_BUG, "Internal error: couldn't complete DH handshake");
|
|
@@ -1377,7 +1334,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
int flags = CIRCLAUNCH_NEED_CAPACITY | CIRCLAUNCH_IS_INTERNAL;
|
|
|
if (circ_needs_uptime) flags |= CIRCLAUNCH_NEED_UPTIME;
|
|
|
launched = circuit_launch_by_extend_info(
|
|
|
- CIRCUIT_PURPOSE_S_CONNECT_REND, extend_info, flags);
|
|
|
+ CIRCUIT_PURPOSE_S_CONNECT_REND, rp, flags);
|
|
|
|
|
|
if (launched)
|
|
|
break;
|
|
@@ -1385,7 +1342,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
if (!launched) { /* give up */
|
|
|
log_warn(LD_REND, "Giving up launching first hop of circuit to rendezvous "
|
|
|
"point %s for service %s.",
|
|
|
- safe_str_client(extend_info_describe(extend_info)),
|
|
|
+ safe_str_client(extend_info_describe(rp)),
|
|
|
serviceid);
|
|
|
reason = END_CIRC_REASON_CONNECTFAILED;
|
|
|
goto err;
|
|
@@ -1393,7 +1350,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
log_info(LD_REND,
|
|
|
"Accepted intro; launching circuit to %s "
|
|
|
"(cookie %s) for service %s.",
|
|
|
- safe_str_client(extend_info_describe(extend_info)),
|
|
|
+ safe_str_client(extend_info_describe(rp)),
|
|
|
hexcookie, serviceid);
|
|
|
tor_assert(launched->build_state);
|
|
|
/* Fill in the circuit's state. */
|
|
@@ -1401,7 +1358,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
memcpy(launched->rend_data->rend_pk_digest,
|
|
|
circuit->rend_data->rend_pk_digest,
|
|
|
DIGEST_LEN);
|
|
|
- memcpy(launched->rend_data->rend_cookie, r_cookie, REND_COOKIE_LEN);
|
|
|
+ memcpy(launched->rend_data->rend_cookie, parsed_req->rc, REND_COOKIE_LEN);
|
|
|
strlcpy(launched->rend_data->onion_address, service->service_id,
|
|
|
sizeof(launched->rend_data->onion_address));
|
|
|
|
|
@@ -1419,16 +1376,26 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
if (circuit_init_cpath_crypto(cpath,keys+DIGEST_LEN,1)<0)
|
|
|
goto err;
|
|
|
memcpy(cpath->handshake_digest, keys, DIGEST_LEN);
|
|
|
- if (extend_info) extend_info_free(extend_info);
|
|
|
|
|
|
goto done;
|
|
|
|
|
|
err:
|
|
|
status = -1;
|
|
|
+ if (!err_msg) {
|
|
|
+ if (stage_descr) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "unknown %s error for INTRODUCE2", stage_descr);
|
|
|
+ } else {
|
|
|
+ err_msg = tor_strdup("unknown error for INTRODUCE2");
|
|
|
+ }
|
|
|
+ }
|
|
|
if (dh) crypto_dh_free(dh);
|
|
|
- if (launched)
|
|
|
+ if (launched) {
|
|
|
circuit_mark_for_close(TO_CIRCUIT(launched), reason);
|
|
|
- if (extend_info) extend_info_free(extend_info);
|
|
|
+ }
|
|
|
+ log_warn(LD_REND, "%s on circ %d", err_msg, circuit->_base.n_circ_id);
|
|
|
+ tor_free(err_msg);
|
|
|
+
|
|
|
done:
|
|
|
memset(keys, 0, sizeof(keys));
|
|
|
memset(buf, 0, sizeof(buf));
|
|
@@ -1438,6 +1405,842 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
|
|
|
memset(auth_data, 0, sizeof(auth_data));
|
|
|
memset(diffie_hellman_hash, 0, sizeof(diffie_hellman_hash));
|
|
|
|
|
|
+ /* Free the parsed cell */
|
|
|
+ if (parsed_req) {
|
|
|
+ rend_service_free_intro(parsed_req);
|
|
|
+ parsed_req = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Free rp if we must */
|
|
|
+ if (need_rp_free) extend_info_free(rp);
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/** Given a parsed and decrypted INTRODUCE2, find the rendezvous point or
|
|
|
+ * return NULL and an error string if we can't.
|
|
|
+ */
|
|
|
+
|
|
|
+static extend_info_t *
|
|
|
+find_rp_for_intro(const rend_intro_cell_t *intro,
|
|
|
+ uint8_t *need_free_out, char **err_msg_out)
|
|
|
+{
|
|
|
+ extend_info_t *rp = NULL;
|
|
|
+ char *err_msg = NULL;
|
|
|
+ const char *rp_nickname = NULL;
|
|
|
+ const node_t *node = NULL;
|
|
|
+ uint8_t need_free = 0;
|
|
|
+
|
|
|
+ if (!intro || !need_free_out) {
|
|
|
+ if (err_msg_out)
|
|
|
+ err_msg = tor_strdup("Bad parameters to find_rp_for_intro()");
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (intro->version == 0 || intro->version == 1) {
|
|
|
+ if (intro->version == 1) rp_nickname = (const char *)(intro->u.v1.rp);
|
|
|
+ else rp_nickname = (const char *)(intro->u.v0.rp);
|
|
|
+
|
|
|
+ node = node_get_by_nickname(rp_nickname, 0);
|
|
|
+ if (!node) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "Couldn't find router %s named in INTRODUCE2 cell",
|
|
|
+ escaped_safe_str_client(rp_nickname));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ rp = extend_info_from_node(node, 0);
|
|
|
+ if (!rp) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "Could build extend_info_t for router %s named "
|
|
|
+ "in INTRODUCE2 cell",
|
|
|
+ escaped_safe_str_client(rp_nickname));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ else need_free = 1;
|
|
|
+ } else if (intro->version == 2) rp = intro->u.v2.extend_info;
|
|
|
+ else if (intro->version == 3) rp = intro->u.v3.extend_info;
|
|
|
+ else {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "Unknown version %d in INTRODUCE2 cell",
|
|
|
+ (int)(intro->version));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ err:
|
|
|
+ if (err_msg_out) *err_msg_out = err_msg;
|
|
|
+ else tor_free(err_msg);
|
|
|
+
|
|
|
+ done:
|
|
|
+ if (rp && need_free_out) *need_free_out = need_free;
|
|
|
+
|
|
|
+ return rp;
|
|
|
+}
|
|
|
+
|
|
|
+/** Remove unnecessary parts from a rend_intro_cell_t - the ciphertext if
|
|
|
+ * already decrypted, the plaintext too if already parsed
|
|
|
+ */
|
|
|
+
|
|
|
+void
|
|
|
+rend_service_compact_intro(rend_intro_cell_t *request)
|
|
|
+{
|
|
|
+ if (!request) return;
|
|
|
+
|
|
|
+ if ((request->plaintext && request->plaintext_len > 0) ||
|
|
|
+ request->parsed) {
|
|
|
+ tor_free(request->ciphertext);
|
|
|
+ request->ciphertext_len = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (request->parsed) {
|
|
|
+ tor_free(request->plaintext);
|
|
|
+ request->plaintext_len = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** Free a parsed INTRODUCE1 or INTRODUCE2 cell that was allocated by
|
|
|
+ * rend_service_parse_intro().
|
|
|
+ */
|
|
|
+void
|
|
|
+rend_service_free_intro(rend_intro_cell_t *request)
|
|
|
+{
|
|
|
+ if (!request) {
|
|
|
+ log_info(LD_BUG, "rend_service_free_intro() called with NULL request!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Free ciphertext */
|
|
|
+ tor_free(request->ciphertext);
|
|
|
+ request->ciphertext_len = 0;
|
|
|
+
|
|
|
+ /* Have plaintext? */
|
|
|
+ if (request->plaintext) {
|
|
|
+ /* Zero it out just to be safe */
|
|
|
+ memset(request->plaintext, 0, request->plaintext_len);
|
|
|
+ tor_free(request->plaintext);
|
|
|
+ request->plaintext_len = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Have parsed plaintext? */
|
|
|
+ if (request->parsed) {
|
|
|
+ switch (request->version) {
|
|
|
+ case 0:
|
|
|
+ case 1:
|
|
|
+ /*
|
|
|
+ * Nothing more to do; these formats have no further pointers
|
|
|
+ * in them.
|
|
|
+ */
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ extend_info_free(request->u.v2.extend_info);
|
|
|
+ request->u.v2.extend_info = NULL;
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ if (request->u.v3.auth_data) {
|
|
|
+ memset(request->u.v3.auth_data, 0, request->u.v3.auth_len);
|
|
|
+ tor_free(request->u.v3.auth_data);
|
|
|
+ }
|
|
|
+
|
|
|
+ extend_info_free(request->u.v3.extend_info);
|
|
|
+ request->u.v3.extend_info = NULL;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ log_info(LD_BUG,
|
|
|
+ "rend_service_free_intro() saw unknown protocol "
|
|
|
+ "version %d.",
|
|
|
+ request->version);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Zero it out to make sure sensitive stuff doesn't hang around in memory */
|
|
|
+ memset(request, 0, sizeof(*request));
|
|
|
+
|
|
|
+ tor_free(request);
|
|
|
+}
|
|
|
+
|
|
|
+/** Parse an INTRODUCE1 or INTRODUCE2 cell into a newly allocated
|
|
|
+ * rend_intro_cell_t structure. Free it with rend_service_free_intro()
|
|
|
+ * when finished. The type parameter should be 1 or 2 to indicate whether
|
|
|
+ * this is INTRODUCE1 or INTRODUCE2. This parses only the non-encrypted
|
|
|
+ * parts; after this, call rend_service_decrypt_intro() with a key, then
|
|
|
+ * rend_service_parse_intro_plaintext() to finish parsing. The optional
|
|
|
+ * err_msg_out parameter is set to a string suitable for log output
|
|
|
+ * if parsing fails. This function does some validation, but only
|
|
|
+ * that which depends solely on the contents of the cell and the
|
|
|
+ * key; it can be unit-tested. Further validation is done in
|
|
|
+ * rend_service_validate_intro().
|
|
|
+ */
|
|
|
+
|
|
|
+rend_intro_cell_t *
|
|
|
+rend_service_begin_parse_intro(const uint8_t *request,
|
|
|
+ size_t request_len,
|
|
|
+ uint8_t type,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ rend_intro_cell_t *rv = NULL;
|
|
|
+ char *err_msg = NULL;
|
|
|
+
|
|
|
+ if (!request || request_len <= 0) goto err;
|
|
|
+ if (!(type == 1 || type == 2)) goto err;
|
|
|
+
|
|
|
+ /* First, check that the cell is long enough to be a sensible INTRODUCE */
|
|
|
+
|
|
|
+ /* min key length plus digest length plus nickname length */
|
|
|
+ if (request_len <
|
|
|
+ (DIGEST_LEN + REND_COOKIE_LEN + (MAX_NICKNAME_LEN + 1) +
|
|
|
+ DH_KEY_LEN + 42)) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "got a truncated INTRODUCE%d cell",
|
|
|
+ (int)type);
|
|
|
+ }
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Allocate a new parsed cell structure */
|
|
|
+ rv = tor_malloc_zero(sizeof(*rv));
|
|
|
+
|
|
|
+ /* Set the type */
|
|
|
+ rv->type = type;
|
|
|
+
|
|
|
+ /* Copy in the ID */
|
|
|
+ memcpy(rv->pk, request, DIGEST_LEN);
|
|
|
+
|
|
|
+ /* Copy in the ciphertext */
|
|
|
+ rv->ciphertext = tor_malloc(request_len - DIGEST_LEN);
|
|
|
+ memcpy(rv->ciphertext, request + DIGEST_LEN, request_len - DIGEST_LEN);
|
|
|
+ rv->ciphertext_len = request_len - DIGEST_LEN;
|
|
|
+
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ err:
|
|
|
+ if (rv) rend_service_free_intro(rv);
|
|
|
+ rv = NULL;
|
|
|
+ if (err_msg_out && !err_msg) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "unknown INTRODUCE%d error",
|
|
|
+ (int)type);
|
|
|
+ }
|
|
|
+
|
|
|
+ done:
|
|
|
+ if (err_msg_out) *err_msg_out = err_msg;
|
|
|
+ else tor_free(err_msg);
|
|
|
+
|
|
|
+ return rv;
|
|
|
+}
|
|
|
+
|
|
|
+/** Parse the version-specific parts of a v0 or v1 INTRODUCE1 or INTRODUCE2
|
|
|
+ * cell
|
|
|
+ */
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+rend_service_parse_intro_for_v0_or_v1(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ const uint8_t *buf,
|
|
|
+ size_t plaintext_len,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ const char *rp_nickname, *endptr;
|
|
|
+ size_t nickname_field_len, ver_specific_len;
|
|
|
+
|
|
|
+ if (intro->version == 1) {
|
|
|
+ ver_specific_len = MAX_HEX_NICKNAME_LEN + 2;
|
|
|
+ rp_nickname = ((const char *)buf) + 1;
|
|
|
+ nickname_field_len = MAX_HEX_NICKNAME_LEN + 1;
|
|
|
+ } else if (intro->version == 0) {
|
|
|
+ ver_specific_len = MAX_NICKNAME_LEN + 1;
|
|
|
+ rp_nickname = (const char *)buf;
|
|
|
+ nickname_field_len = MAX_NICKNAME_LEN + 1;
|
|
|
+ } else {
|
|
|
+ if (err_msg_out)
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "rend_service_parse_intro_for_v0_or_v1() called with "
|
|
|
+ "bad version %d on INTRODUCE%d cell (this is a bug)",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (plaintext_len < ver_specific_len) {
|
|
|
+ if (err_msg_out)
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "short plaintext of encrypted part in v1 INTRODUCE%d "
|
|
|
+ "cell (%lu bytes, needed %lu)",
|
|
|
+ (int)(intro->type),
|
|
|
+ (unsigned long)plaintext_len,
|
|
|
+ (unsigned long)ver_specific_len);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ endptr = memchr(rp_nickname, 0, nickname_field_len);
|
|
|
+ if (!endptr || endptr == rp_nickname) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "couldn't find a nul-padded nickname in "
|
|
|
+ "INTRODUCE%d cell",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((intro->version == 0 &&
|
|
|
+ !is_legal_nickname(rp_nickname)) ||
|
|
|
+ (intro->version == 1 &&
|
|
|
+ !is_legal_nickname_or_hexdigest(rp_nickname))) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "bad nickname in INTRODUCE%d cell",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (intro->version == 1) {
|
|
|
+ memcpy(intro->u.v1.rp, rp_nickname, endptr - rp_nickname + 1);
|
|
|
+ } else {
|
|
|
+ memcpy(intro->u.v0.rp, rp_nickname, endptr - rp_nickname + 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return ver_specific_len;
|
|
|
+
|
|
|
+ err:
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/** Parse the version-specific parts of a v2 INTRODUCE1 or INTRODUCE2 cell
|
|
|
+ */
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+rend_service_parse_intro_for_v2(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ const uint8_t *buf,
|
|
|
+ size_t plaintext_len,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ unsigned int klen;
|
|
|
+ extend_info_t *extend_info = NULL;
|
|
|
+ ssize_t ver_specific_len;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We accept version 3 too so that the v3 parser can call this with
|
|
|
+ * and adjusted buffer for the latter part of a v3 cell, which is
|
|
|
+ * identical to a v2 cell.
|
|
|
+ */
|
|
|
+ if (!(intro->version == 2 ||
|
|
|
+ intro->version == 3)) {
|
|
|
+ if (err_msg_out)
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "rend_service_parse_intro_for_v2() called with "
|
|
|
+ "bad version %d on INTRODUCE%d cell (this is a bug)",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */
|
|
|
+ if (plaintext_len < 7 + DIGEST_LEN + 2) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "truncated plaintext of encrypted parted of "
|
|
|
+ "version %d INTRODUCE%d cell",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ extend_info = tor_malloc_zero(sizeof(extend_info_t));
|
|
|
+ tor_addr_from_ipv4n(&extend_info->addr, get_uint32(buf + 1));
|
|
|
+ extend_info->port = ntohs(get_uint16(buf + 5));
|
|
|
+ memcpy(extend_info->identity_digest, buf + 7, DIGEST_LEN);
|
|
|
+ extend_info->nickname[0] = '$';
|
|
|
+ base16_encode(extend_info->nickname + 1, sizeof(extend_info->nickname) - 1,
|
|
|
+ extend_info->identity_digest, DIGEST_LEN);
|
|
|
+ klen = ntohs(get_uint16(buf + 7 + DIGEST_LEN));
|
|
|
+
|
|
|
+ /* 7 == version, IP and port, DIGEST_LEN == id, 2 == key length */
|
|
|
+ if (plaintext_len < 7 + DIGEST_LEN + 2 + klen) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "truncated plaintext of encrypted parted of "
|
|
|
+ "version %d INTRODUCE%d cell",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ extend_info->onion_key =
|
|
|
+ crypto_pk_asn1_decode((const char *)(buf + 7 + DIGEST_LEN + 2), klen);
|
|
|
+ if (!extend_info->onion_key) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "error decoding onion key in version %d "
|
|
|
+ "INTRODUCE%d cell",
|
|
|
+ intro->version,
|
|
|
+ (intro->type));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ ver_specific_len = 7+DIGEST_LEN+2+klen;
|
|
|
+
|
|
|
+ if (intro->version == 2) intro->u.v2.extend_info = extend_info;
|
|
|
+ else intro->u.v3.extend_info = extend_info;
|
|
|
+
|
|
|
+ return ver_specific_len;
|
|
|
+
|
|
|
+ err:
|
|
|
+ extend_info_free(extend_info);
|
|
|
+
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/** Parse the version-specific parts of a v3 INTRODUCE1 or INTRODUCE2 cell
|
|
|
+ */
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+rend_service_parse_intro_for_v3(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ const uint8_t *buf,
|
|
|
+ size_t plaintext_len,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ ssize_t adjust, v2_ver_specific_len, ts_offset;
|
|
|
+
|
|
|
+ /* This should only be called on v3 cells */
|
|
|
+ if (intro->version != 3) {
|
|
|
+ if (err_msg_out)
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "rend_service_parse_intro_for_v3() called with "
|
|
|
+ "bad version %d on INTRODUCE%d cell (this is a bug)",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Check that we have at least enough to get auth_len:
|
|
|
+ *
|
|
|
+ * 1 octet for version, 1 for auth_type, 2 for auth_len
|
|
|
+ */
|
|
|
+ if (plaintext_len < 4) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "truncated plaintext of encrypted parted of "
|
|
|
+ "version %d INTRODUCE%d cell",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The rend_client_send_introduction() function over in rendclient.c is
|
|
|
+ * broken (i.e., fails to match the spec) in such a way that we can't
|
|
|
+ * change it without breaking the protocol. Specifically, it doesn't
|
|
|
+ * emit auth_len when auth-type is REND_NO_AUTH, so everything is off
|
|
|
+ * by two bytes after that. Calculate ts_offset and do everything from
|
|
|
+ * the timestamp on relative to that to handle this dain bramage.
|
|
|
+ */
|
|
|
+
|
|
|
+ intro->u.v3.auth_type = buf[1];
|
|
|
+ if (intro->u.v3.auth_type != REND_NO_AUTH) {
|
|
|
+ intro->u.v3.auth_len = ntohs(get_uint16(buf + 2));
|
|
|
+ ts_offset = 4 + intro->u.v3.auth_len;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ intro->u.v3.auth_len = 0;
|
|
|
+ ts_offset = 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check that auth len makes sense for this auth type */
|
|
|
+ if (intro->u.v3.auth_type == REND_BASIC_AUTH ||
|
|
|
+ intro->u.v3.auth_type == REND_STEALTH_AUTH) {
|
|
|
+ if (intro->u.v3.auth_len != REND_DESC_COOKIE_LEN) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "wrong auth data size %d for INTRODUCE%d cell, "
|
|
|
+ "should be %d",
|
|
|
+ (int)(intro->u.v3.auth_len),
|
|
|
+ (int)(intro->type),
|
|
|
+ REND_DESC_COOKIE_LEN);
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check that we actually have everything up to the timestamp */
|
|
|
+ if (plaintext_len < (size_t)(ts_offset)) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "truncated plaintext of encrypted parted of "
|
|
|
+ "version %d INTRODUCE%d cell",
|
|
|
+ intro->version,
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (intro->u.v3.auth_type != REND_NO_AUTH &&
|
|
|
+ intro->u.v3.auth_len > 0) {
|
|
|
+ /* Okay, we can go ahead and copy auth_data */
|
|
|
+ intro->u.v3.auth_data = tor_malloc(intro->u.v3.auth_len);
|
|
|
+ /*
|
|
|
+ * We know we had an auth_len field in this case, so 4 is
|
|
|
+ * always right.
|
|
|
+ */
|
|
|
+ memcpy(intro->u.v3.auth_data, buf + 4, intro->u.v3.auth_len);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Apparently we don't use the timestamp any more, but might as well copy
|
|
|
+ * over just in case we ever care about it.
|
|
|
+ */
|
|
|
+ intro->u.v3.timestamp = ntohl(get_uint32(buf + ts_offset));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * From here on, the format is as in v2, so we call the v2 parser with
|
|
|
+ * adjusted buffer and length. We are 4 + ts_offset octets in, but the
|
|
|
+ * v2 parser expects to skip over a version byte at the start, so we
|
|
|
+ * adjust by 3 + ts_offset.
|
|
|
+ */
|
|
|
+ adjust = 3 + ts_offset;
|
|
|
+
|
|
|
+ v2_ver_specific_len =
|
|
|
+ rend_service_parse_intro_for_v2(intro,
|
|
|
+ buf + adjust, plaintext_len - adjust,
|
|
|
+ err_msg_out);
|
|
|
+
|
|
|
+ /* Success in v2 parser */
|
|
|
+ if (v2_ver_specific_len >= 0) return v2_ver_specific_len + adjust;
|
|
|
+ /* Failure in v2 parser; it will have provided an err_msg */
|
|
|
+ else return v2_ver_specific_len;
|
|
|
+
|
|
|
+ err:
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/** Table of parser functions for version-specific parts of an INTRODUCE2
|
|
|
+ * cell.
|
|
|
+ */
|
|
|
+
|
|
|
+static ssize_t
|
|
|
+ (*intro_version_handlers[])(
|
|
|
+ rend_intro_cell_t *,
|
|
|
+ const uint8_t *,
|
|
|
+ size_t,
|
|
|
+ char **) =
|
|
|
+{ rend_service_parse_intro_for_v0_or_v1,
|
|
|
+ rend_service_parse_intro_for_v0_or_v1,
|
|
|
+ rend_service_parse_intro_for_v2,
|
|
|
+ rend_service_parse_intro_for_v3 };
|
|
|
+
|
|
|
+/** Decrypt the encrypted part of an INTRODUCE1 or INTRODUCE2 cell,
|
|
|
+ * return 0 if successful, or < 0 and write an error message to
|
|
|
+ * *err_msg_out if provided.
|
|
|
+ */
|
|
|
+
|
|
|
+int
|
|
|
+rend_service_decrypt_intro(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ crypto_pk_t *key,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ char *err_msg = NULL;
|
|
|
+ uint8_t key_digest[DIGEST_LEN];
|
|
|
+ char service_id[REND_SERVICE_ID_LEN_BASE32+1];
|
|
|
+ ssize_t key_len;
|
|
|
+ uint8_t buf[RELAY_PAYLOAD_SIZE];
|
|
|
+ int result, status = 0;
|
|
|
+
|
|
|
+ if (!intro || !key) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ err_msg =
|
|
|
+ tor_strdup("rend_service_decrypt_intro() called with bad "
|
|
|
+ "parameters");
|
|
|
+ }
|
|
|
+
|
|
|
+ status = -2;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Make sure we have ciphertext */
|
|
|
+ if (!(intro->ciphertext) || intro->ciphertext_len <= 0) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "rend_intro_cell_t was missing ciphertext for "
|
|
|
+ "INTRODUCE%d cell",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+ status = -3;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check that this cell actually matches this service key */
|
|
|
+
|
|
|
+ /* first DIGEST_LEN bytes of request is intro or service pk digest */
|
|
|
+ crypto_pk_get_digest(key, (char *)key_digest);
|
|
|
+ if (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1,
|
|
|
+ (char*)(intro->pk), REND_SERVICE_ID_LEN);
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "got an INTRODUCE%d cell for the wrong service (%s)",
|
|
|
+ (int)(intro->type),
|
|
|
+ escaped(service_id));
|
|
|
+ }
|
|
|
+
|
|
|
+ status = -4;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Make sure the encrypted part is long enough to decrypt */
|
|
|
+
|
|
|
+ key_len = crypto_pk_keysize(key);
|
|
|
+ if (intro->ciphertext_len < key_len) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "got an INTRODUCE%d cell with a truncated PK-encrypted "
|
|
|
+ "part",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+
|
|
|
+ status = -5;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Decrypt the encrypted part */
|
|
|
+
|
|
|
+ note_crypto_pk_op(REND_SERVER);
|
|
|
+ result =
|
|
|
+ crypto_pk_private_hybrid_decrypt(
|
|
|
+ key, (char *)buf, sizeof(buf),
|
|
|
+ (const char *)(intro->ciphertext), intro->ciphertext_len,
|
|
|
+ PK_PKCS1_OAEP_PADDING, 1);
|
|
|
+ if (result < 0) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "couldn't decrypt INTRODUCE%d cell",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+ status = -6;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ intro->plaintext_len = result;
|
|
|
+ intro->plaintext = tor_malloc(intro->plaintext_len);
|
|
|
+ memcpy(intro->plaintext, buf, intro->plaintext_len);
|
|
|
+
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ err:
|
|
|
+ if (err_msg_out && !err_msg) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "unknown INTRODUCE%d error decrypting encrypted part",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+ if (status >= 0) status = -1;
|
|
|
+
|
|
|
+ done:
|
|
|
+ if (err_msg_out) *err_msg_out = err_msg;
|
|
|
+ else tor_free(err_msg);
|
|
|
+
|
|
|
+ /* clean up potentially sensitive material */
|
|
|
+ memset(buf, 0, sizeof(buf));
|
|
|
+ memset(key_digest, 0, sizeof(key_digest));
|
|
|
+ memset(service_id, 0, sizeof(service_id));
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/** Parse the plaintext of the encrypted part of an INTRODUCE1 or
|
|
|
+ * INTRODUCE2 cell, return 0 if successful, or < 0 and write an error
|
|
|
+ * message to *err_msg_out if provided.
|
|
|
+ */
|
|
|
+
|
|
|
+int
|
|
|
+rend_service_parse_intro_plaintext(
|
|
|
+ rend_intro_cell_t *intro,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ char *err_msg = NULL;
|
|
|
+ ssize_t ver_specific_len, ver_invariant_len;
|
|
|
+ uint8_t version;
|
|
|
+ int status = 0;
|
|
|
+
|
|
|
+ if (!intro) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ err_msg =
|
|
|
+ tor_strdup("rend_service_parse_intro_plaintext() called with NULL "
|
|
|
+ "rend_intro_cell_t");
|
|
|
+ }
|
|
|
+
|
|
|
+ status = -2;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check that we have plaintext */
|
|
|
+ if (!(intro->plaintext) || intro->plaintext_len <= 0) {
|
|
|
+ if (err_msg_out) {
|
|
|
+ err_msg = tor_strdup("rend_intro_cell_t was missing plaintext");
|
|
|
+ }
|
|
|
+ status = -3;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* In all formats except v0, the first byte is a version number */
|
|
|
+ version = intro->plaintext[0];
|
|
|
+
|
|
|
+ /* v0 has no version byte (stupid...), so handle it as a fallback */
|
|
|
+ if (version > 3) version = 0;
|
|
|
+
|
|
|
+ /* Copy the version into the parsed cell structure */
|
|
|
+ intro->version = version;
|
|
|
+
|
|
|
+ /* Call the version-specific parser from the table */
|
|
|
+ ver_specific_len =
|
|
|
+ intro_version_handlers[version](intro,
|
|
|
+ intro->plaintext, intro->plaintext_len,
|
|
|
+ &err_msg);
|
|
|
+ if (ver_specific_len < 0) {
|
|
|
+ status = -4;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** The rendezvous cookie and Diffie-Hellman stuff are version-invariant
|
|
|
+ * and at the end of the plaintext of the encrypted part of the cell.
|
|
|
+ */
|
|
|
+
|
|
|
+ ver_invariant_len = intro->plaintext_len - ver_specific_len;
|
|
|
+ if (ver_invariant_len < REND_COOKIE_LEN + DH_KEY_LEN) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "decrypted plaintext of INTRODUCE%d cell was truncated (%ld bytes)",
|
|
|
+ (int)(intro->type),
|
|
|
+ (long)(intro->plaintext_len));
|
|
|
+ status = -5;
|
|
|
+ goto err;
|
|
|
+ } else if (ver_invariant_len > REND_COOKIE_LEN + DH_KEY_LEN) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "decrypted plaintext of INTRODUCE%d cell was too long (%ld bytes)",
|
|
|
+ (int)(intro->type),
|
|
|
+ (long)(intro->plaintext_len));
|
|
|
+ status = -6;
|
|
|
+ } else {
|
|
|
+ memcpy(intro->rc,
|
|
|
+ intro->plaintext + ver_specific_len,
|
|
|
+ REND_COOKIE_LEN);
|
|
|
+ memcpy(intro->dh,
|
|
|
+ intro->plaintext + ver_specific_len + REND_COOKIE_LEN,
|
|
|
+ DH_KEY_LEN);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Flag it as being fully parsed */
|
|
|
+ intro->parsed = 1;
|
|
|
+
|
|
|
+ goto done;
|
|
|
+
|
|
|
+ err:
|
|
|
+ if (err_msg_out && !err_msg) {
|
|
|
+ tor_asprintf(&err_msg,
|
|
|
+ "unknown INTRODUCE%d error parsing encrypted part",
|
|
|
+ (int)(intro->type));
|
|
|
+ }
|
|
|
+ if (status >= 0) status = -1;
|
|
|
+
|
|
|
+ done:
|
|
|
+ if (err_msg_out) *err_msg_out = err_msg;
|
|
|
+ else tor_free(err_msg);
|
|
|
+
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/** Do validity checks on a parsed intro cell before decryption; some of
|
|
|
+ * these are not done in rend_service_begin_parse_intro() itself because
|
|
|
+ * they depend on a lot of other state and would make it hard to unit test.
|
|
|
+ * Returns >= 0 if successful or < 0 if the intro cell is invalid, and
|
|
|
+ * optionally writes out an error message for logging. If an err_msg
|
|
|
+ * pointer is provided, it is the caller's responsibility to free any
|
|
|
+ * provided message.
|
|
|
+ */
|
|
|
+
|
|
|
+int
|
|
|
+rend_service_validate_intro_early(const rend_intro_cell_t *intro,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ int status = 0;
|
|
|
+
|
|
|
+ if (!intro) {
|
|
|
+ if (err_msg_out)
|
|
|
+ *err_msg_out =
|
|
|
+ tor_strdup("NULL intro cell passed to "
|
|
|
+ "rend_service_validate_intro_early()");
|
|
|
+
|
|
|
+ status = -1;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* TODO */
|
|
|
+
|
|
|
+ err:
|
|
|
+ return status;
|
|
|
+}
|
|
|
+
|
|
|
+/** Do validity checks on a parsed intro cell after decryption; some of
|
|
|
+ * these are not done in rend_service_parse_intro_plaintext() itself because
|
|
|
+ * they depend on a lot of other state and would make it hard to unit test.
|
|
|
+ * Returns >= 0 if successful or < 0 if the intro cell is invalid, and
|
|
|
+ * optionally writes out an error message for logging. If an err_msg
|
|
|
+ * pointer is provided, it is the caller's responsibility to free any
|
|
|
+ * provided message.
|
|
|
+ */
|
|
|
+
|
|
|
+int
|
|
|
+rend_service_validate_intro_late(const rend_intro_cell_t *intro,
|
|
|
+ char **err_msg_out)
|
|
|
+{
|
|
|
+ int status = 0;
|
|
|
+
|
|
|
+ if (!intro) {
|
|
|
+ if (err_msg_out)
|
|
|
+ *err_msg_out =
|
|
|
+ tor_strdup("NULL intro cell passed to "
|
|
|
+ "rend_service_validate_intro_late()");
|
|
|
+
|
|
|
+ status = -1;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (intro->version == 3 && intro->parsed) {
|
|
|
+ if (!(intro->u.v3.auth_type == REND_NO_AUTH ||
|
|
|
+ intro->u.v3.auth_type == REND_BASIC_AUTH ||
|
|
|
+ intro->u.v3.auth_type == REND_STEALTH_AUTH)) {
|
|
|
+ /* This is an informative message, not an error, as in the old code */
|
|
|
+ if (err_msg_out)
|
|
|
+ tor_asprintf(err_msg_out,
|
|
|
+ "unknown authorization type %d",
|
|
|
+ intro->u.v3.auth_type);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ err:
|
|
|
return status;
|
|
|
}
|
|
|
|