diff options
Diffstat (limited to 'src/or')
64 files changed, 7000 insertions, 1982 deletions
diff --git a/src/or/addressmap.c b/src/or/addressmap.c index 33fd7e0f4a..85a6434f4a 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -376,29 +376,38 @@ addressmap_rewrite(char *address, size_t maxlen, char *addr_orig = tor_strdup(address); char *log_addr_orig = NULL; + /* We use a loop here to limit the total number of rewrites we do, + * so that we can't hit an infinite loop. */ for (rewrites = 0; rewrites < 16; rewrites++) { int exact_match = 0; log_addr_orig = tor_strdup(escaped_safe_str_client(address)); + /* First check to see if there's an exact match for this address */ ent = strmap_get(addressmap, address); if (!ent || !ent->new_address) { + /* And if we don't have an exact match, try to check whether + * we have a pattern-based match. + */ ent = addressmap_match_superdomains(address); } else { if (ent->src_wildcard && !ent->dst_wildcard && !strcasecmp(address, ent->new_address)) { - /* This is a rule like *.example.com example.com, and we just got - * "example.com" */ + /* This is a rule like "rewrite *.example.com to example.com", and we + * just got "example.com". Instead of calling it an infinite loop, + * call it complete. */ goto done; } - exact_match = 1; } if (!ent || !ent->new_address) { + /* We still have no match at all. We're done! */ goto done; } + /* Check wither the flags we were passed tell us not to use this + * mapping. */ switch (ent->source) { case ADDRMAPSRC_DNS: { @@ -431,6 +440,8 @@ addressmap_rewrite(char *address, size_t maxlen, goto done; } + /* Now fill in the address with the new address. That might be via + * appending some new stuff to the end, or via just replacing it. */ if (ent->dst_wildcard && !exact_match) { strlcat(address, ".", maxlen); strlcat(address, ent->new_address, maxlen); @@ -438,6 +449,7 @@ addressmap_rewrite(char *address, size_t maxlen, strlcpy(address, ent->new_address, maxlen); } + /* Is this now a .exit address? If so, remember where we got it.*/ if (!strcmpend(address, ".exit") && strcmpend(addr_orig, ".exit") && exit_source == ADDRMAPSRC_NONE) { diff --git a/src/or/channel.c b/src/or/channel.c index f547aea1b3..af5810788c 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -1738,7 +1738,7 @@ channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q) rv = get_cell_network_size(chan->wide_circ_ids); break; default: - tor_assert(1); + tor_assert_nonfatal_unreached_once(); } return rv; @@ -1838,45 +1838,58 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) } } -/** - * Write a cell to a channel +/** Write a generic cell type to a channel * - * Write a fixed-length cell to a channel using the write_cell() method. - * This is equivalent to the pre-channels connection_or_write_cell_to_buf(); - * it is called by the transport-independent code to deliver a cell to a - * channel for transmission. + * Write a generic cell to a channel. It is called by channel_write_cell(), + * channel_write_var_cell() and channel_write_packed_cell() in order to reduce + * code duplication. Notice that it takes cell as pointer of type void, + * this can be dangerous because no type check is performed. */ void -channel_write_cell(channel_t *chan, cell_t *cell) +channel_write_cell_generic_(channel_t *chan, const char *cell_type, + void *cell, cell_queue_entry_t *q) { - cell_queue_entry_t q; tor_assert(chan); tor_assert(cell); if (CHANNEL_IS_CLOSING(chan)) { - log_debug(LD_CHANNEL, "Discarding cell_t %p on closing channel %p with " - "global ID "U64_FORMAT, cell, chan, + log_debug(LD_CHANNEL, "Discarding %c %p on closing channel %p with " + "global ID "U64_FORMAT, *cell_type, cell, chan, U64_PRINTF_ARG(chan->global_identifier)); tor_free(cell); return; } - log_debug(LD_CHANNEL, - "Writing cell_t %p to channel %p with global ID " - U64_FORMAT, + "Writing %c %p to channel %p with global ID " + U64_FORMAT, *cell_type, cell, chan, U64_PRINTF_ARG(chan->global_identifier)); - q.type = CELL_QUEUE_FIXED; - q.u.fixed.cell = cell; - channel_write_cell_queue_entry(chan, &q); - + channel_write_cell_queue_entry(chan, q); /* Update the queue size estimate */ channel_update_xmit_queue_size(chan); } /** + * Write a cell to a channel + * + * Write a fixed-length cell to a channel using the write_cell() method. + * This is equivalent to the pre-channels connection_or_write_cell_to_buf(); + * it is called by the transport-independent code to deliver a cell to a + * channel for transmission. + */ + +void +channel_write_cell(channel_t *chan, cell_t *cell) +{ + cell_queue_entry_t q; + q.type = CELL_QUEUE_FIXED; + q.u.fixed.cell = cell; + channel_write_cell_generic_(chan, "cell_t", cell, &q); +} + +/** * Write a packed cell to a channel * * Write a packed cell to a channel using the write_cell() method. This is @@ -1888,30 +1901,9 @@ void channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell) { cell_queue_entry_t q; - - tor_assert(chan); - tor_assert(packed_cell); - - if (CHANNEL_IS_CLOSING(chan)) { - log_debug(LD_CHANNEL, "Discarding packed_cell_t %p on closing channel %p " - "with global ID "U64_FORMAT, packed_cell, chan, - U64_PRINTF_ARG(chan->global_identifier)); - packed_cell_free(packed_cell); - return; - } - - log_debug(LD_CHANNEL, - "Writing packed_cell_t %p to channel %p with global ID " - U64_FORMAT, - packed_cell, chan, - U64_PRINTF_ARG(chan->global_identifier)); - q.type = CELL_QUEUE_PACKED; q.u.packed.packed_cell = packed_cell; - channel_write_cell_queue_entry(chan, &q); - - /* Update the queue size estimate */ - channel_update_xmit_queue_size(chan); + channel_write_cell_generic_(chan, "packed_cell_t", packed_cell, &q); } /** @@ -1927,30 +1919,9 @@ void channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) { cell_queue_entry_t q; - - tor_assert(chan); - tor_assert(var_cell); - - if (CHANNEL_IS_CLOSING(chan)) { - log_debug(LD_CHANNEL, "Discarding var_cell_t %p on closing channel %p " - "with global ID "U64_FORMAT, var_cell, chan, - U64_PRINTF_ARG(chan->global_identifier)); - var_cell_free(var_cell); - return; - } - - log_debug(LD_CHANNEL, - "Writing var_cell_t %p to channel %p with global ID " - U64_FORMAT, - var_cell, chan, - U64_PRINTF_ARG(chan->global_identifier)); - q.type = CELL_QUEUE_VAR; q.u.var.var_cell = var_cell; - channel_write_cell_queue_entry(chan, &q); - - /* Update the queue size estimate */ - channel_update_xmit_queue_size(chan); + channel_write_cell_generic_(chan, "var_cell_t", var_cell, &q); } /** @@ -3249,9 +3220,10 @@ channel_free_all(void) channel_t * channel_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest) + const char *id_digest, + const ed25519_public_key_t *ed_id) { - return channel_tls_connect(addr, port, id_digest); + return channel_tls_connect(addr, port, id_digest, ed_id); } /** diff --git a/src/or/channel.h b/src/or/channel.h index a711b56d44..7e7b2ec899 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -382,6 +382,9 @@ struct cell_queue_entry_s { STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue); STATIC void cell_queue_entry_free(cell_queue_entry_t *q, int handed_off); + +void channel_write_cell_generic_(channel_t *chan, const char *cell_type, + void *cell, cell_queue_entry_t *q); #endif /* Channel operations for subclasses and internal use only */ @@ -486,7 +489,8 @@ int channel_send_destroy(circid_t circ_id, channel_t *chan, */ channel_t * channel_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest); + const char *id_digest, + const ed25519_public_key_t *ed_id); channel_t * channel_get_for_extend(const char *digest, const tor_addr_t *target_addr, diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 09cca95b64..9fb309d0fd 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -55,6 +55,7 @@ #include "router.h" #include "routerlist.h" #include "scheduler.h" +#include "torcert.h" /** How many CELL_PADDING cells have we received, ever? */ uint64_t stats_n_padding_cells_processed = 0; @@ -170,8 +171,10 @@ channel_tls_common_init(channel_tls_t *tlschan) channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest) + const char *id_digest, + const ed25519_public_key_t *ed_id) { + (void) ed_id; // XXXX not fully used yet channel_tls_t *tlschan = tor_malloc_zero(sizeof(*tlschan)); channel_t *chan = &(tlschan->base_); @@ -198,7 +201,7 @@ channel_tls_connect(const tor_addr_t *addr, uint16_t port, channel_mark_outgoing(chan); /* Set up or_connection stuff */ - tlschan->conn = connection_or_connect(addr, port, id_digest, tlschan); + tlschan->conn = connection_or_connect(addr, port, id_digest, ed_id, tlschan); /* connection_or_connect() will fill in tlschan->conn */ if (!(tlschan->conn)) { chan->reason_for_closing = CHANNEL_CLOSE_FOR_ERROR; @@ -598,7 +601,7 @@ channel_tls_get_remote_descr_method(channel_t *chan, int flags) break; default: /* Something's broken in channel.c */ - tor_assert(1); + tor_assert_nonfatal_unreached_once(); } } else { strlcpy(buf, "(No connection)", sizeof(buf)); @@ -667,7 +670,7 @@ channel_tls_is_canonical_method(channel_t *chan, int req) break; default: /* This shouldn't happen; channel.c is broken if it does */ - tor_assert(1); + tor_assert_nonfatal_unreached_once(); } } /* else return 0 for tlschan->conn == NULL */ @@ -1639,7 +1642,10 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) if (!(chan->conn->handshake_state->authenticated)) { tor_assert(tor_digest_is_zero( (const char*)(chan->conn->handshake_state-> - authenticated_peer_id))); + authenticated_rsa_peer_id))); + tor_assert(tor_mem_is_zero( + (const char*)(chan->conn->handshake_state-> + authenticated_ed25519_peer_id.pubkey), 32)); channel_set_circid_type(TLS_CHAN_TO_BASE(chan), NULL, chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS); @@ -1647,7 +1653,8 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) &(chan->conn->base_.addr), chan->conn->base_.port, (const char*)(chan->conn->handshake_state-> - authenticated_peer_id), + authenticated_rsa_peer_id), + NULL, // XXXX Ed key 0); } } @@ -1744,6 +1751,41 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) assert_connection_ok(TO_CONN(chan->conn),time(NULL)); } +/** Types of certificates that we know how to parse from CERTS cells. Each + * type corresponds to a different encoding format. */ +typedef enum cert_encoding_t { + CERT_ENCODING_UNKNOWN, /**< We don't recognize this. */ + CERT_ENCODING_X509, /**< It's an RSA key, signed with RSA, encoded in x509. + * (Actually, it might not be RSA. We test that later.) */ + CERT_ENCODING_ED25519, /**< It's something signed with an Ed25519 key, + * encoded asa a tor_cert_t.*/ + CERT_ENCODING_RSA_CROSSCERT, /**< It's an Ed key signed with an RSA key. */ +} cert_encoding_t; + +/** + * Given one of the certificate type codes used in a CERTS cell, + * return the corresponding cert_encoding_t that we should use to parse + * the certificate. + */ +static cert_encoding_t +certs_cell_typenum_to_cert_type(int typenum) +{ + switch (typenum) { + case CERTTYPE_RSA1024_ID_LINK: + case CERTTYPE_RSA1024_ID_ID: + case CERTTYPE_RSA1024_ID_AUTH: + return CERT_ENCODING_X509; + case CERTTYPE_ED_ID_SIGN: + case CERTTYPE_ED_SIGN_LINK: + case CERTTYPE_ED_SIGN_AUTH: + return CERT_ENCODING_ED25519; + case CERTTYPE_RSA1024_ID_EDID: + return CERT_ENCODING_RSA_CROSSCERT; + default: + return CERT_ENCODING_UNKNOWN; + } +} + /** * Process a CERTS cell from a channel. * @@ -1763,14 +1805,21 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) STATIC void channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) { -#define MAX_CERT_TYPE_WANTED OR_CERT_TYPE_AUTH_1024 - tor_x509_cert_t *certs[MAX_CERT_TYPE_WANTED + 1]; +#define MAX_CERT_TYPE_WANTED CERTTYPE_RSA1024_ID_EDID + /* These arrays will be sparse, since a cert type can be at most one + * of ed/x509 */ + tor_x509_cert_t *x509_certs[MAX_CERT_TYPE_WANTED + 1]; + tor_cert_t *ed_certs[MAX_CERT_TYPE_WANTED + 1]; + uint8_t *rsa_ed_cc_cert = NULL; + size_t rsa_ed_cc_cert_len = 0; + int n_certs, i; certs_cell_t *cc = NULL; int send_netinfo = 0; - memset(certs, 0, sizeof(certs)); + memset(x509_certs, 0, sizeof(x509_certs)); + memset(ed_certs, 0, sizeof(ed_certs)); tor_assert(cell); tor_assert(chan); tor_assert(chan->conn); @@ -1814,77 +1863,146 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) if (cert_type > MAX_CERT_TYPE_WANTED) continue; + const cert_encoding_t ct = certs_cell_typenum_to_cert_type(cert_type); + switch (ct) { + default: + case CERT_ENCODING_UNKNOWN: + break; + case CERT_ENCODING_X509: { + tor_x509_cert_t *x509_cert = tor_x509_cert_decode(cert_body, cert_len); + if (!x509_cert) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received undecodable certificate in CERTS cell from %s:%d", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + } else { + if (x509_certs[cert_type]) { + tor_x509_cert_free(x509_cert); + ERR("Duplicate x509 certificate"); + } else { + x509_certs[cert_type] = x509_cert; + } + } + break; + } + case CERT_ENCODING_ED25519: { + tor_cert_t *ed_cert = tor_cert_parse(cert_body, cert_len); + if (!ed_cert) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Received undecodable Ed certificate " + "in CERTS cell from %s:%d", + safe_str(chan->conn->base_.address), + chan->conn->base_.port); + } else { + if (ed_certs[cert_type]) { + tor_cert_free(ed_cert); + ERR("Duplicate Ed25519 certificate"); + } else { + ed_certs[cert_type] = ed_cert; + } + } + break; + } - tor_x509_cert_t *cert = tor_x509_cert_decode(cert_body, cert_len); - if (!cert) { - log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, - "Received undecodable certificate in CERTS cell from %s:%d", - safe_str(chan->conn->base_.address), - chan->conn->base_.port); - } else { - if (certs[cert_type]) { - tor_x509_cert_free(cert); - ERR("Duplicate x509 certificate"); - } else { - certs[cert_type] = cert; + case CERT_ENCODING_RSA_CROSSCERT: { + if (rsa_ed_cc_cert) { + ERR("Duplicate RSA->Ed25519 crosscert"); + } else { + rsa_ed_cc_cert = tor_memdup(cert_body, cert_len); + rsa_ed_cc_cert_len = cert_len; + } + break; } } } - tor_x509_cert_t *id_cert = certs[OR_CERT_TYPE_ID_1024]; - tor_x509_cert_t *auth_cert = certs[OR_CERT_TYPE_AUTH_1024]; - tor_x509_cert_t *link_cert = certs[OR_CERT_TYPE_TLS_LINK]; + /* Move the certificates we (might) want into the handshake_state->certs + * structure. */ + tor_x509_cert_t *id_cert = x509_certs[CERTTYPE_RSA1024_ID_ID]; + tor_x509_cert_t *auth_cert = x509_certs[CERTTYPE_RSA1024_ID_AUTH]; + tor_x509_cert_t *link_cert = x509_certs[CERTTYPE_RSA1024_ID_LINK]; + chan->conn->handshake_state->certs->auth_cert = auth_cert; + chan->conn->handshake_state->certs->link_cert = link_cert; + chan->conn->handshake_state->certs->id_cert = id_cert; + x509_certs[CERTTYPE_RSA1024_ID_ID] = + x509_certs[CERTTYPE_RSA1024_ID_AUTH] = + x509_certs[CERTTYPE_RSA1024_ID_LINK] = NULL; + + tor_cert_t *ed_id_sign = ed_certs[CERTTYPE_ED_ID_SIGN]; + tor_cert_t *ed_sign_link = ed_certs[CERTTYPE_ED_SIGN_LINK]; + tor_cert_t *ed_sign_auth = ed_certs[CERTTYPE_ED_SIGN_AUTH]; + chan->conn->handshake_state->certs->ed_id_sign = ed_id_sign; + chan->conn->handshake_state->certs->ed_sign_link = ed_sign_link; + chan->conn->handshake_state->certs->ed_sign_auth = ed_sign_auth; + ed_certs[CERTTYPE_ED_ID_SIGN] = + ed_certs[CERTTYPE_ED_SIGN_LINK] = + ed_certs[CERTTYPE_ED_SIGN_AUTH] = NULL; + + chan->conn->handshake_state->certs->ed_rsa_crosscert = rsa_ed_cc_cert; + chan->conn->handshake_state->certs->ed_rsa_crosscert_len = + rsa_ed_cc_cert_len; + rsa_ed_cc_cert = NULL; + + int severity; + /* Note that this warns more loudly about time and validity if we were + * _trying_ to connect to an authority, not necessarily if we _did_ connect + * to one. */ + if (chan->conn->handshake_state->started_here && + router_digest_is_trusted_dir(TLS_CHAN_TO_BASE(chan)->identity_digest)) + severity = LOG_WARN; + else + severity = LOG_PROTOCOL_WARN; + + const ed25519_public_key_t *checked_ed_id = NULL; + const common_digests_t *checked_rsa_id = NULL; + or_handshake_certs_check_both(severity, + chan->conn->handshake_state->certs, + chan->conn->tls, + time(NULL), + &checked_ed_id, + &checked_rsa_id); + + if (!checked_rsa_id) + ERR("Invalid certificate chain!"); if (chan->conn->handshake_state->started_here) { - int severity; - if (! (id_cert && link_cert)) - ERR("The certs we wanted were missing"); - /* Okay. We should be able to check the certificates now. */ - if (! tor_tls_cert_matches_key(chan->conn->tls, link_cert)) { - ERR("The link certificate didn't match the TLS public key"); - } - /* Note that this warns more loudly about time and validity if we were - * _trying_ to connect to an authority, not necessarily if we _did_ connect - * to one. */ - if (router_digest_is_trusted_dir( - TLS_CHAN_TO_BASE(chan)->identity_digest)) - severity = LOG_WARN; - else - severity = LOG_PROTOCOL_WARN; - - if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, 0)) - ERR("The link certificate was not valid"); - if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, 1)) - ERR("The ID certificate was not valid"); + /* No more information is needed. */ chan->conn->handshake_state->authenticated = 1; + chan->conn->handshake_state->authenticated_rsa = 1; { - const common_digests_t *id_digests = - tor_x509_cert_get_id_digests(id_cert); + const common_digests_t *id_digests = checked_rsa_id; crypto_pk_t *identity_rcvd; if (!id_digests) ERR("Couldn't compute digests for key in ID cert"); identity_rcvd = tor_tls_cert_get_key(id_cert); - if (!identity_rcvd) - ERR("Internal error: Couldn't get RSA key from ID cert."); - memcpy(chan->conn->handshake_state->authenticated_peer_id, + if (!identity_rcvd) { + ERR("Couldn't get RSA key from ID cert."); + } + memcpy(chan->conn->handshake_state->authenticated_rsa_peer_id, id_digests->d[DIGEST_SHA1], DIGEST_LEN); channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd, chan->conn->link_proto < MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS); crypto_pk_free(identity_rcvd); } + if (checked_ed_id) { + chan->conn->handshake_state->authenticated_ed25519 = 1; + memcpy(&chan->conn->handshake_state->authenticated_ed25519_peer_id, + checked_ed_id, sizeof(ed25519_public_key_t)); + } + if (connection_or_client_learned_peer_id(chan->conn, - chan->conn->handshake_state->authenticated_peer_id) < 0) + chan->conn->handshake_state->authenticated_rsa_peer_id, + checked_ed_id) < 0) ERR("Problem setting or checking peer id"); log_info(LD_OR, - "Got some good certificates from %s:%d: Authenticated it.", - safe_str(chan->conn->base_.address), chan->conn->base_.port); - - chan->conn->handshake_state->id_cert = id_cert; - certs[OR_CERT_TYPE_ID_1024] = NULL; + "Got some good certificates from %s:%d: Authenticated it with " + "RSA%s", + safe_str(chan->conn->base_.address), chan->conn->base_.port, + checked_ed_id ? " and Ed25519" : ""); if (!public_server_mode(get_options())) { /* If we initiated the connection and we are not a public server, we @@ -1893,25 +2011,14 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) send_netinfo = 1; } } else { - if (! (id_cert && auth_cert)) - ERR("The certs we wanted were missing"); - - /* Remember these certificates so we can check an AUTHENTICATE cell */ - if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, 1)) - ERR("The authentication certificate was not valid"); - if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, 1)) - ERR("The ID certificate was not valid"); - + /* We can't call it authenticated till we see an AUTHENTICATE cell. */ log_info(LD_OR, - "Got some good certificates from %s:%d: " + "Got some good RSA%s certificates from %s:%d. " "Waiting for AUTHENTICATE.", + checked_ed_id ? " and Ed25519" : "", safe_str(chan->conn->base_.address), chan->conn->base_.port); /* XXXX check more stuff? */ - - chan->conn->handshake_state->id_cert = id_cert; - chan->conn->handshake_state->auth_cert = auth_cert; - certs[OR_CERT_TYPE_ID_1024] = certs[OR_CERT_TYPE_AUTH_1024] = NULL; } chan->conn->handshake_state->received_certs_cell = 1; @@ -1925,9 +2032,13 @@ channel_tls_process_certs_cell(var_cell_t *cell, channel_tls_t *chan) } err: - for (unsigned u = 0; u < ARRAY_LENGTH(certs); ++u) { - tor_x509_cert_free(certs[u]); + for (unsigned u = 0; u < ARRAY_LENGTH(x509_certs); ++u) { + tor_x509_cert_free(x509_certs[u]); } + for (unsigned u = 0; u < ARRAY_LENGTH(ed_certs); ++u) { + tor_cert_free(ed_certs[u]); + } + tor_free(rsa_ed_cc_cert); certs_cell_free(cc); #undef ERR } @@ -1984,8 +2095,12 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan) /* Now see if there is an authentication type we can use */ for (i = 0; i < n_types; ++i) { uint16_t authtype = auth_challenge_cell_get_methods(ac, i); - if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET) - use_type = authtype; + if (authchallenge_type_is_supported(authtype)) { + if (use_type == -1 || + authchallenge_type_is_better(authtype, use_type)) { + use_type = authtype; + } + } } chan->conn->handshake_state->received_auth_challenge = 1; @@ -2000,9 +2115,10 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan) if (use_type >= 0) { log_info(LD_OR, "Got an AUTH_CHALLENGE cell from %s:%d: Sending " - "authentication", + "authentication type %d", safe_str(chan->conn->base_.address), - chan->conn->base_.port); + chan->conn->base_.port, + use_type); if (connection_or_send_authenticate_cell(chan->conn, use_type) < 0) { log_warn(LD_OR, @@ -2043,9 +2159,11 @@ channel_tls_process_auth_challenge_cell(var_cell_t *cell, channel_tls_t *chan) STATIC void channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) { - uint8_t expected[V3_AUTH_FIXED_PART_LEN+256]; + var_cell_t *expected_cell = NULL; const uint8_t *auth; int authlen; + int authtype; + int bodylen; tor_assert(cell); tor_assert(chan); @@ -2058,6 +2176,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) safe_str(chan->conn->base_.address), \ chan->conn->base_.port, (s)); \ connection_or_close_for_error(chan->conn, 0); \ + var_cell_free(expected_cell); \ return; \ } while (0) @@ -2075,9 +2194,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) } if (!(chan->conn->handshake_state->received_certs_cell)) ERR("We never got a certs cell"); - if (chan->conn->handshake_state->auth_cert == NULL) - ERR("We never got an authentication certificate"); - if (chan->conn->handshake_state->id_cert == NULL) + if (chan->conn->handshake_state->certs->id_cert == NULL) ERR("We never got an identity certificate"); if (cell->payload_len < 4) ERR("Cell was way too short"); @@ -2089,8 +2206,9 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) if (4 + len > cell->payload_len) ERR("Authenticator was truncated"); - if (type != AUTHTYPE_RSA_SHA256_TLSSECRET) + if (! authchallenge_type_is_supported(type)) ERR("Authenticator type was not recognized"); + authtype = type; auth += 4; authlen = len; @@ -2099,25 +2217,55 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) if (authlen < V3_AUTH_BODY_LEN + 1) ERR("Authenticator was too short"); - ssize_t bodylen = - connection_or_compute_authenticate_cell_body( - chan->conn, expected, sizeof(expected), NULL, 1); - if (bodylen < 0 || bodylen != V3_AUTH_FIXED_PART_LEN) + expected_cell = connection_or_compute_authenticate_cell_body( + chan->conn, authtype, NULL, NULL, 1); + if (! expected_cell) ERR("Couldn't compute expected AUTHENTICATE cell body"); - if (tor_memneq(expected, auth, bodylen)) + int sig_is_rsa; + if (authtype == AUTHTYPE_RSA_SHA256_TLSSECRET || + authtype == AUTHTYPE_RSA_SHA256_RFC5705) { + bodylen = V3_AUTH_BODY_LEN; + sig_is_rsa = 1; + } else { + tor_assert(authtype == AUTHTYPE_ED25519_SHA256_RFC5705); + /* Our earlier check had better have made sure we had room + * for an ed25519 sig (inadvertently) */ + tor_assert(V3_AUTH_BODY_LEN > ED25519_SIG_LEN); + bodylen = authlen - ED25519_SIG_LEN; + sig_is_rsa = 0; + } + if (expected_cell->payload_len != bodylen+4) { + ERR("Expected AUTHENTICATE cell body len not as expected."); + } + + /* Length of random part. */ + if (BUG(bodylen < 24)) { + // LCOV_EXCL_START + ERR("Bodylen is somehow less than 24, which should really be impossible"); + // LCOV_EXCL_STOP + } + + if (tor_memneq(expected_cell->payload+4, auth, bodylen-24)) ERR("Some field in the AUTHENTICATE cell body was not as expected"); - { + if (sig_is_rsa) { + if (chan->conn->handshake_state->certs->ed_id_sign != NULL) + ERR("RSA-signed AUTHENTICATE response provided with an ED25519 cert"); + + if (chan->conn->handshake_state->certs->auth_cert == NULL) + ERR("We never got an RSA authentication certificate"); + crypto_pk_t *pk = tor_tls_cert_get_key( - chan->conn->handshake_state->auth_cert); + chan->conn->handshake_state->certs->auth_cert); char d[DIGEST256_LEN]; char *signed_data; size_t keysize; int signed_len; - if (!pk) - ERR("Internal error: couldn't get RSA key from AUTH cert."); + if (! pk) { + ERR("Couldn't get RSA key from AUTH cert."); + } crypto_digest256(d, (char*)auth, V3_AUTH_BODY_LEN, DIGEST_SHA256); keysize = crypto_pk_keysize(pk); @@ -2128,7 +2276,7 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) crypto_pk_free(pk); if (signed_len < 0) { tor_free(signed_data); - ERR("Signature wasn't valid"); + ERR("RSA signature wasn't valid"); } if (signed_len < DIGEST256_LEN) { tor_free(signed_data); @@ -2141,22 +2289,45 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) ERR("Signature did not match data to be signed."); } tor_free(signed_data); + } else { + if (chan->conn->handshake_state->certs->ed_id_sign == NULL) + ERR("We never got an Ed25519 identity certificate."); + if (chan->conn->handshake_state->certs->ed_sign_auth == NULL) + ERR("We never got an Ed25519 authentication certificate."); + + const ed25519_public_key_t *authkey = + &chan->conn->handshake_state->certs->ed_sign_auth->signed_key; + ed25519_signature_t sig; + tor_assert(authlen > ED25519_SIG_LEN); + memcpy(&sig.sig, auth + authlen - ED25519_SIG_LEN, ED25519_SIG_LEN); + if (ed25519_checksig(&sig, auth, authlen - ED25519_SIG_LEN, authkey)<0) { + ERR("Ed25519 signature wasn't valid."); + } } /* Okay, we are authenticated. */ chan->conn->handshake_state->received_authenticate = 1; chan->conn->handshake_state->authenticated = 1; + chan->conn->handshake_state->authenticated_rsa = 1; chan->conn->handshake_state->digest_received_data = 0; { - crypto_pk_t *identity_rcvd = - tor_tls_cert_get_key(chan->conn->handshake_state->id_cert); - const common_digests_t *id_digests = - tor_x509_cert_get_id_digests(chan->conn->handshake_state->id_cert); + tor_x509_cert_t *id_cert = chan->conn->handshake_state->certs->id_cert; + crypto_pk_t *identity_rcvd = tor_tls_cert_get_key(id_cert); + const common_digests_t *id_digests = tor_x509_cert_get_id_digests(id_cert); + const ed25519_public_key_t *ed_identity_received = NULL; + + if (! sig_is_rsa) { + chan->conn->handshake_state->authenticated_ed25519 = 1; + ed_identity_received = + &chan->conn->handshake_state->certs->ed_id_sign->signing_key; + memcpy(&chan->conn->handshake_state->authenticated_ed25519_peer_id, + ed_identity_received, sizeof(ed25519_public_key_t)); + } /* This must exist; we checked key type when reading the cert. */ tor_assert(id_digests); - memcpy(chan->conn->handshake_state->authenticated_peer_id, + memcpy(chan->conn->handshake_state->authenticated_rsa_peer_id, id_digests->d[DIGEST_SHA1], DIGEST_LEN); channel_set_circid_type(TLS_CHAN_TO_BASE(chan), identity_rcvd, @@ -2167,15 +2338,19 @@ channel_tls_process_authenticate_cell(var_cell_t *cell, channel_tls_t *chan) &(chan->conn->base_.addr), chan->conn->base_.port, (const char*)(chan->conn->handshake_state-> - authenticated_peer_id), + authenticated_rsa_peer_id), + ed_identity_received, 0); log_info(LD_OR, - "Got an AUTHENTICATE cell from %s:%d: Looks good.", + "Got an AUTHENTICATE cell from %s:%d, type %d: Looks good.", safe_str(chan->conn->base_.address), - chan->conn->base_.port); + chan->conn->base_.port, + authtype); } + var_cell_free(expected_cell); + #undef ERR } diff --git a/src/or/channeltls.h b/src/or/channeltls.h index 8b5863a461..729e595615 100644 --- a/src/or/channeltls.h +++ b/src/or/channeltls.h @@ -29,7 +29,8 @@ struct channel_tls_s { #endif /* TOR_CHANNEL_INTERNAL_ */ channel_t * channel_tls_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest); + const char *id_digest, + const ed25519_public_key_t *ed_id); channel_listener_t * channel_tls_get_listener(void); channel_listener_t * channel_tls_start_listener(void); channel_t * channel_tls_handle_incoming(or_connection_t *orconn); diff --git a/src/or/circpathbias.c b/src/or/circpathbias.c index 9f93e737f7..6ee69aac1e 100644 --- a/src/or/circpathbias.c +++ b/src/or/circpathbias.c @@ -11,6 +11,14 @@ * different tor nodes, in an attempt to detect attacks where * an attacker deliberately causes circuits to fail until the client * choses a path they like. + * + * This code is currently configured in a warning-only mode, though false + * positives appear to be rare in practice. There is also support for + * disabling really bad guards, but it's quite experimental and may have bad + * anonymity effects. + * + * The information here is associated with the entry_guard_t object for + * each guard, and stored persistently in the state file. */ #include "or.h" diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index cb9c146fb7..0881f231aa 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -9,6 +9,20 @@ * * \brief Implements the details of building circuits (by chosing paths, * constructing/sending create/extend cells, and so on). + * + * On the client side, this module handles launching circuits. Circuit + * launches are srtarted from circuit_establish_circuit(), called from + * circuit_launch_by_extend_info()). To choose the path the circuit will + * take, onion_extend_cpath() calls into a maze of node selection functions. + * + * Once the circuit is ready to be launched, the first hop is treated as a + * special case with circuit_handle_first_hop(), since it might need to open a + * channel. As the channel opens, and later as CREATED and RELAY_EXTENDED + * cells arrive, the client will invoke circuit_send_next_onion_skin() to send + * CREATE or RELAY_EXTEND cells. + * + * On the server side, this module also handles the logic of responding to + * RELAY_EXTEND requests, using circuit_extend(). **/ #define CIRCUITBUILD_PRIVATE @@ -70,7 +84,9 @@ channel_connect_for_circuit(const tor_addr_t *addr, uint16_t port, { channel_t *chan; - chan = channel_connect(addr, port, id_digest); + chan = channel_connect(addr, port, id_digest, + NULL // XXXX Ed25519 id. + ); if (chan) command_setup_channel(chan); return chan; @@ -2216,7 +2232,6 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) * This is an incomplete fix, but is no worse than the previous behaviour, * and only applies to minimal, testing tor networks * (so it's no less secure) */ - /*XXXX++ use the using_as_guard flag to accomplish this.*/ if (options->UseEntryGuards && (!options->TestingTorNetwork || smartlist_len(nodelist_get_list()) > smartlist_len(get_entry_guards()) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 2b3c4169cb..dee103e36a 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -7,7 +7,48 @@ /** * \file circuitlist.c * - * \brief Manage the global circuit list, and looking up circuits within it. + * \brief Manage global structures that list and index circuits, and + * look up circuits within them. + * + * One of the most frequent operations in Tor occurs every time that + * a relay cell arrives on a channel. When that happens, we need to + * find which circuit it is associated with, based on the channel and the + * circuit ID in the relay cell. + * + * To handle that, we maintain a global list of circuits, and a hashtable + * mapping [channel,circID] pairs to circuits. Circuits are added to and + * removed from this mapping using circuit_set_p_circid_chan() and + * circuit_set_n_circid_chan(). To look up a circuit from this map, most + * callers should use circuit_get_by_circid_channel(), though + * circuit_get_by_circid_channel_even_if_marked() is appropriate under some + * circumstances. + * + * We also need to allow for the possibility that we have blocked use of a + * circuit ID (because we are waiting to send a DESTROY cell), but the + * circuit is not there any more. For that case, we allow placeholder + * entries in the table, using channel_mark_circid_unusable(). + * + * To efficiently handle a channel that has just opened, we also maintain a + * list of the circuits waiting for channels, so we can attach them as + * needed without iterating through the whole list of circuits, using + * circuit_get_all_pending_on_channel(). + * + * In this module, we also handle the list of circuits that have been + * marked for close elsewhere, and close them as needed. (We use this + * "mark now, close later" pattern here and elsewhere to avoid + * unpredictable recursion if we closed every circuit immediately upon + * realizing it needed to close.) See circuit_mark_for_close() for the + * mark function, and circuit_close_all_marked() for the close function. + * + * For hidden services, we need to be able to look up introduction point + * circuits and rendezvous circuits by cookie, key, etc. These are + * currently handled with linear searches in + * circuit_get_ready_rend_circuit_by_rend_data(), + * circuit_get_next_by_pk_and_purpose(), and with hash lookups in + * circuit_get_rendezvous() and circuit_get_intro_point(). + * + * This module is also the entry point for our out-of-memory handler + * logic, which was originally circuit-focused. **/ #define CIRCUITLIST_PRIVATE #include "or.h" @@ -23,6 +64,7 @@ #include "connection_or.h" #include "control.h" #include "main.h" +#include "hs_common.h" #include "networkstatus.h" #include "nodelist.h" #include "onion.h" @@ -1311,9 +1353,11 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) if (!circ->marked_for_close && circ->purpose == CIRCUIT_PURPOSE_C_REND_READY) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); - if (ocirc->rend_data && - !rend_cmp_service_ids(rend_data->onion_address, - ocirc->rend_data->onion_address) && + if (ocirc->rend_data == NULL) { + continue; + } + if (!rend_cmp_service_ids(rend_data_get_address(rend_data), + rend_data_get_address(ocirc->rend_data)) && tor_memeq(ocirc->rend_data->rend_cookie, rend_data->rend_cookie, REND_COOKIE_LEN)) @@ -1325,13 +1369,14 @@ circuit_get_ready_rend_circ_by_rend_data(const rend_data_t *rend_data) } /** Return the first circuit originating here in global_circuitlist after - * <b>start</b> whose purpose is <b>purpose</b>, and where - * <b>digest</b> (if set) matches the rend_pk_digest field. Return NULL if no - * circuit is found. If <b>start</b> is NULL, begin at the start of the list. + * <b>start</b> whose purpose is <b>purpose</b>, and where <b>digest</b> (if + * set) matches the private key digest of the rend data associated with the + * circuit. Return NULL if no circuit is found. If <b>start</b> is NULL, + * begin at the start of the list. */ origin_circuit_t * circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, - const char *digest, uint8_t purpose) + const uint8_t *digest, uint8_t purpose) { int idx; smartlist_t *lst = circuit_get_global_list(); @@ -1343,17 +1388,23 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, for ( ; idx < smartlist_len(lst); ++idx) { circuit_t *circ = smartlist_get(lst, idx); + origin_circuit_t *ocirc; if (circ->marked_for_close) continue; if (circ->purpose != purpose) continue; + /* At this point we should be able to get a valid origin circuit because + * the origin purpose we are looking for matches this circuit. */ + if (BUG(!CIRCUIT_PURPOSE_IS_ORIGIN(circ->purpose))) { + break; + } + ocirc = TO_ORIGIN_CIRCUIT(circ); if (!digest) - return TO_ORIGIN_CIRCUIT(circ); - else if (TO_ORIGIN_CIRCUIT(circ)->rend_data && - tor_memeq(TO_ORIGIN_CIRCUIT(circ)->rend_data->rend_pk_digest, - digest, DIGEST_LEN)) - return TO_ORIGIN_CIRCUIT(circ); + return ocirc; + if (rend_circuit_pk_digest_eq(ocirc, digest)) { + return ocirc; + } } return NULL; } @@ -1539,6 +1590,14 @@ circuit_set_intro_point_digest(or_circuit_t *circ, const uint8_t *digest) * cannibalize. * * If !CIRCLAUNCH_NEED_UPTIME, prefer returning non-uptime circuits. + * + * To "cannibalize" a circuit means to extend it an extra hop, and use it + * for some other purpose than we had originally intended. We do this when + * we want to perform some low-bandwidth task at a specific relay, and we + * would like the circuit to complete as soon as possible. (If we were going + * to use a lot of bandwidth, we wouldn't want a circuit with an extra hop. + * If we didn't care about circuit completion latency, we would just build + * a new circuit.) */ origin_circuit_t * circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, @@ -1833,7 +1892,7 @@ circuit_about_to_free(circuit_t *circ) if (orig_reason != END_CIRC_REASON_IP_NOW_REDUNDANT) { /* treat this like getting a nack from it */ log_info(LD_REND, "Failed intro circ %s to %s (awaiting ack). %s", - safe_str_client(ocirc->rend_data->onion_address), + safe_str_client(rend_data_get_address(ocirc->rend_data)), safe_str_client(build_state_get_exit_nickname(ocirc->build_state)), timed_out ? "Recording timeout." : "Removing from descriptor."); rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, @@ -1850,7 +1909,7 @@ circuit_about_to_free(circuit_t *circ) log_info(LD_REND, "Failed intro circ %s to %s " "(building circuit to intro point). " "Marking intro point as possibly unreachable.", - safe_str_client(ocirc->rend_data->onion_address), + safe_str_client(rend_data_get_address(ocirc->rend_data)), safe_str_client(build_state_get_exit_nickname( ocirc->build_state))); rend_client_report_intro_point_failure(ocirc->build_state->chosen_exit, diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 2707b426ab..989c02afd5 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -45,7 +45,7 @@ origin_circuit_t *circuit_get_by_global_id(uint32_t id); origin_circuit_t *circuit_get_ready_rend_circ_by_rend_data( const rend_data_t *rend_data); origin_circuit_t *circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, - const char *digest, uint8_t purpose); + const uint8_t *digest, uint8_t purpose); or_circuit_t *circuit_get_rendezvous(const uint8_t *cookie); or_circuit_t *circuit_get_intro_point(const uint8_t *digest); void circuit_set_rendezvous_cookie(or_circuit_t *circ, const uint8_t *cookie); diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index 5c2ebde73b..0219459cdb 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -500,7 +500,7 @@ ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, tor_assert(pol_data_2); p1 = TO_EWMA_POL_DATA(pol_data_1); - p2 = TO_EWMA_POL_DATA(pol_data_1); + p2 = TO_EWMA_POL_DATA(pol_data_2); if (p1 != p2) { /* Get the head cell_ewma_t from each queue */ diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 84574cd5b9..ba7b75ff25 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -6,7 +6,25 @@ /** * \file circuituse.c - * \brief Launch the right sort of circuits and attach streams to them. + * \brief Launch the right sort of circuits and attach the right streams to + * them. + * + * As distinct from circuitlist.c, which manages lookups to find circuits, and + * circuitbuild.c, which handles the logistics of circuit construction, this + * module keeps track of which streams can be attached to which circuits (in + * circuit_get_best()), and attaches streams to circuits (with + * circuit_try_attaching_streams(), connection_ap_handshake_attach_circuit(), + * and connection_ap_handshake_attach_chosen_circuit() ). + * + * This module also makes sure that we are building circuits for all of the + * predicted ports, using circuit_remove_handled_ports(), + * circuit_stream_is_being_handled(), and circuit_build_needed_cirs(). It + * handles launching circuits for specific targets using + * circuit_launch_by_extend_info(). + * + * This is also where we handle expiring circuits that have been around for + * too long without actually completing, along with the circuit_build_timeout + * logic in circuitstats.c. **/ #include "or.h" @@ -22,6 +40,7 @@ #include "connection_edge.h" #include "control.h" #include "entrynodes.h" +#include "hs_common.h" #include "nodelist.h" #include "networkstatus.h" #include "policies.h" @@ -154,8 +173,8 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, if ((edge_conn->rend_data && !origin_circ->rend_data) || (!edge_conn->rend_data && origin_circ->rend_data) || (edge_conn->rend_data && origin_circ->rend_data && - rend_cmp_service_ids(edge_conn->rend_data->onion_address, - origin_circ->rend_data->onion_address))) { + rend_cmp_service_ids(rend_data_get_address(edge_conn->rend_data), + rend_data_get_address(origin_circ->rend_data)))) { /* this circ is not for this conn */ return 0; } @@ -1874,16 +1893,22 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, c->state, conn_state_to_string(c->type, c->state)); } tor_assert(ENTRY_TO_CONN(conn)->state == AP_CONN_STATE_CIRCUIT_WAIT); + + /* Will the exit policy of the exit node apply to this stream? */ check_exit_policy = conn->socks_request->command == SOCKS_COMMAND_CONNECT && !conn->use_begindir && !connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn)); + + /* Does this connection want a one-hop circuit? */ want_onehop = conn->want_onehop; + /* Do we need a high-uptime circuit? */ need_uptime = !conn->want_onehop && !conn->use_begindir && smartlist_contains_int_as_string(options->LongLivedPorts, conn->socks_request->port); + /* Do we need an "internal" circuit? */ if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL) need_internal = 1; else if (conn->use_begindir || conn->want_onehop) @@ -1891,21 +1916,31 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, else need_internal = 0; - circ = circuit_get_best(conn, 1, desired_circuit_purpose, + /* We now know what kind of circuit we need. See if there is an + * open circuit that we can use for this stream */ + circ = circuit_get_best(conn, 1 /* Insist on open circuits */, + desired_circuit_purpose, need_uptime, need_internal); if (circ) { + /* We got a circuit that will work for this stream! We can return it. */ *circp = circ; return 1; /* we're happy */ } + /* Okay, there's no circuit open that will work for this stream. Let's + * see if there's an in-progress circuit or if we have to launch one */ + + /* Do we know enough directory info to build circuits at all? */ int have_path = have_enough_path_info(!need_internal); if (!want_onehop && (!router_have_minimum_dir_info() || !have_path)) { + /* If we don't have enough directory information, we can't build + * multihop circuits. + */ if (!connection_get_by_type(CONN_TYPE_DIR)) { int severity = LOG_NOTICE; - /* FFFF if this is a tunneled directory fetch, don't yell - * as loudly. the user doesn't even know it's happening. */ + /* Retry some stuff that might help the connection work. */ if (entry_list_is_constrained(options) && entries_known_but_down(options)) { log_fn(severity, LD_APP|LD_DIR, @@ -1926,14 +1961,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, routerlist_retry_directory_downloads(time(NULL)); } } - /* the stream will be dealt with when router_have_minimum_dir_info becomes - * 1, or when all directory attempts fail and directory_all_unreachable() + /* Since we didn't have enough directory info, we can't attach now. The + * stream will be dealt with when router_have_minimum_dir_info becomes 1, + * or when all directory attempts fail and directory_all_unreachable() * kills it. */ return 0; } - /* Do we need to check exit policy? */ + /* Check whether the exit policy of the chosen exit, or the exit policies + * of _all_ nodes, would forbid this node. */ if (check_exit_policy) { if (!conn->chosen_exit_name) { struct in_addr in; @@ -1974,16 +2011,25 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } } - /* is one already on the way? */ - circ = circuit_get_best(conn, 0, desired_circuit_purpose, + /* Now, check whether there already a circuit on the way that could handle + * this stream. This check matches the one above, but this time we + * do not require that the circuit will work. */ + circ = circuit_get_best(conn, 0 /* don't insist on open circuits */, + desired_circuit_purpose, need_uptime, need_internal); if (circ) log_debug(LD_CIRC, "one on the way!"); + if (!circ) { + /* No open or in-progress circuit could handle this stream! We + * will have to launch one! + */ + + /* THe chosen exit node, if there is one. */ extend_info_t *extend_info=NULL; - uint8_t new_circ_purpose; const int n_pending = count_pending_general_client_circuits(); + /* Do we have too many pending circuits? */ if (n_pending >= options->MaxClientCircuitsPending) { static ratelim_t delay_limit = RATELIM_INIT(10*60); char *m; @@ -1997,6 +2043,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, return 0; } + /* If this is a hidden service trying to start an introduction point, + * handle that case. */ if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { /* need to pick an intro point */ rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; @@ -2005,7 +2053,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (!extend_info) { log_info(LD_REND, "No intro points for '%s': re-fetching service descriptor.", - safe_str_client(rend_data->onion_address)); + safe_str_client(rend_data_get_address(rend_data))); rend_client_refetch_v2_renddesc(rend_data); connection_ap_mark_as_non_pending_circuit(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; @@ -2013,7 +2061,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } log_info(LD_REND,"Chose %s as intro point for '%s'.", extend_info_describe(extend_info), - safe_str_client(rend_data->onion_address)); + safe_str_client(rend_data_get_address(rend_data))); } /* If we have specified a particular exit node for our @@ -2034,7 +2082,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, "Discarding this circuit.", conn->chosen_exit_name); return -1; } - } else { + } else { /* ! (r && node_has_descriptor(r)) */ log_debug(LD_DIR, "considering %d, %s", want_onehop, conn->chosen_exit_name); if (want_onehop && conn->chosen_exit_name[0] == '$') { @@ -2057,7 +2105,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, extend_info = extend_info_new(conn->chosen_exit_name+1, digest, NULL, NULL, &addr, conn->socks_request->port); - } else { + } else { /* ! (want_onehop && conn->chosen_exit_name[0] == '$') */ /* We will need an onion key for the router, and we * don't have one. Refuse or relax requirements. */ log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, @@ -2075,8 +2123,10 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } } } - } + } /* Done checking for general circutis with chosen exits. */ + /* What purpose do we need to launch this circuit with? */ + uint8_t new_circ_purpose; if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED) new_circ_purpose = CIRCUIT_PURPOSE_C_ESTABLISH_REND; else if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) @@ -2085,6 +2135,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, new_circ_purpose = desired_circuit_purpose; #ifdef ENABLE_TOR2WEB_MODE + /* If tor2Web is on, then hidden service requests should be one-hop. + */ if (options->Tor2webMode && (new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND || new_circ_purpose == CIRCUIT_PURPOSE_C_INTRODUCING)) { @@ -2092,6 +2144,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } #endif + /* Determine what kind of a circuit to launch, and actually launch it. */ { int flags = CIRCLAUNCH_NEED_CAPACITY; if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL; @@ -2103,6 +2156,8 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, extend_info_free(extend_info); + /* Now trigger things that need to happen when we launch circuits */ + if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_GENERAL) { /* We just caused a circuit to get built because of this stream. * If this stream has caused a _lot_ of circuits to be built, that's @@ -2126,6 +2181,10 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } } } /* endif (!circ) */ + + /* We either found a good circuit, or launched a new circuit, or failed to + * do so. Report success, and delay. */ + if (circ) { /* Mark the circuit with the isolation fields for this connection. * When the circuit arrives, we'll clear these flags: this is @@ -2325,7 +2384,9 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, pathbias_count_use_attempt(circ); + /* Now, actually link the connection. */ link_apconn_to_circ(conn, circ, cpath); + tor_assert(conn->socks_request); if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) { if (!conn->use_begindir) @@ -2340,12 +2401,11 @@ connection_ap_handshake_attach_chosen_circuit(entry_connection_t *conn, return 1; } -/** Try to find a safe live circuit for CONN_TYPE_AP connection conn. If - * we don't find one: if conn cannot be handled by any known nodes, - * warn and return -1 (conn needs to die, and is maybe already marked); - * else launch new circuit (if necessary) and return 0. - * Otherwise, associate conn with a safe live circuit, do the - * right next step, and return 1. +/** Try to find a safe live circuit for stream <b>conn</b>. If we find one, + * attach the stream, send appropriate cells, and return 1. Otherwise, + * try to launch new circuit(s) for the stream. If we can launch + * circuits, return 0. Otherwise, if we simply can't proceed with + * this stream, return -1. (conn needs to die, and is maybe already marked). */ /* XXXX this function should mark for close whenever it returns -1; * its callers shouldn't have to worry about that. */ @@ -2364,6 +2424,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) conn_age = (int)(time(NULL) - base_conn->timestamp_created); + /* Is this connection so old that we should give up on it? */ if (conn_age >= get_options()->SocksTimeout) { int severity = (tor_addr_is_null(&base_conn->addr) && !base_conn->port) ? LOG_INFO : LOG_NOTICE; @@ -2374,12 +2435,14 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) return -1; } + /* We handle "general" (non-onion) connections much more straightforwardly. + */ if (!connection_edge_is_rendezvous_stream(ENTRY_TO_EDGE_CONN(conn))) { /* we're a general conn */ origin_circuit_t *circ=NULL; /* Are we linked to a dir conn that aims to fetch a consensus? - * We check here because this conn might no longer be needed. */ + * We check here because the conn might no longer be needed. */ if (base_conn->linked_conn && base_conn->linked_conn->type == CONN_TYPE_DIR && base_conn->linked_conn->purpose == DIR_PURPOSE_FETCH_CONSENSUS) { @@ -2397,6 +2460,9 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) } } + /* If we have a chosen exit, we need to use a circuit that's + * open to that exit. See what exit we meant, and whether we can use it. + */ if (conn->chosen_exit_name) { const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); int opt = conn->chosen_exit_optional; @@ -2410,6 +2476,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) "Requested exit point '%s' is not known. %s.", conn->chosen_exit_name, opt ? "Trying others" : "Closing"); if (opt) { + /* If we are allowed to ignore the .exit request, do so */ conn->chosen_exit_optional = 0; tor_free(conn->chosen_exit_name); return 0; @@ -2422,6 +2489,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) "would refuse request. %s.", conn->chosen_exit_name, opt ? "Trying others" : "Closing"); if (opt) { + /* If we are allowed to ignore the .exit request, do so */ conn->chosen_exit_optional = 0; tor_free(conn->chosen_exit_name); return 0; @@ -2430,11 +2498,15 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) } } - /* find the circuit that we should use, if there is one. */ + /* Find the circuit that we should use, if there is one. Otherwise + * launch it. */ retval = circuit_get_open_circ_or_launch( conn, CIRCUIT_PURPOSE_C_GENERAL, &circ); - if (retval < 1) // XXXX++ if we totally fail, this still returns 0 -RD + if (retval < 1) { + /* We were either told "-1" (complete failure) or 0 (circuit in + * progress); we can't attach this stream yet. */ return retval; + } log_debug(LD_APP|LD_CIRC, "Attaching apconn to circ %u (stream %d sec old).", @@ -2443,7 +2515,8 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) * sucking. */ circuit_log_path(LOG_INFO,LD_APP|LD_CIRC,circ); - /* We have found a suitable circuit for our conn. Hurray. */ + /* We have found a suitable circuit for our conn. Hurray. Do + * the attachment. */ return connection_ap_handshake_attach_chosen_circuit(conn, circ, NULL); } else { /* we're a rendezvous conn */ diff --git a/src/or/config.c b/src/or/config.c index 8568ea9d64..972e3be09e 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -6,7 +6,56 @@ /** * \file config.c - * \brief Code to parse and interpret configuration files. + * \brief Code to interpret the user's configuration of Tor. + * + * This module handles torrc configuration file, including parsing it, + * combining it with torrc.defaults and the command line, allowing + * user changes to it (via editing and SIGHUP or via the control port), + * writing it back to disk (because of SAVECONF from the control port), + * and -- most importantly, acting on it. + * + * The module additionally has some tools for manipulating and + * inspecting values that are calculated as a result of the + * configured options. + * + * <h3>How to add new options</h3> + * + * To add new items to the torrc, there are a minimum of three places to edit: + * <ul> + * <li>The or_options_t structure in or.h, where the options are stored. + * <li>The option_vars_ array below in this module, which configures + * the names of the torrc options, their types, their multiplicities, + * and their mappings to fields in or_options_t. + * <li>The manual in doc/tor.1.txt, to document what the new option + * is, and how it works. + * </ul> + * + * Additionally, you might need to edit these places too: + * <ul> + * <li>options_validate() below, in case you want to reject some possible + * values of the new configuration option. + * <li>options_transition_allowed() below, in case you need to + * forbid some or all changes in the option while Tor is + * running. + * <li>options_transition_affects_workers(), in case changes in the option + * might require Tor to relaunch or reconfigure its worker threads. + * <li>options_transition_affects_descriptor(), in case changes in the + * option might require a Tor relay to build and publish a new server + * descriptor. + * <li>options_act() and/or options_act_reversible(), in case there's some + * action that needs to be taken immediately based on the option's + * value. + * </ul> + * + * <h3>Changing the value of an option</h3> + * + * Because of the SAVECONF command from the control port, it's a bad + * idea to change the value of any user-configured option in the + * or_options_t. If you want to sometimes do this anyway, we recommend + * that you create a secondary field in or_options_t; that you have the + * user option linked only to the secondary field; that you use the + * secondary field to initialize the one that Tor actually looks at; and that + * you use the one Tor looks as the one that you modify. **/ #define CONFIG_PRIVATE @@ -781,7 +830,7 @@ set_options(or_options_t *new_val, char **msg) tor_free(line); } } else { - smartlist_add(elements, tor_strdup(options_format.vars[i].name)); + smartlist_add_strdup(elements, options_format.vars[i].name); smartlist_add(elements, NULL); } } @@ -5301,7 +5350,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2); if (smartlist_len(elts) == 0) - smartlist_add(elts, tor_strdup("stdout")); + smartlist_add_strdup(elts, "stdout"); if (smartlist_len(elts) == 1 && (!strcasecmp(smartlist_get(elts,0), "stdout") || @@ -5836,7 +5885,7 @@ get_options_from_transport_options_line(const char *line,const char *transport) } /* add it to the options smartlist */ - smartlist_add(options, tor_strdup(option)); + smartlist_add_strdup(options, option); log_debug(LD_CONFIG, "Added %s to the list of options", escaped(option)); } SMARTLIST_FOREACH_END(option); diff --git a/src/or/confparse.c b/src/or/confparse.c index efcf4f981e..1706fa85e2 100644 --- a/src/or/confparse.c +++ b/src/or/confparse.c @@ -1,3 +1,4 @@ + /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. @@ -9,6 +10,16 @@ * * \brief Back-end for parsing and generating key-value files, used to * implement the torrc file format and the state file. + * + * This module is used by config.c to parse and encode torrc + * configuration files, and by statefile.c to parse and encode the + * $DATADIR/state file. + * + * To use this module, its callers provide an instance of + * config_format_t to describe the mappings from a set of configuration + * options to a number of fields in a C structure. With this mapping, + * the functions here can convert back and forth between the C structure + * specified, and a linked list of key-value pairs. */ #include "or.h" @@ -1213,6 +1224,8 @@ static struct unit_table_t memory_units[] = { { "gbits", 1<<27 }, { "gbit", 1<<27 }, { "tb", U64_LITERAL(1)<<40 }, + { "tbyte", U64_LITERAL(1)<<40 }, + { "tbytes", U64_LITERAL(1)<<40 }, { "terabyte", U64_LITERAL(1)<<40 }, { "terabytes", U64_LITERAL(1)<<40 }, { "terabits", U64_LITERAL(1)<<37 }, diff --git a/src/or/connection.c b/src/or/connection.c index d30ec46357..bdf14bb2fc 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -82,6 +82,7 @@ #include "ext_orport.h" #include "geoip.h" #include "main.h" +#include "hs_common.h" #include "nodelist.h" #include "policies.h" #include "reasons.h" @@ -4129,12 +4130,12 @@ connection_get_by_type_state_rendquery(int type, int state, (type == CONN_TYPE_DIR && TO_DIR_CONN(conn)->rend_data && !rend_cmp_service_ids(rendquery, - TO_DIR_CONN(conn)->rend_data->onion_address)) + rend_data_get_address(TO_DIR_CONN(conn)->rend_data))) || (CONN_IS_EDGE(conn) && TO_EDGE_CONN(conn)->rend_data && !rend_cmp_service_ids(rendquery, - TO_EDGE_CONN(conn)->rend_data->onion_address)) + rend_data_get_address(TO_EDGE_CONN(conn)->rend_data))) )); } diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 7b9c315a11..3874d52c23 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -75,6 +75,7 @@ #include "directory.h" #include "dirserv.h" #include "hibernate.h" +#include "hs_common.h" #include "main.h" #include "nodelist.h" #include "policies.h" @@ -830,7 +831,8 @@ connection_ap_rescan_and_attach_pending(void) #endif /** Tell any AP streams that are listed as waiting for a new circuit to try - * again, either attaching to an available circ or launching a new one. + * again. If there is an available circuit for a stream, attach it. Otherwise, + * launch a new circuit. * * If <b>retry</b> is false, only check the list if it contains at least one * streams that we have not yet tried to attach to a circuit. @@ -845,8 +847,9 @@ connection_ap_attach_pending(int retry) if (untried_pending_connections == 0 && !retry) return; - /* Don't allow modifications to pending_entry_connections while we are - * iterating over it. */ + /* Don't allow any modifications to list while we are iterating over + * it. We'll put streams back on this list if we can't attach them + * immediately. */ smartlist_t *pending = pending_entry_connections; pending_entry_connections = smartlist_new(); @@ -873,6 +876,7 @@ connection_ap_attach_pending(int retry) continue; } + /* Okay, we're through the sanity checks. Try to handle this stream. */ if (connection_ap_handshake_attach_circuit(entry_conn) < 0) { if (!conn->marked_for_close) connection_mark_unattached_ap(entry_conn, @@ -882,12 +886,17 @@ connection_ap_attach_pending(int retry) if (! conn->marked_for_close && conn->type == CONN_TYPE_AP && conn->state == AP_CONN_STATE_CIRCUIT_WAIT) { + /* Is it still waiting for a circuit? If so, we didn't attach it, + * so it's still pending. Put it back on the list. + */ if (!smartlist_contains(pending_entry_connections, entry_conn)) { smartlist_add(pending_entry_connections, entry_conn); continue; } } + /* If we got here, then we either closed the connection, or + * we attached it. */ UNMARK(); } SMARTLIST_FOREACH_END(entry_conn); @@ -1195,6 +1204,8 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, /* Remember the original address so we can tell the user about what * they actually said, not just what it turned into. */ + /* XXX yes, this is the same as out->orig_address above. One is + * in the output, and one is in the connection. */ if (! conn->original_dest_address) { /* Is the 'if' necessary here? XXXX */ conn->original_dest_address = tor_strdup(conn->socks_request->address); @@ -1202,7 +1213,7 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, /* First, apply MapAddress and MAPADDRESS mappings. We need to do * these only for non-reverse lookups, since they don't exist for those. - * We need to do this before we consider automapping, since we might + * We also need to do this before we consider automapping, since we might * e.g. resolve irc.oftc.net into irconionaddress.onion, at which point * we'd need to automap it. */ if (socks->command != SOCKS_COMMAND_RESOLVE_PTR) { @@ -1214,9 +1225,12 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } - /* Now, handle automapping. Automapping happens when we're asked to - * resolve a hostname, and AutomapHostsOnResolve is set, and - * the hostname has a suffix listed in AutomapHostsSuffixes. + /* Now see if we need to create or return an existing Hostname->IP + * automapping. Automapping happens when we're asked to resolve a + * hostname, and AutomapHostsOnResolve is set, and the hostname has a + * suffix listed in AutomapHostsSuffixes. It's a handy feature + * that lets you have Tor assign e.g. IPv6 addresses for .onion + * names, and return them safely from DNSPort. */ if (socks->command == SOCKS_COMMAND_RESOLVE && tor_addr_parse(&addr_tmp, socks->address)<0 && @@ -1256,7 +1270,8 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } /* Now handle reverse lookups, if they're in the cache. This doesn't - * happen too often, since client-side DNS caching is off by default. */ + * happen too often, since client-side DNS caching is off by default, + * and very deprecated. */ if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) { unsigned rewrite_flags = 0; if (conn->entry_cfg.use_cached_ipv4_answers) @@ -1301,11 +1316,12 @@ connection_ap_handshake_rewrite(entry_connection_t *conn, } } - /* If we didn't automap it before, then this is still the address - * that came straight from the user, mapped according to any - * MapAddress/MAPADDRESS commands. Now other mappings, including - * previously registered Automap entries, TrackHostExits entries, - * and client-side DNS cache entries (not recommended). + /* If we didn't automap it before, then this is still the address that + * came straight from the user, mapped according to any + * MapAddress/MAPADDRESS commands. Now apply other mappings, + * including previously registered Automap entries (IP back to + * hostname), TrackHostExits entries, and client-side DNS cache + * entries (if they're turned on). */ if (socks->command != SOCKS_COMMAND_RESOLVE_PTR && !out->automap) { @@ -1370,11 +1386,14 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, time_t now = time(NULL); rewrite_result_t rr; + /* First we'll do the rewrite part. Let's see if we get a reasonable + * answer. + */ memset(&rr, 0, sizeof(rr)); connection_ap_handshake_rewrite(conn,&rr); if (rr.should_close) { - /* connection_ap_handshake_rewrite told us to close the connection, + /* connection_ap_handshake_rewrite told us to close the connection: * either because it sent back an answer, or because it sent back an * error */ connection_mark_unattached_ap(conn, rr.end_reason); @@ -1388,8 +1407,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, const int automap = rr.automap; const addressmap_entry_source_t exit_source = rr.exit_source; - /* Parse the address provided by SOCKS. Modify it in-place if it - * specifies a hidden-service (.onion) or particular exit node (.exit). + /* Now, we parse the address to see if it's an .onion or .exit or + * other special address. */ const hostname_type_t addresstype = parse_extended_hostname(socks->address); @@ -1403,8 +1422,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* If this is a .exit hostname, strip off the .name.exit part, and - * see whether we're going to connect there, and otherwise handle it. - * (The ".exit" part got stripped off by "parse_extended_hostname"). + * see whether we're willing to connect there, and and otherwise handle the + * .exit address. * * We'll set chosen_exit_name and/or close the connection as appropriate. */ @@ -1416,7 +1435,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, const node_t *node = NULL; /* If this .exit was added by an AUTOMAP, then it came straight from - * a user. Make sure that options->AllowDotExit permits that. */ + * a user. Make sure that options->AllowDotExit permits that! */ if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) { /* Whoops; this one is stale. It must have gotten added earlier, * when AllowDotExit was on. */ @@ -1445,7 +1464,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } tor_assert(!automap); - /* Now, find the character before the .(name) part. */ + + /* Now, find the character before the .(name) part. + * (The ".exit" part got stripped off by "parse_extended_hostname"). + * + * We're going to put the exit name into conn->chosen_exit_name, and + * look up a node correspondingly. */ char *s = strrchr(socks->address,'.'); if (s) { /* The address was of the form "(stuff).(name).exit */ @@ -1501,10 +1525,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, implies no. */ } - /* Now, handle everything that isn't a .onion address. */ + /* Now, we handle everything that isn't a .onion address. */ if (addresstype != ONION_HOSTNAME) { /* Not a hidden-service request. It's either a hostname or an IP, - * possibly with a .exit that we stripped off. */ + * possibly with a .exit that we stripped off. We're going to check + * if we're allowed to connect/resolve there, and then launch the + * appropriate request. */ /* Check for funny characters in the address. */ if (address_is_invalid_destination(socks->address, 1)) { @@ -1551,30 +1577,37 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* Then check if we have a hostname or IP address, and whether DNS or - * the IP address family are permitted */ + * the IP address family are permitted. Reject if not. */ tor_addr_t dummy_addr; int socks_family = tor_addr_parse(&dummy_addr, socks->address); /* family will be -1 for a non-onion hostname that's not an IP */ - if (socks_family == -1 && !conn->entry_cfg.dns_request) { - log_warn(LD_APP, "Refusing to connect to hostname %s " - "because Port has NoDNSRequest set.", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } else if (socks_family == AF_INET && !conn->entry_cfg.ipv4_traffic) { - log_warn(LD_APP, "Refusing to connect to IPv4 address %s because " - "Port has NoIPv4Traffic set.", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; - } else if (socks_family == AF_INET6 && !conn->entry_cfg.ipv6_traffic) { - log_warn(LD_APP, "Refusing to connect to IPv6 address %s because " - "Port has NoIPv6Traffic set.", - safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); - return -1; + if (socks_family == -1) { + if (!conn->entry_cfg.dns_request) { + log_warn(LD_APP, "Refusing to connect to hostname %s " + "because Port has NoDNSRequest set.", + safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + } else if (socks_family == AF_INET) { + if (!conn->entry_cfg.ipv4_traffic) { + log_warn(LD_APP, "Refusing to connect to IPv4 address %s because " + "Port has NoIPv4Traffic set.", + safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + } else if (socks_family == AF_INET6) { + if (!conn->entry_cfg.ipv6_traffic) { + log_warn(LD_APP, "Refusing to connect to IPv6 address %s because " + "Port has NoIPv6Traffic set.", + safe_str_client(socks->address)); + connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); + return -1; + } + } else { + tor_assert_nonfatal_unreached_once(); } - /* No else, we've covered all possible returned value. */ /* See if this is a hostname lookup that we can answer immediately. * (For example, an attempt to look up the IP address for an IP address.) @@ -1595,7 +1628,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, tor_assert(!automap); rep_hist_note_used_resolve(now); /* help predict this next time */ } else if (socks->command == SOCKS_COMMAND_CONNECT) { - /* Special handling for attempts to connect */ + /* Now see if this is a connect request that we can reject immediately */ + tor_assert(!automap); /* Don't allow connections to port 0. */ if (socks->port == 0) { @@ -1648,7 +1682,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } /* end "if we should check for internal addresses" */ /* Okay. We're still doing a CONNECT, and it wasn't a private - * address. Do special handling for literal IP addresses */ + * address. Here we do special handling for literal IP addresses, + * to see if we should reject this preemptively, and to set up + * fields in conn->entry_cfg to tell the exit what AF we want. */ { tor_addr_t addr; /* XXX Duplicate call to tor_addr_parse. */ @@ -1691,11 +1727,15 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } + /* we never allow IPv6 answers on socks4. (TODO: Is this smart?) */ if (socks->socks_version == 4) conn->entry_cfg.ipv6_traffic = 0; /* Still handling CONNECT. Now, check for exit enclaves. (Which we - * don't do on BEGINDIR, or there is a chosen exit.) + * don't do on BEGINDIR, or when there is a chosen exit.) + * + * TODO: Should we remove this? Exit enclaves are nutty and don't + * work very well */ if (!conn->use_begindir && !conn->chosen_exit_name && !circ) { /* see if we can find a suitable enclave exit */ @@ -1719,7 +1759,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (consider_plaintext_ports(conn, socks->port) < 0) return -1; - /* Remember the port so that we do predicted requests there. */ + /* Remember the port so that we will predict that more requests + there will happen in the future. */ if (!conn->use_begindir) { /* help predict this next time */ rep_hist_note_used_port(now, socks->port); @@ -1728,7 +1769,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, rep_hist_note_used_resolve(now); /* help predict this next time */ /* no extra processing needed */ } else { - /* We should only be doing CONNECT or RESOLVE! */ + /* We should only be doing CONNECT, RESOLVE, or RESOLVE_PTR! */ tor_fragile_assert(); } @@ -1744,6 +1785,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (circ) { rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath); } else { + /* We'll try to attach it at the next event loop, or whenever + * we call connection_ap_attach_pending() */ connection_ap_mark_as_pending_circuit(conn); rv = 0; } @@ -1817,24 +1860,26 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (rend_data == NULL) { return -1; } + const char *onion_address = rend_data_get_address(rend_data); log_info(LD_REND,"Got a hidden service request for ID '%s'", - safe_str_client(rend_data->onion_address)); + safe_str_client(onion_address)); - /* Lookup the given onion address. If invalid, stop right now else we - * might have it in the cache or not, it will be tested later on. */ + /* Lookup the given onion address. If invalid, stop right now. + * Otherwise, we might have it in the cache or not. */ unsigned int refetch_desc = 0; rend_cache_entry_t *entry = NULL; const int rend_cache_lookup_result = - rend_cache_lookup_entry(rend_data->onion_address, -1, &entry); + rend_cache_lookup_entry(onion_address, -1, &entry); if (rend_cache_lookup_result < 0) { switch (-rend_cache_lookup_result) { case EINVAL: /* We should already have rejected this address! */ log_warn(LD_BUG,"Invalid service name '%s'", - safe_str_client(rend_data->onion_address)); + safe_str_client(onion_address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return -1; case ENOENT: + /* We didn't have this; we should look it up. */ refetch_desc = 1; break; default: @@ -1844,8 +1889,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } - /* Help predict this next time. We're not sure if it will need - * a stable circuit yet, but we know we'll need *something*. */ + /* Help predict that we'll want to do hidden service circuits in the + * future. We're not sure if it will need a stable circuit yet, but + * we know we'll need *something*. */ rep_hist_note_used_internal(now, 0, 1); /* Now we have a descriptor but is it usable or not? If not, refetch. @@ -1855,14 +1901,17 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_ap_mark_as_non_pending_circuit(conn); base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; log_info(LD_REND, "Unknown descriptor %s. Fetching.", - safe_str_client(rend_data->onion_address)); + safe_str_client(onion_address)); rend_client_refetch_v2_renddesc(rend_data); return 0; } - /* We have the descriptor so launch a connection to the HS. */ + /* We have the descriptor! So launch a connection to the HS. */ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; log_info(LD_REND, "Descriptor is here. Great."); + + /* We'll try to attach it at the next event loop, or whenever + * we call connection_ap_attach_pending() */ connection_ap_mark_as_pending_circuit(conn); return 0; } @@ -2443,7 +2492,9 @@ connection_ap_handshake_send_begin(entry_connection_t *ap_conn) * Otherwise, directory connections are typically one-hop. * This matches the earlier check for directory connection path anonymity * in directory_initiate_command_rend(). */ - if (is_sensitive_dir_purpose(linked_dir_conn_base->purpose)) { + if (purpose_needs_anonymity(linked_dir_conn_base->purpose, + TO_DIR_CONN(linked_dir_conn_base)->router_purpose, + TO_DIR_CONN(linked_dir_conn_base)->requested_resource)) { assert_circ_anonymity_ok(circ, options); } } else { diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 267c32dda4..eb67f0653f 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -49,9 +49,11 @@ #include "relay.h" #include "rephist.h" #include "router.h" +#include "routerkeys.h" #include "routerlist.h" #include "ext_orport.h" #include "scheduler.h" +#include "torcert.h" static int connection_tls_finish_handshake(or_connection_t *conn); static int connection_or_launch_v3_or_handshake(or_connection_t *conn); @@ -143,15 +145,18 @@ connection_or_clear_identity_map(void) /** Change conn->identity_digest to digest, and add conn into * orconn_digest_map. */ static void -connection_or_set_identity_digest(or_connection_t *conn, const char *digest) +connection_or_set_identity_digest(or_connection_t *conn, + const char *rsa_digest, + const ed25519_public_key_t *ed_id) { + (void) ed_id; // DOCDOC // XXXX not implemented yet. or_connection_t *tmp; tor_assert(conn); - tor_assert(digest); + tor_assert(rsa_digest); if (!orconn_identity_map) orconn_identity_map = digestmap_new(); - if (tor_memeq(conn->identity_digest, digest, DIGEST_LEN)) + if (tor_memeq(conn->identity_digest, rsa_digest, DIGEST_LEN)) return; /* If the identity was set previously, remove the old mapping. */ @@ -161,23 +166,23 @@ connection_or_set_identity_digest(or_connection_t *conn, const char *digest) channel_clear_identity_digest(TLS_CHAN_TO_BASE(conn->chan)); } - memcpy(conn->identity_digest, digest, DIGEST_LEN); + memcpy(conn->identity_digest, rsa_digest, DIGEST_LEN); /* If we're setting the ID to zero, don't add a mapping. */ - if (tor_digest_is_zero(digest)) + if (tor_digest_is_zero(rsa_digest)) return; - tmp = digestmap_set(orconn_identity_map, digest, conn); + tmp = digestmap_set(orconn_identity_map, rsa_digest, conn); conn->next_with_same_id = tmp; /* Deal with channels */ if (conn->chan) - channel_set_identity_digest(TLS_CHAN_TO_BASE(conn->chan), digest); + channel_set_identity_digest(TLS_CHAN_TO_BASE(conn->chan), rsa_digest); #if 1 /* Testing code to check for bugs in representation. */ for (; tmp; tmp = tmp->next_with_same_id) { - tor_assert(tor_memeq(tmp->identity_digest, digest, DIGEST_LEN)); + tor_assert(tor_memeq(tmp->identity_digest, rsa_digest, DIGEST_LEN)); tor_assert(tmp != conn); } #endif @@ -875,10 +880,12 @@ void connection_or_init_conn_from_address(or_connection_t *conn, const tor_addr_t *addr, uint16_t port, const char *id_digest, + const ed25519_public_key_t *ed_id, int started_here) { + (void) ed_id; // not fully used yet. const node_t *r = node_get_by_id(id_digest); - connection_or_set_identity_digest(conn, id_digest); + connection_or_set_identity_digest(conn, id_digest, ed_id); connection_or_update_token_buckets_helper(conn, 1, get_options()); conn->base_.port = port; @@ -1171,8 +1178,11 @@ connection_or_notify_error(or_connection_t *conn, MOCK_IMPL(or_connection_t *, connection_or_connect, (const tor_addr_t *_addr, uint16_t port, - const char *id_digest, channel_tls_t *chan)) + const char *id_digest, + const ed25519_public_key_t *ed_id, + channel_tls_t *chan)) { + (void) ed_id; // XXXX not fully used yet. or_connection_t *conn; const or_options_t *options = get_options(); int socket_error = 0; @@ -1203,7 +1213,7 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port, */ conn->chan = chan; chan->conn = conn; - connection_or_init_conn_from_address(conn, &addr, port, id_digest, 1); + connection_or_init_conn_from_address(conn, &addr, port, id_digest, ed_id, 1); connection_or_change_state(conn, OR_CONN_STATE_CONNECTING); control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0); @@ -1562,7 +1572,9 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, if (started_here) return connection_or_client_learned_peer_id(conn, - (const uint8_t*)digest_rcvd_out); + (const uint8_t*)digest_rcvd_out, + NULL // Ed25519 ID + ); return 0; } @@ -1592,12 +1604,16 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, */ int connection_or_client_learned_peer_id(or_connection_t *conn, - const uint8_t *peer_id) + const uint8_t *rsa_peer_id, + const ed25519_public_key_t *ed_peer_id) { + (void) ed_peer_id; // not used yet. + const or_options_t *options = get_options(); if (tor_digest_is_zero(conn->identity_digest)) { - connection_or_set_identity_digest(conn, (const char*)peer_id); + connection_or_set_identity_digest(conn, + (const char*)rsa_peer_id, ed_peer_id); tor_free(conn->nickname); conn->nickname = tor_malloc(HEX_DIGEST_LEN+2); conn->nickname[0] = '$'; @@ -1609,14 +1625,14 @@ connection_or_client_learned_peer_id(or_connection_t *conn, /* if it's a bridge and we didn't know its identity fingerprint, now * we do -- remember it for future attempts. */ learned_router_identity(&conn->base_.addr, conn->base_.port, - (const char*)peer_id); + (const char*)rsa_peer_id /*, ed_peer_id XXXX */); } - if (tor_memneq(peer_id, conn->identity_digest, DIGEST_LEN)) { + if (tor_memneq(rsa_peer_id, conn->identity_digest, DIGEST_LEN)) { /* I was aiming for a particular digest. I didn't get it! */ char seen[HEX_DIGEST_LEN+1]; char expected[HEX_DIGEST_LEN+1]; - base16_encode(seen, sizeof(seen), (const char*)peer_id, DIGEST_LEN); + base16_encode(seen, sizeof(seen), (const char*)rsa_peer_id, DIGEST_LEN); base16_encode(expected, sizeof(expected), conn->identity_digest, DIGEST_LEN); const int using_hardcoded_fingerprints = @@ -1669,7 +1685,7 @@ connection_or_client_learned_peer_id(or_connection_t *conn, } if (authdir_mode_tests_reachability(options)) { dirserv_orconn_tls_done(&conn->base_.addr, conn->base_.port, - (const char*)peer_id); + (const char*)rsa_peer_id /*, ed_id XXXX */); } return 0; @@ -1725,7 +1741,8 @@ connection_tls_finish_handshake(or_connection_t *conn) if (tor_tls_used_v1_handshake(conn->tls)) { conn->link_proto = 1; connection_or_init_conn_from_address(conn, &conn->base_.addr, - conn->base_.port, digest_rcvd, 0); + conn->base_.port, digest_rcvd, + NULL, 0); tor_tls_block_renegotiation(conn->tls); rep_hist_note_negotiated_link_proto(1, started_here); return connection_or_set_state_open(conn); @@ -1734,7 +1751,8 @@ connection_tls_finish_handshake(or_connection_t *conn) if (connection_init_or_handshake_state(conn, started_here) < 0) return -1; connection_or_init_conn_from_address(conn, &conn->base_.addr, - conn->base_.port, digest_rcvd, 0); + conn->base_.port, digest_rcvd, + NULL, 0); return connection_or_send_versions(conn, 0); } } @@ -1773,6 +1791,8 @@ connection_init_or_handshake_state(or_connection_t *conn, int started_here) s->started_here = started_here ? 1 : 0; s->digest_sent_data = 1; s->digest_received_data = 1; + s->certs = or_handshake_certs_new(); + s->certs->started_here = s->started_here; return 0; } @@ -1784,8 +1804,7 @@ or_handshake_state_free(or_handshake_state_t *state) return; crypto_digest_free(state->digest_sent); crypto_digest_free(state->digest_received); - tor_x509_cert_free(state->auth_cert); - tor_x509_cert_free(state->id_cert); + or_handshake_certs_free(state->certs); memwipe(state, 0xBE, sizeof(or_handshake_state_t)); tor_free(state); } @@ -2132,57 +2151,171 @@ connection_or_send_netinfo,(or_connection_t *conn)) return 0; } +/** Helper used to add an encoded certs to a cert cell */ +static void +add_certs_cell_cert_helper(certs_cell_t *certs_cell, + uint8_t cert_type, + const uint8_t *cert_encoded, + size_t cert_len) +{ + tor_assert(cert_len <= UINT16_MAX); + certs_cell_cert_t *ccc = certs_cell_cert_new(); + ccc->cert_type = cert_type; + ccc->cert_len = cert_len; + certs_cell_cert_setlen_body(ccc, cert_len); + memcpy(certs_cell_cert_getarray_body(ccc), cert_encoded, cert_len); + + certs_cell_add_certs(certs_cell, ccc); +} + +/** Add an encoded X509 cert (stored as <b>cert_len</b> bytes at + * <b>cert_encoded</b>) to the trunnel certs_cell_t object that we are + * building in <b>certs_cell</b>. Set its type field to <b>cert_type</b>. */ +static void +add_x509_cert(certs_cell_t *certs_cell, + uint8_t cert_type, + const tor_x509_cert_t *cert) +{ + if (NULL == cert) + return; + + const uint8_t *cert_encoded = NULL; + size_t cert_len; + tor_x509_cert_get_der(cert, &cert_encoded, &cert_len); + + add_certs_cell_cert_helper(certs_cell, cert_type, cert_encoded, cert_len); +} + +/** Add an Ed25519 cert from <b>cert</b> to the trunnel certs_cell_t object + * that we are building in <b>certs_cell</b>. Set its type field to + * <b>cert_type</b>. */ +static void +add_ed25519_cert(certs_cell_t *certs_cell, + uint8_t cert_type, + const tor_cert_t *cert) +{ + if (NULL == cert) + return; + + add_certs_cell_cert_helper(certs_cell, cert_type, + cert->encoded, cert->encoded_len); +} + /** Send a CERTS cell on the connection <b>conn</b>. Return 0 on success, -1 * on failure. */ int connection_or_send_certs_cell(or_connection_t *conn) { const tor_x509_cert_t *link_cert = NULL, *id_cert = NULL; - const uint8_t *link_encoded = NULL, *id_encoded = NULL; - size_t link_len, id_len; var_cell_t *cell; - size_t cell_len; - ssize_t pos; + + certs_cell_t *certs_cell = NULL; tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3); if (! conn->handshake_state) return -1; + const int conn_in_server_mode = ! conn->handshake_state->started_here; + + /* Get the encoded values of the X509 certificates */ if (tor_tls_get_my_certs(conn_in_server_mode, &link_cert, &id_cert) < 0) return -1; - tor_x509_cert_get_der(link_cert, &link_encoded, &link_len); - tor_x509_cert_get_der(id_cert, &id_encoded, &id_len); - cell_len = 1 /* 1 byte: num certs in cell */ + - 2 * ( 1 + 2 ) /* For each cert: 1 byte for type, 2 for length */ + - link_len + id_len; - cell = var_cell_new(cell_len); - cell->command = CELL_CERTS; - cell->payload[0] = 2; - pos = 1; + tor_assert(link_cert); + tor_assert(id_cert); - if (conn_in_server_mode) - cell->payload[pos] = OR_CERT_TYPE_TLS_LINK; /* Link cert */ - else - cell->payload[pos] = OR_CERT_TYPE_AUTH_1024; /* client authentication */ - set_uint16(&cell->payload[pos+1], htons(link_len)); - memcpy(&cell->payload[pos+3], link_encoded, link_len); - pos += 3 + link_len; + certs_cell = certs_cell_new(); - cell->payload[pos] = OR_CERT_TYPE_ID_1024; /* ID cert */ - set_uint16(&cell->payload[pos+1], htons(id_len)); - memcpy(&cell->payload[pos+3], id_encoded, id_len); - pos += 3 + id_len; + /* Start adding certs. First the link cert or auth1024 cert. */ + if (conn_in_server_mode) { + add_x509_cert(certs_cell, + OR_CERT_TYPE_TLS_LINK, link_cert); + } else { + add_x509_cert(certs_cell, + OR_CERT_TYPE_AUTH_1024, link_cert); + } - tor_assert(pos == (int)cell_len); /* Otherwise we just smashed the heap */ + /* Next the RSA->RSA ID cert */ + add_x509_cert(certs_cell, + OR_CERT_TYPE_ID_1024, id_cert); + + /* Next the Ed25519 certs */ + add_ed25519_cert(certs_cell, + CERTTYPE_ED_ID_SIGN, + get_master_signing_key_cert()); + if (conn_in_server_mode) { + add_ed25519_cert(certs_cell, + CERTTYPE_ED_SIGN_LINK, + get_current_link_cert_cert()); + } else { + add_ed25519_cert(certs_cell, + CERTTYPE_ED_SIGN_AUTH, + get_current_auth_key_cert()); + } + + /* And finally the crosscert. */ + { + const uint8_t *crosscert=NULL; + size_t crosscert_len; + get_master_rsa_crosscert(&crosscert, &crosscert_len); + if (crosscert) { + add_certs_cell_cert_helper(certs_cell, + CERTTYPE_RSA1024_ID_EDID, + crosscert, crosscert_len); + } + } + + /* We've added all the certs; make the cell. */ + certs_cell->n_certs = certs_cell_getlen_certs(certs_cell); + + ssize_t alloc_len = certs_cell_encoded_len(certs_cell); + tor_assert(alloc_len >= 0 && alloc_len <= UINT16_MAX); + cell = var_cell_new(alloc_len); + cell->command = CELL_CERTS; + ssize_t enc_len = certs_cell_encode(cell->payload, alloc_len, certs_cell); + tor_assert(enc_len > 0 && enc_len <= alloc_len); + cell->payload_len = enc_len; connection_or_write_var_cell_to_buf(cell, conn); var_cell_free(cell); + certs_cell_free(certs_cell); return 0; } +/** Return true iff <b>challenge_type</b> is an AUTHCHALLENGE type that + * we can send and receive. */ +int +authchallenge_type_is_supported(uint16_t challenge_type) +{ + switch (challenge_type) { + case AUTHTYPE_RSA_SHA256_TLSSECRET: + case AUTHTYPE_ED25519_SHA256_RFC5705: + return 1; + case AUTHTYPE_RSA_SHA256_RFC5705: + default: + return 0; + } +} + +/** Return true iff <b>challenge_type_a</b> is one that we would rather + * use than <b>challenge_type_b</b>. */ +int +authchallenge_type_is_better(uint16_t challenge_type_a, + uint16_t challenge_type_b) +{ + /* Any supported type is better than an unsupported one; + * all unsupported types are equally bad. */ + if (!authchallenge_type_is_supported(challenge_type_a)) + return 0; + if (!authchallenge_type_is_supported(challenge_type_b)) + return 1; + /* It happens that types are superior in numerically ascending order. + * If that ever changes, this must change too. */ + return (challenge_type_a > challenge_type_b); +} + /** Send an AUTH_CHALLENGE cell on the connection <b>conn</b>. Return 0 * on success, -1 on failure. */ int @@ -2197,17 +2330,26 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn) auth_challenge_cell_t *ac = auth_challenge_cell_new(); + tor_assert(sizeof(ac->challenge) == 32); crypto_rand((char*)ac->challenge, sizeof(ac->challenge)); auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET); + /* Disabled, because everything that supports this method also supports + * the much-superior ED25519_SHA256_RFC5705 */ + /* auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_RFC5705); */ + auth_challenge_cell_add_methods(ac, AUTHTYPE_ED25519_SHA256_RFC5705); auth_challenge_cell_set_n_methods(ac, auth_challenge_cell_getlen_methods(ac)); cell = var_cell_new(auth_challenge_cell_encoded_len(ac)); ssize_t len = auth_challenge_cell_encode(cell->payload, cell->payload_len, ac); - if (len != cell->payload_len) + if (len != cell->payload_len) { + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Encoded auth challenge cell length not as expected"); goto done; + /* LCOV_EXCL_STOP */ + } cell->command = CELL_AUTH_CHALLENGE; connection_or_write_var_cell_to_buf(cell, conn); @@ -2221,8 +2363,8 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn) } /** Compute the main body of an AUTHENTICATE cell that a client can use - * to authenticate itself on a v3 handshake for <b>conn</b>. Write it to the - * <b>outlen</b>-byte buffer at <b>out</b>. + * to authenticate itself on a v3 handshake for <b>conn</b>. Return it + * in a var_cell_t. * * If <b>server</b> is true, only calculate the first * V3_AUTH_FIXED_PART_LEN bytes -- the part of the authenticator that's @@ -2238,24 +2380,44 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn) * * Return the length of the cell body on success, and -1 on failure. */ -int +var_cell_t * connection_or_compute_authenticate_cell_body(or_connection_t *conn, - uint8_t *out, size_t outlen, + const int authtype, crypto_pk_t *signing_key, - int server) + const ed25519_keypair_t *ed_signing_key, + int server) { auth1_t *auth = NULL; auth_ctx_t *ctx = auth_ctx_new(); - int result; + var_cell_t *result = NULL; + int old_tlssecrets_algorithm = 0; + const char *authtype_str = NULL; - /* assert state is reasonable XXXX */ + int is_ed = 0; - ctx->is_ed = 0; + /* assert state is reasonable XXXX */ + switch (authtype) { + case AUTHTYPE_RSA_SHA256_TLSSECRET: + authtype_str = "AUTH0001"; + old_tlssecrets_algorithm = 1; + break; + case AUTHTYPE_RSA_SHA256_RFC5705: + authtype_str = "AUTH0002"; + break; + case AUTHTYPE_ED25519_SHA256_RFC5705: + authtype_str = "AUTH0003"; + is_ed = 1; + break; + default: + tor_assert(0); + break; + } auth = auth1_new(); + ctx->is_ed = is_ed; /* Type: 8 bytes. */ - memcpy(auth1_getarray_type(auth), "AUTH0001", 8); + memcpy(auth1_getarray_type(auth), authtype_str, 8); { const tor_x509_cert_t *id_cert=NULL, *link_cert=NULL; @@ -2265,7 +2427,7 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, goto err; my_digests = tor_x509_cert_get_id_digests(id_cert); their_digests = - tor_x509_cert_get_id_digests(conn->handshake_state->id_cert); + tor_x509_cert_get_id_digests(conn->handshake_state->certs->id_cert); tor_assert(my_digests); tor_assert(their_digests); my_id = (uint8_t*)my_digests->d[DIGEST_SHA256]; @@ -2281,6 +2443,22 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, memcpy(auth->sid, server_id, 32); } + if (is_ed) { + const ed25519_public_key_t *my_ed_id, *their_ed_id; + if (!conn->handshake_state->certs->ed_id_sign) { + log_warn(LD_OR, "Ed authenticate without Ed ID cert from peer."); + goto err; + } + my_ed_id = get_master_identity_key(); + their_ed_id = &conn->handshake_state->certs->ed_id_sign->signing_key; + + const uint8_t *cid_ed = (server ? their_ed_id : my_ed_id)->pubkey; + const uint8_t *sid_ed = (server ? my_ed_id : their_ed_id)->pubkey; + + memcpy(auth->u1_cid_ed, cid_ed, ED25519_PUBKEY_LEN); + memcpy(auth->u1_sid_ed, sid_ed, ED25519_PUBKEY_LEN); + } + { crypto_digest_t *server_d, *client_d; if (server) { @@ -2309,7 +2487,8 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, cert = freecert; } if (!cert) { - log_warn(LD_OR, "Unable to find cert when making AUTH1 data."); + log_warn(LD_OR, "Unable to find cert when making %s data.", + authtype_str); goto err; } @@ -2321,36 +2500,79 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, } /* HMAC of clientrandom and serverrandom using master key : 32 octets */ - tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets); + if (old_tlssecrets_algorithm) { + tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets); + } else { + char label[128]; + tor_snprintf(label, sizeof(label), + "EXPORTER FOR TOR TLS CLIENT BINDING %s", authtype_str); + tor_tls_export_key_material(conn->tls, auth->tlssecrets, + auth->cid, sizeof(auth->cid), + label); + } /* 8 octets were reserved for the current time, but we're trying to get out * of the habit of sending time around willynilly. Fortunately, nothing * checks it. That's followed by 16 bytes of nonce. */ crypto_rand((char*)auth->rand, 24); + ssize_t maxlen = auth1_encoded_len(auth, ctx); + if (ed_signing_key && is_ed) { + maxlen += ED25519_SIG_LEN; + } else if (signing_key && !is_ed) { + maxlen += crypto_pk_keysize(signing_key); + } + + const int AUTH_CELL_HEADER_LEN = 4; /* 2 bytes of type, 2 bytes of length */ + result = var_cell_new(AUTH_CELL_HEADER_LEN + maxlen); + uint8_t *const out = result->payload + AUTH_CELL_HEADER_LEN; + const size_t outlen = maxlen; ssize_t len; + + result->command = CELL_AUTHENTICATE; + set_uint16(result->payload, htons(authtype)); + if ((len = auth1_encode(out, outlen, auth, ctx)) < 0) { - log_warn(LD_OR, "Unable to encode signed part of AUTH1 data."); + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Unable to encode signed part of AUTH1 data."); goto err; + /* LCOV_EXCL_STOP */ } if (server) { auth1_t *tmp = NULL; ssize_t len2 = auth1_parse(&tmp, out, len, ctx); if (!tmp) { - log_warn(LD_OR, "Unable to parse signed part of AUTH1 data."); + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Unable to parse signed part of AUTH1 data that " + "we just encoded"); goto err; + /* LCOV_EXCL_STOP */ } - result = (int) (tmp->end_of_fixed_part - out); + result->payload_len = (tmp->end_of_signed - result->payload); + auth1_free(tmp); if (len2 != len) { - log_warn(LD_OR, "Mismatched length when re-parsing AUTH1 data."); + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Mismatched length when re-parsing AUTH1 data."); goto err; + /* LCOV_EXCL_STOP */ } goto done; } - if (signing_key) { + if (ed_signing_key && is_ed) { + ed25519_signature_t sig; + if (ed25519_sign(&sig, out, len, ed_signing_key) < 0) { + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Unable to sign ed25519 authentication data"); + goto err; + /* LCOV_EXCL_STOP */ + } + auth1_setlen_sig(auth, ED25519_SIG_LEN); + memcpy(auth1_getarray_sig(auth), sig.sig, ED25519_SIG_LEN); + + } else if (signing_key && !is_ed) { auth1_setlen_sig(auth, crypto_pk_keysize(signing_key)); char d[32]; @@ -2365,18 +2587,24 @@ connection_or_compute_authenticate_cell_body(or_connection_t *conn, } auth1_setlen_sig(auth, siglen); + } - len = auth1_encode(out, outlen, auth, ctx); - if (len < 0) { - log_warn(LD_OR, "Unable to encode signed AUTH1 data."); - goto err; - } + len = auth1_encode(out, outlen, auth, ctx); + if (len < 0) { + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Unable to encode signed AUTH1 data."); + goto err; + /* LCOV_EXCL_STOP */ } - result = (int) len; + tor_assert(len + AUTH_CELL_HEADER_LEN <= result->payload_len); + result->payload_len = len + AUTH_CELL_HEADER_LEN; + set_uint16(result->payload+2, htons(len)); + goto done; err: - result = -1; + var_cell_free(result); + result = NULL; done: auth1_free(auth); auth_ctx_free(ctx); @@ -2390,44 +2618,29 @@ connection_or_send_authenticate_cell,(or_connection_t *conn, int authtype)) { var_cell_t *cell; crypto_pk_t *pk = tor_tls_get_my_client_auth_key(); - int authlen; - size_t cell_maxlen; /* XXXX make sure we're actually supposed to send this! */ if (!pk) { log_warn(LD_BUG, "Can't compute authenticate cell: no client auth key"); return -1; } - if (authtype != AUTHTYPE_RSA_SHA256_TLSSECRET) { + if (! authchallenge_type_is_supported(authtype)) { log_warn(LD_BUG, "Tried to send authenticate cell with unknown " "authentication type %d", authtype); return -1; } - cell_maxlen = 4 + /* overhead */ - V3_AUTH_BODY_LEN + /* Authentication body */ - crypto_pk_keysize(pk) + /* Max signature length */ - 16 /* add a few extra bytes just in case. */; - - cell = var_cell_new(cell_maxlen); - cell->command = CELL_AUTHENTICATE; - set_uint16(cell->payload, htons(AUTHTYPE_RSA_SHA256_TLSSECRET)); - /* skip over length ; we don't know that yet. */ - - authlen = connection_or_compute_authenticate_cell_body(conn, - cell->payload+4, - cell_maxlen-4, - pk, - 0 /* not server */); - if (authlen < 0) { + cell = connection_or_compute_authenticate_cell_body(conn, + authtype, + pk, + get_current_auth_keypair(), + 0 /* not server */); + if (! cell) { + /* LCOV_EXCL_START */ log_warn(LD_BUG, "Unable to compute authenticate cell!"); - var_cell_free(cell); return -1; + /* LCOV_EXCL_STOP */ } - tor_assert(authlen + 4 <= cell->payload_len); - set_uint16(cell->payload+2, htons(authlen)); - cell->payload_len = authlen + 4; - connection_or_write_var_cell_to_buf(cell, conn); var_cell_free(cell); diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 2e8c6066cc..da95718ac9 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -40,7 +40,9 @@ void connection_or_notify_error(or_connection_t *conn, MOCK_DECL(or_connection_t *, connection_or_connect, (const tor_addr_t *addr, uint16_t port, - const char *id_digest, channel_tls_t *chan)); + const char *id_digest, + const ed25519_public_key_t *ed_id, + channel_tls_t *chan)); void connection_or_close_normally(or_connection_t *orconn, int flush); MOCK_DECL(void,connection_or_close_for_error, @@ -59,10 +61,12 @@ int connection_init_or_handshake_state(or_connection_t *conn, void connection_or_init_conn_from_address(or_connection_t *conn, const tor_addr_t *addr, uint16_t port, - const char *id_digest, + const char *rsa_id_digest, + const ed25519_public_key_t *ed_id, int started_here); int connection_or_client_learned_peer_id(or_connection_t *conn, - const uint8_t *peer_id); + const uint8_t *rsa_peer_id, + const ed25519_public_key_t *ed_peer_id); time_t connection_or_client_used(or_connection_t *conn); MOCK_DECL(int, connection_or_get_num_circuits, (or_connection_t *conn)); void or_handshake_state_free(or_handshake_state_t *state); @@ -84,10 +88,14 @@ int connection_or_send_versions(or_connection_t *conn, int v3_plus); MOCK_DECL(int,connection_or_send_netinfo,(or_connection_t *conn)); int connection_or_send_certs_cell(or_connection_t *conn); int connection_or_send_auth_challenge_cell(or_connection_t *conn); -int connection_or_compute_authenticate_cell_body(or_connection_t *conn, - uint8_t *out, size_t outlen, - crypto_pk_t *signing_key, - int server); +int authchallenge_type_is_supported(uint16_t challenge_type); +int authchallenge_type_is_better(uint16_t challenge_type_a, + uint16_t challenge_type_b); +var_cell_t *connection_or_compute_authenticate_cell_body(or_connection_t *conn, + const int authtype, + crypto_pk_t *signing_key, + const ed25519_keypair_t *ed_signing_key, + int server); MOCK_DECL(int,connection_or_send_authenticate_cell, (or_connection_t *conn, int type)); diff --git a/src/or/control.c b/src/or/control.c index c8c5062e86..a22113174a 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -57,6 +57,7 @@ #include "entrynodes.h" #include "geoip.h" #include "hibernate.h" +#include "hs_common.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -942,7 +943,7 @@ control_setconf_helper(control_connection_t *conn, uint32_t len, char *body, ++body; } - smartlist_add(entries, tor_strdup("")); + smartlist_add_strdup(entries, ""); config = smartlist_join_strings(entries, "\n", 0, NULL); SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp)); smartlist_free(entries); @@ -2028,7 +2029,7 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "dir/status/")) { *answer = tor_strdup(""); } else if (!strcmp(question, "dir/status-vote/current/consensus")) { /* v3 */ - if (directory_caches_dir_info(get_options())) { + if (we_want_to_fetch_flavor(get_options(), FLAV_NS)) { const cached_dir_t *consensus = dirserv_get_consensus("ns"); if (consensus) *answer = tor_strdup(consensus->dir); @@ -2539,7 +2540,7 @@ circuit_describe_status_for_controller(origin_circuit_t *circ) if (circ->rend_data != NULL) { smartlist_add_asprintf(descparts, "REND_QUERY=%s", - circ->rend_data->onion_address); + rend_data_get_address(circ->rend_data)); } { @@ -3139,7 +3140,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, if (!ans) { smartlist_add(unrecognized, (char*)q); } else { - smartlist_add(answers, tor_strdup(q)); + smartlist_add_strdup(answers, q); smartlist_add(answers, ans); } } SMARTLIST_FOREACH_END(q); @@ -4081,7 +4082,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, * of the id. */ desc_id = digest; } else { - connection_printf_to_buf(conn, "513 Unrecognized \"%s\"\r\n", + connection_printf_to_buf(conn, "513 Invalid argument \"%s\"\r\n", arg1); goto done; } @@ -6045,9 +6046,9 @@ control_event_networkstatus_changed_helper(smartlist_t *statuses, return 0; strs = smartlist_new(); - smartlist_add(strs, tor_strdup("650+")); - smartlist_add(strs, tor_strdup(event_string)); - smartlist_add(strs, tor_strdup("\r\n")); + smartlist_add_strdup(strs, "650+"); + smartlist_add_strdup(strs, event_string); + smartlist_add_strdup(strs, "\r\n"); SMARTLIST_FOREACH(statuses, const routerstatus_t *, rs, { s = networkstatus_getinfo_helper_single(rs); @@ -6856,8 +6857,10 @@ control_event_hs_descriptor_requested(const rend_data_t *rend_query, send_control_event(EVENT_HS_DESC, "650 HS_DESC REQUESTED %s %s %s %s\r\n", - rend_hsaddress_str_or_unknown(rend_query->onion_address), - rend_auth_type_to_string(rend_query->auth_type), + rend_hsaddress_str_or_unknown( + rend_data_get_address(rend_query)), + rend_auth_type_to_string( + TO_REND_DATA_V2(rend_query)->auth_type), node_describe_longname_by_id(id_digest), desc_id_base32); } @@ -6873,11 +6876,12 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) { int replica; const char *desc_id = NULL; + const rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data); /* Possible if the fetch was done using a descriptor ID. This means that * the HSFETCH command was used. */ - if (!tor_digest_is_zero(rend_data->desc_id_fetch)) { - desc_id = rend_data->desc_id_fetch; + if (!tor_digest_is_zero(rend_data_v2->desc_id_fetch)) { + desc_id = rend_data_v2->desc_id_fetch; goto end; } @@ -6885,7 +6889,7 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) * is the one associated with the HSDir fingerprint. */ for (replica = 0; replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS; replica++) { - const char *digest = rend_data->descriptor_id[replica]; + const char *digest = rend_data_get_desc_id(rend_data, replica, NULL); SMARTLIST_FOREACH_BEGIN(rend_data->hsdirs_fp, char *, fingerprint) { if (tor_memcmp(fingerprint, hsdir_fp, DIGEST_LEN) == 0) { @@ -6994,7 +6998,8 @@ control_event_hs_descriptor_receive_end(const char *action, "650 HS_DESC %s %s %s %s%s%s\r\n", action, rend_hsaddress_str_or_unknown(onion_address), - rend_auth_type_to_string(rend_data->auth_type), + rend_auth_type_to_string( + TO_REND_DATA_V2(rend_data)->auth_type), node_describe_longname_by_id(id_digest), desc_id_field ? desc_id_field : "", reason_field ? reason_field : ""); @@ -7091,7 +7096,7 @@ control_event_hs_descriptor_failed(const rend_data_t *rend_data, return; } control_event_hs_descriptor_receive_end("FAILED", - rend_data->onion_address, + rend_data_get_address(rend_data), rend_data, id_digest, reason); } diff --git a/src/or/directory.c b/src/or/directory.c index f4fd521929..65ddd7d583 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -3,6 +3,8 @@ * Copyright (c) 2007-2016, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#define DIRECTORY_PRIVATE + #include "or.h" #include "backtrace.h" #include "buffers.h" @@ -16,6 +18,8 @@ #include "dirvote.h" #include "entrynodes.h" #include "geoip.h" +#include "hs_cache.h" +#include "hs_common.h" #include "main.h" #include "microdesc.h" #include "networkstatus.h" @@ -40,9 +44,38 @@ /** * \file directory.c - * \brief Code to send and fetch directories and router - * descriptors via HTTP. Directories use dirserv.c to generate the - * results; clients use routers.c to parse them. + * \brief Code to send and fetch information from directory authorities and + * caches via HTTP. + * + * Directory caches and authorities use dirserv.c to generate the results of a + * query and stream them to the connection; clients use routerparse.c to parse + * them. + * + * Every directory request has a dir_connection_t on the client side and on + * the server side. In most cases, the dir_connection_t object is a linked + * connection, tunneled through an edge_connection_t so that it can be a + * stream on the Tor network. The only non-tunneled connections are those + * that are used to upload material (descriptors and votes) to authorities. + * Among tunneled connections, some use one-hop circuits, and others use + * multi-hop circuits for anonymity. + * + * Directory requests are launched by calling + * directory_initiate_command_rend() or one of its numerous variants. This + * launch the connection, will construct an HTTP request with + * directory_send_command(), send the and wait for a response. The client + * later handles the response with connection_dir_client_reached_eof(), + * which passes the information received to another part of Tor. + * + * On the server side, requests are read in directory_handle_command(), + * which dispatches first on the request type (GET or POST), and then on + * the URL requested. GET requests are processed with a table-based + * dispatcher in url_table[]. The process of handling larger GET requests + * is complicated because we need to avoid allocating a copy of all the + * data to be sent to the client in one huge buffer. Instead, we spool the + * data into the buffer using logic in connection_dirserv_flushed_some() in + * dirserv.c. (TODO: If we extended buf.c to have a zero-copy + * reference-based buffer type, we could remove most of that code, at the + * cost of a bit more reference counting.) **/ /* In-points to directory.c: @@ -120,29 +153,55 @@ static void connection_dir_close_consensus_fetches( /********* END VARIABLES ************/ -/** Return true iff the directory purpose <b>dir_purpose</b> (and if it's - * fetching descriptors, it's fetching them for <b>router_purpose</b>) - * must use an anonymous connection to a directory. */ +/** Return false if the directory purpose <b>dir_purpose</b> + * does not require an anonymous (three-hop) connection. + * + * Return true 1) by default, 2) if all directory actions have + * specifically been configured to be over an anonymous connection, + * or 3) if the router is a bridge */ int -purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose) +purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, + const char *resource) { if (get_options()->AllDirActionsPrivate) return 1; - if (router_purpose == ROUTER_PURPOSE_BRIDGE) + + if (router_purpose == ROUTER_PURPOSE_BRIDGE) { + if (dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC + && resource && !strcmp(resource, "authority.z")) { + /* We are asking a bridge for its own descriptor. That doesn't need + anonymity. */ + return 0; + } + /* Assume all other bridge stuff needs anonymity. */ return 1; /* if no circuits yet, this might break bootstrapping, but it's * needed to be safe. */ - if (dir_purpose == DIR_PURPOSE_UPLOAD_DIR || - dir_purpose == DIR_PURPOSE_UPLOAD_VOTE || - dir_purpose == DIR_PURPOSE_UPLOAD_SIGNATURES || - dir_purpose == DIR_PURPOSE_FETCH_STATUS_VOTE || - dir_purpose == DIR_PURPOSE_FETCH_DETACHED_SIGNATURES || - dir_purpose == DIR_PURPOSE_FETCH_CONSENSUS || - dir_purpose == DIR_PURPOSE_FETCH_CERTIFICATE || - dir_purpose == DIR_PURPOSE_FETCH_SERVERDESC || - dir_purpose == DIR_PURPOSE_FETCH_EXTRAINFO || - dir_purpose == DIR_PURPOSE_FETCH_MICRODESC) - return 0; - return 1; + } + + switch (dir_purpose) + { + case DIR_PURPOSE_UPLOAD_DIR: + case DIR_PURPOSE_UPLOAD_VOTE: + case DIR_PURPOSE_UPLOAD_SIGNATURES: + case DIR_PURPOSE_FETCH_STATUS_VOTE: + case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES: + case DIR_PURPOSE_FETCH_CONSENSUS: + case DIR_PURPOSE_FETCH_CERTIFICATE: + case DIR_PURPOSE_FETCH_SERVERDESC: + case DIR_PURPOSE_FETCH_EXTRAINFO: + case DIR_PURPOSE_FETCH_MICRODESC: + return 0; + case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2: + case DIR_PURPOSE_UPLOAD_RENDDESC_V2: + case DIR_PURPOSE_FETCH_RENDDESC_V2: + return 1; + case DIR_PURPOSE_SERVER: + default: + log_warn(LD_BUG, "Called with dir_purpose=%d, router_purpose=%d", + dir_purpose, router_purpose); + tor_assert_nonfatal_unreached(); + return 1; /* Assume it needs anonymity; better safe than sorry. */ + } } /** Return a newly allocated string describing <b>auth</b>. Only describes @@ -347,7 +406,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, log_info(LD_DIR, "Uploading an extrainfo too (length %d)", (int) extrainfo_len); } - if (purpose_needs_anonymity(dir_purpose, router_purpose)) { + if (purpose_needs_anonymity(dir_purpose, router_purpose, NULL)) { indirection = DIRIND_ANONYMOUS; } else if (!fascist_firewall_allows_dir_server(ds, FIREWALL_DIR_CONNECTION, @@ -441,7 +500,8 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( int prefer_authority = (directory_fetches_from_authorities(options) || want_authority == DL_WANT_AUTHORITY); int require_authority = 0; - int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose); + int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose, + resource); dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource); time_t if_modified_since = 0; @@ -575,7 +635,7 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( "While fetching directory info, " "no running dirservers known. Will try again later. " "(purpose %d)", dir_purpose); - if (!purpose_needs_anonymity(dir_purpose, router_purpose)) { + if (!purpose_needs_anonymity(dir_purpose, router_purpose, resource)) { /* remember we tried them all and failed. */ directory_all_unreachable(time(NULL)); } @@ -1078,18 +1138,6 @@ directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, if_modified_since, NULL); } -/** Return non-zero iff a directory connection with purpose - * <b>dir_purpose</b> reveals sensitive information about a Tor - * instance's client activities. (Such connections must be performed - * through normal three-hop Tor circuits.) */ -int -is_sensitive_dir_purpose(uint8_t dir_purpose) -{ - return ((dir_purpose == DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) || - (dir_purpose == DIR_PURPOSE_UPLOAD_RENDDESC_V2) || - (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC_V2)); -} - /** Same as directory_initiate_command(), but accepts rendezvous data to * fetch a hidden service descriptor, and takes its address & port arguments * as tor_addr_port_t. */ @@ -1137,7 +1185,7 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose)); - if (is_sensitive_dir_purpose(dir_purpose)) { + if (purpose_needs_anonymity(dir_purpose, router_purpose, resource)) { tor_assert(anonymized_connection || rend_non_anonymous_mode_enabled(options)); } @@ -2341,10 +2389,10 @@ connection_dir_client_reached_eof(dir_connection_t *conn) conn->identity_digest, \ reason) ) #define SEND_HS_DESC_FAILED_CONTENT() ( \ - control_event_hs_descriptor_content(conn->rend_data->onion_address, \ - conn->requested_resource, \ - conn->identity_digest, \ - NULL) ) + control_event_hs_descriptor_content(rend_data_get_address(conn->rend_data), \ + conn->requested_resource, \ + conn->identity_digest, \ + NULL) ) tor_assert(conn->rend_data); log_info(LD_REND,"Received rendezvous descriptor (size %d, status %d " "(%s))", @@ -2417,7 +2465,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) #define SEND_HS_DESC_UPLOAD_FAILED_EVENT(reason) ( \ control_event_hs_descriptor_upload_failed( \ conn->identity_digest, \ - conn->rend_data->onion_address, \ + rend_data_get_address(conn->rend_data), \ reason) ) log_info(LD_REND,"Uploaded rendezvous descriptor (status %d " "(%s))", @@ -2431,7 +2479,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) "Uploading rendezvous descriptor: finished with status " "200 (%s)", escaped(reason)); control_event_hs_descriptor_uploaded(conn->identity_digest, - conn->rend_data->onion_address); + rend_data_get_address(conn->rend_data)); rend_service_desc_has_uploaded(conn->rend_data); break; case 400: @@ -2542,7 +2590,8 @@ connection_dir_about_to_close(dir_connection_t *dir_conn) * refetching is unnecessary.) */ if (conn->purpose == DIR_PURPOSE_FETCH_RENDDESC_V2 && dir_conn->rend_data && - strlen(dir_conn->rend_data->onion_address) == REND_SERVICE_ID_LEN_BASE32) + strlen(rend_data_get_address(dir_conn->rend_data)) == + REND_SERVICE_ID_LEN_BASE32) rend_client_refetch_v2_renddesc(dir_conn->rend_data); } @@ -2762,8 +2811,8 @@ static int handle_get_descriptor(dir_connection_t *conn, const get_handler_args_t *args); static int handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args); -static int handle_get_rendezvous2(dir_connection_t *conn, - const get_handler_args_t *args); +static int handle_get_hs_descriptor_v2(dir_connection_t *conn, + const get_handler_args_t *args); static int handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args); static int handle_get_networkstatus_bridges(dir_connection_t *conn, @@ -2779,7 +2828,8 @@ static const url_table_ent_t url_table[] = { { "/tor/server/", 1, handle_get_descriptor }, { "/tor/extra/", 1, handle_get_descriptor }, { "/tor/keys/", 1, handle_get_keys }, - { "/tor/rendezvous2/", 1, handle_get_rendezvous2 }, + { "/tor/rendezvous2/", 1, handle_get_hs_descriptor_v2 }, + { "/tor/hs/3/", 1, handle_get_hs_descriptor_v3 }, { "/tor/robots.txt", 0, handle_get_robots }, { "/tor/networkstatus-bridges", 0, handle_get_networkstatus_bridges }, { NULL, 0, NULL }, @@ -3347,7 +3397,8 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) /** Helper function for GET /tor/rendezvous2/ */ static int -handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args) +handle_get_hs_descriptor_v2(dir_connection_t *conn, + const get_handler_args_t *args) { const char *url = args->url; if (connection_dir_is_encrypted(conn)) { @@ -3381,6 +3432,50 @@ handle_get_rendezvous2(dir_connection_t *conn, const get_handler_args_t *args) return 0; } +/** Helper function for GET /tor/hs/3/<z>. Only for version 3. + */ +STATIC int +handle_get_hs_descriptor_v3(dir_connection_t *conn, + const get_handler_args_t *args) +{ + int retval; + const char *desc_str = NULL; + const char *pubkey_str = NULL; + const char *url = args->url; + + /* Don't serve v3 descriptors if next gen onion service is disabled. */ + if (!hs_v3_protocol_is_enabled()) { + /* 404 is used for an unrecognized URL so send back the same. */ + write_http_status_line(conn, 404, "Not found"); + goto done; + } + + /* Reject unencrypted dir connections */ + if (!connection_dir_is_encrypted(conn)) { + write_http_status_line(conn, 404, "Not found"); + goto done; + } + + /* After the path prefix follows the base64 encoded blinded pubkey which we + * use to get the descriptor from the cache. Skip the prefix and get the + * pubkey. */ + tor_assert(!strcmpstart(url, "/tor/hs/3/")); + pubkey_str = url + strlen("/tor/hs/3/"); + retval = hs_cache_lookup_as_dir(HS_VERSION_THREE, + pubkey_str, &desc_str); + if (retval < 0) { + write_http_status_line(conn, 404, "Not found"); + goto done; + } + + /* Found requested descriptor! Pass it to this nice client. */ + write_http_response_header(conn, strlen(desc_str), 0, 0); + connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn)); + + done: + return 0; +} + /** Helper function for GET /tor/networkstatus-bridges */ static int @@ -3436,6 +3531,90 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) return 0; } +/* Given the <b>url</b> from a POST request, try to extract the version number + * using the provided <b>prefix</b>. The version should be after the prefix and + * ending with the seperator "/". For instance: + * /tor/hs/3/publish + * + * On success, <b>end_pos</b> points to the position right after the version + * was found. On error, it is set to NULL. + * + * Return version on success else negative value. */ +STATIC int +parse_hs_version_from_post(const char *url, const char *prefix, + const char **end_pos) +{ + int ok; + unsigned long version; + const char *start; + char *end = NULL; + + tor_assert(url); + tor_assert(prefix); + tor_assert(end_pos); + + /* Check if the prefix does start the url. */ + if (strcmpstart(url, prefix)) { + goto err; + } + /* Move pointer to the end of the prefix string. */ + start = url + strlen(prefix); + /* Try this to be the HS version and if we are still at the separator, next + * will be move to the right value. */ + version = tor_parse_long(start, 10, 0, INT_MAX, &ok, &end); + if (!ok) { + goto err; + } + + *end_pos = end; + return (int) version; + err: + *end_pos = NULL; + return -1; +} + +/* Handle the POST request for a hidden service descripror. The request is in + * <b>url</b>, the body of the request is in <b>body</b>. Return 200 on success + * else return 400 indicating a bad request. */ +STATIC int +handle_post_hs_descriptor(const char *url, const char *body) +{ + int version; + const char *end_pos; + + tor_assert(url); + tor_assert(body); + + version = parse_hs_version_from_post(url, "/tor/hs/", &end_pos); + if (version < 0) { + goto err; + } + + /* We have a valid version number, now make sure it's a publish request. Use + * the end position just after the version and check for the command. */ + if (strcmpstart(end_pos, "/publish")) { + goto err; + } + + switch (version) { + case HS_VERSION_THREE: + if (hs_cache_store_as_dir(body) < 0) { + goto err; + } + log_info(LD_REND, "Publish request for HS descriptor handled " + "successfully."); + break; + default: + /* Unsupported version, return a bad request. */ + goto err; + } + + return 200; + err: + /* Bad request. */ + return 400; +} + /** Helper function: called when a dirserver gets a complete HTTP POST * request. Look for an uploaded server descriptor or rendezvous * service descriptor. On finding one, process it and write a @@ -3480,6 +3659,28 @@ directory_handle_command_post(dir_connection_t *conn, const char *headers, goto done; } + /* Handle HS descriptor publish request. */ + /* XXX: This should be disabled with a consensus param until we want to + * the prop224 be deployed and thus use. */ + if (connection_dir_is_encrypted(conn) && !strcmpstart(url, "/tor/hs/")) { + const char *msg = "HS descriptor stored successfully."; + /* Don't accept v3 and onward publish request if next gen onion service is + * disabled. */ + if (!hs_v3_protocol_is_enabled()) { + /* 404 is used for an unrecognized URL so send back the same. */ + write_http_status_line(conn, 404, "Not found"); + goto done; + } + + /* We most probably have a publish request for an HS descriptor. */ + int code = handle_post_hs_descriptor(url, body); + if (code != 200) { + msg = "Invalid HS descriptor. Rejected."; + } + write_http_status_line(conn, code, msg); + goto done; + } + if (!authdir_mode(options)) { /* we just provide cached directories; we don't want to * receive anything. */ @@ -3859,7 +4060,7 @@ download_status_schedule_get_delay(download_status_t *dls, delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); } else if (dls->backoff == DL_SCHED_RANDOM_EXPONENTIAL) { /* Check if we missed a reset somehow */ - if (dls->last_backoff_position > dls_schedule_position) { + IF_BUG_ONCE(dls->last_backoff_position > dls_schedule_position) { dls->last_backoff_position = 0; dls->last_delay_used = 0; } diff --git a/src/or/directory.h b/src/or/directory.h index 629b3ead90..589df7b70d 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -138,13 +138,19 @@ int download_status_get_n_failures(const download_status_t *dls); int download_status_get_n_attempts(const download_status_t *dls); time_t download_status_get_next_attempt_at(const download_status_t *dls); -/* Yes, these two functions are confusingly similar. - * Let's sort that out in #20077. */ -int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose); -int is_sensitive_dir_purpose(uint8_t dir_purpose); +int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose, + const char *resource); + +#ifdef DIRECTORY_PRIVATE + +struct get_handler_args_t; +STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn, + const struct get_handler_args_t *args); + +#endif #ifdef TOR_UNIT_TESTS -/* Used only by directory.c and test_dir.c */ +/* Used only by test_dir.c */ STATIC int parse_http_url(const char *headers, char **url); STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, @@ -158,6 +164,8 @@ STATIC int download_status_schedule_get_delay(download_status_t *dls, int min_delay, int max_delay, time_t now); +STATIC int handle_post_hs_descriptor(const char *url, const char *body); + STATIC char* authdir_type_to_string(dirinfo_type_t auth); STATIC const char * dir_conn_purpose_to_string(int purpose); STATIC int should_use_directory_guards(const or_options_t *options); @@ -169,6 +177,9 @@ STATIC void find_dl_min_and_max_delay(download_status_t *dls, int *min, int *max); STATIC int next_random_exponential_delay(int delay, int max_delay); +STATIC int parse_hs_version_from_post(const char *url, const char *prefix, + const char **end_pos); + #endif #endif diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 34db06355b..e2a6943708 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -948,7 +948,7 @@ list_server_status_v1(smartlist_t *routers, char **router_status_out, if (!node->is_running) *cp++ = '!'; router_get_verbose_nickname(cp, ri); - smartlist_add(rs_entries, tor_strdup(name_buf)); + smartlist_add_strdup(rs_entries, name_buf); } else if (ri->cache_info.published_on >= cutoff) { smartlist_add(rs_entries, list_single_server_status(ri, node->is_running)); @@ -1069,8 +1069,10 @@ directory_fetches_dir_info_later(const or_options_t *options) return options->UseBridges != 0; } -/** Return true iff we want to fetch and keep certificates for authorities +/** Return true iff we want to serve certificates for authorities * that we don't acknowledge as authorities ourself. + * Use we_want_to_fetch_unknown_auth_certs to check if we want to fetch + * and keep these certificates. */ int directory_caches_unknown_auth_certs(const or_options_t *options) @@ -1078,11 +1080,14 @@ directory_caches_unknown_auth_certs(const or_options_t *options) return dir_server_mode(options) || options->BridgeRelay; } -/** Return 1 if we want to keep descriptors, networkstatuses, etc around. +/** Return 1 if we want to fetch and serve descriptors, networkstatuses, etc * Else return 0. * Check options->DirPort_set and directory_permits_begindir_requests() * to see if we are willing to serve these directory documents to others via * the DirPort and begindir-over-ORPort, respectively. + * + * To check if we should fetch documents, use we_want_to_fetch_flavor and + * we_want_to_fetch_unknown_auth_certs instead of this function. */ int directory_caches_dir_info(const or_options_t *options) @@ -1949,7 +1954,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, vrs->status.guardfraction_percentage); } - smartlist_add(chunks, tor_strdup("\n")); + smartlist_add_strdup(chunks, "\n"); if (desc) { summary = policy_summarize(desc->exit_policy, AF_INET); @@ -1959,7 +1964,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, if (format == NS_V3_VOTE && vrs) { if (tor_mem_is_zero((char*)vrs->ed25519_id, ED25519_PUBKEY_LEN)) { - smartlist_add(chunks, tor_strdup("id ed25519 none\n")); + smartlist_add_strdup(chunks, "id ed25519 none\n"); } else { char ed_b64[BASE64_DIGEST256_LEN+1]; digest256_to_base64(ed_b64, (const char*)vrs->ed25519_id); @@ -2968,7 +2973,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, config_line_t *cl; for (cl = get_options()->RecommendedPackages; cl; cl = cl->next) { if (validate_recommended_package_line(cl->value)) - smartlist_add(v3_out->package_lines, tor_strdup(cl->value)); + smartlist_add_strdup(v3_out->package_lines, cl->value); } } @@ -2977,9 +2982,9 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, "Authority Exit Fast Guard Stable V2Dir Valid HSDir", 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); if (vote_on_reachability) - smartlist_add(v3_out->known_flags, tor_strdup("Running")); + smartlist_add_strdup(v3_out->known_flags, "Running"); if (listbadexits) - smartlist_add(v3_out->known_flags, tor_strdup("BadExit")); + smartlist_add_strdup(v3_out->known_flags, "BadExit"); smartlist_sort_strings(v3_out->known_flags); if (options->ConsensusParams) { @@ -3254,7 +3259,9 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router) router->nickname, fmt_addr32(router->addr), router->or_port); tor_addr_from_ipv4h(&router_addr, router->addr); chan = channel_tls_connect(&router_addr, router->or_port, - router->cache_info.identity_digest); + router->cache_info.identity_digest, + NULL // XXXX Ed25519 ID. + ); if (chan) command_setup_channel(chan); /* Possible IPv6. */ @@ -3266,7 +3273,9 @@ dirserv_single_reachability_test(time_t now, routerinfo_t *router) tor_addr_to_str(addrstr, &router->ipv6_addr, sizeof(addrstr), 1), router->ipv6_orport); chan = channel_tls_connect(&router->ipv6_addr, router->ipv6_orport, - router->cache_info.identity_digest); + router->cache_info.identity_digest, + NULL // XXXX Ed25519 ID. + ); if (chan) command_setup_channel(chan); } } diff --git a/src/or/dirvote.c b/src/or/dirvote.c index 2c10e784b4..d14af41667 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -26,6 +26,39 @@ /** * \file dirvote.c * \brief Functions to compute directory consensus, and schedule voting. + * + * This module is the center of the consensus-voting based directory + * authority system. With this system, a set of authorities first + * publish vote based on their opinions of the network, and then compute + * a consensus from those votes. Each authority signs the consensus, + * and clients trust the consensus if enough known authorities have + * signed it. + * + * The code in this module is only invoked on directory authorities. It's + * responsible for: + * + * <ul> + * <li>Generating this authority's vote networkstatus, based on the + * authority's view of the network as represented in dirserv.c + * <li>Formatting the vote networkstatus objects. + * <li>Generating the microdescriptors that correspond to our own + * vote. + * <li>Sending votes to all the other authorities. + * <li>Trying to fetch missing votes from other authorities. + * <li>Computing the consensus from a set of votes, as well as + * a "detached signature" object for other authorities to fetch. + * <li>Collecting other authorities' signatures on the same consensus, + * until there are enough. + * <li>Publishing the consensus to the reset of the directory system. + * <li>Scheduling all of the above operations. + * </ul> + * + * The main entry points are in dirvote_act(), which handles scheduled + * actions; and dirvote_add_vote() and dirvote_add_signatures(), which + * handle uploaded and downloaded votes and signatures. + * + * (See dir-spec.txt from torspec.git for a complete specification of + * the directory protocol and voting algorithms.) **/ /** A consensus that we have built and are appending signatures to. Once it's @@ -250,11 +283,11 @@ format_networkstatus_vote(crypto_pk_t *private_signing_key, smartlist_add(chunks, rsf); for (h = vrs->microdesc; h; h = h->next) { - smartlist_add(chunks, tor_strdup(h->microdesc_hash_line)); + smartlist_add_strdup(chunks, h->microdesc_hash_line); } } SMARTLIST_FOREACH_END(vrs); - smartlist_add(chunks, tor_strdup("directory-footer\n")); + smartlist_add_strdup(chunks, "directory-footer\n"); /* The digest includes everything up through the space after * directory-signature. (Yuck.) */ @@ -880,7 +913,7 @@ networkstatus_check_weights(int64_t Wgg, int64_t Wgd, int64_t Wmg, * * It returns true if weights could be computed, false otherwise. */ -static int +int networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, int64_t M, int64_t E, int64_t D, int64_t T, int64_t weight_scale) @@ -962,7 +995,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, Wgd = weight_scale; } } else { // Subcase b: R+D >= S - casename = "Case 2b1 (Wgg=1, Wmd=Wgd)"; + casename = "Case 2b1 (Wgg=weight_scale, Wmd=Wgd)"; Wee = (weight_scale*(E - G + M))/E; Wed = (weight_scale*(D - 2*E + 4*G - 2*M))/(3*D); Wme = (weight_scale*(G-M))/E; @@ -975,7 +1008,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, weight_scale, G, M, E, D, T, 10, 1); if (berr) { - casename = "Case 2b2 (Wgg=1, Wee=1)"; + casename = "Case 2b2 (Wgg=weight_scale, Wee=weight_scale)"; Wgg = weight_scale; Wee = weight_scale; Wed = (weight_scale*(D - 2*E + G + M))/(3*D); @@ -1044,7 +1077,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, } else { // Subcase b: S+D >= T/3 // D != 0 because S+D >= T/3 if (G < E) { - casename = "Case 3bg (G scarce, Wgg=1, Wmd == Wed)"; + casename = "Case 3bg (G scarce, Wgg=weight_scale, Wmd == Wed)"; Wgg = weight_scale; Wgd = (weight_scale*(D - 2*G + E + M))/(3*D); Wmg = 0; @@ -1056,7 +1089,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, berr = networkstatus_check_weights(Wgg, Wgd, Wmg, Wme, Wmd, Wee, Wed, weight_scale, G, M, E, D, T, 10, 1); } else { // G >= E - casename = "Case 3be (E scarce, Wee=1, Wmd == Wgd)"; + casename = "Case 3be (E scarce, Wee=weight_scale, Wmd == Wgd)"; Wee = weight_scale; Wed = (weight_scale*(D - 2*E + G + M))/(3*D); Wme = 0; @@ -1090,7 +1123,7 @@ networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, tor_assert(0 < weight_scale && weight_scale <= INT32_MAX); /* - * Provide Wgm=Wgg, Wmm=1, Wem=Wee, Weg=Wed. May later determine + * Provide Wgm=Wgg, Wmm=weight_scale, Wem=Wee, Weg=Wed. May later determine * that middle nodes need different bandwidth weights for dirport traffic, * or that weird exit policies need special weight, or that bridges * need special weight. @@ -1273,7 +1306,17 @@ compute_nth_protocol_set(int n, int n_voters, const smartlist_t *votes) * value in a newly allocated string. * * Note: this function DOES NOT check whether the votes are from - * recognized authorities. (dirvote_add_vote does that.) */ + * recognized authorities. (dirvote_add_vote does that.) + * + * <strong>WATCH OUT</strong>: You need to think before you change the + * behavior of this function, or of the functions it calls! If some + * authorities compute the consensus with a different algorithm than + * others, they will not reach the same result, and they will not all + * sign the same thing! If you really need to change the algorithm + * here, you should allocate a new "consensus_method" for the new + * behavior, and make the new behavior conditional on a new-enough + * consensus_method. + **/ char * networkstatus_compute_consensus(smartlist_t *votes, int total_authorities, @@ -1292,7 +1335,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_t *flags; const char *flavor_name; uint32_t max_unmeasured_bw_kb = DEFAULT_MAX_UNMEASURED_BW_KB; - int64_t G=0, M=0, E=0, D=0, T=0; /* For bandwidth weights */ + int64_t G, M, E, D, T; /* For bandwidth weights */ const routerstatus_format_type_t rs_format = flavor == FLAV_NS ? NS_V3_CONSENSUS : NS_V3_CONSENSUS_MICRODESC; char *params = NULL; @@ -1324,6 +1367,16 @@ networkstatus_compute_consensus(smartlist_t *votes, consensus_method = MAX_SUPPORTED_CONSENSUS_METHOD; } + if (consensus_method >= MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE) { + /* It's smarter to initialize these weights to 1, so that later on, + * we can't accidentally divide by zero. */ + G = M = E = D = 1; + T = 4; + } else { + /* ...but originally, they were set to zero. */ + G = M = E = D = T = 0; + } + /* Compute medians of time-related things, and figure out how many * routers we might need to talk about. */ { @@ -1363,7 +1416,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_free(sv); /* elements get freed later. */ } SMARTLIST_FOREACH(v->known_flags, const char *, cp, - smartlist_add(flags, tor_strdup(cp))); + smartlist_add_strdup(flags, cp)); } SMARTLIST_FOREACH_END(v); valid_after = median_time(va_times, n_votes); fresh_until = median_time(fu_times, n_votes); @@ -1396,7 +1449,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_free(combined_client_versions); if (consensus_method >= MIN_METHOD_FOR_ED25519_ID_VOTING) - smartlist_add(flags, tor_strdup("NoEdConsensus")); + smartlist_add_strdup(flags, "NoEdConsensus"); smartlist_sort_strings(flags); smartlist_uniq_strings(flags); @@ -1460,9 +1513,9 @@ networkstatus_compute_consensus(smartlist_t *votes, total_authorities); if (smartlist_len(param_list)) { params = smartlist_join_strings(param_list, " ", 0, NULL); - smartlist_add(chunks, tor_strdup("params ")); + smartlist_add_strdup(chunks, "params "); smartlist_add(chunks, params); - smartlist_add(chunks, tor_strdup("\n")); + smartlist_add_strdup(chunks, "\n"); } if (consensus_method >= MIN_METHOD_FOR_SHARED_RANDOM) { @@ -2049,10 +2102,10 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_join_strings(chosen_flags, " ", 0, NULL)); /* Now the version line. */ if (chosen_version) { - smartlist_add(chunks, tor_strdup("\nv ")); - smartlist_add(chunks, tor_strdup(chosen_version)); + smartlist_add_strdup(chunks, "\nv "); + smartlist_add_strdup(chunks, chosen_version); } - smartlist_add(chunks, tor_strdup("\n")); + smartlist_add_strdup(chunks, "\n"); if (chosen_protocol_list && consensus_method >= MIN_METHOD_FOR_RS_PROTOCOLS) { smartlist_add_asprintf(chunks, "pr %s\n", chosen_protocol_list); @@ -2105,7 +2158,7 @@ networkstatus_compute_consensus(smartlist_t *votes, } /* Mark the directory footer region */ - smartlist_add(chunks, tor_strdup("directory-footer\n")); + smartlist_add_strdup(chunks, "directory-footer\n"); { int64_t weight_scale = BW_WEIGHT_SCALE; @@ -2156,7 +2209,7 @@ networkstatus_compute_consensus(smartlist_t *votes, const char *algname = crypto_digest_algorithm_get_name(digest_alg); char *signature; - smartlist_add(chunks, tor_strdup("directory-signature ")); + smartlist_add_strdup(chunks, "directory-signature "); /* Compute the hash of the chunks. */ crypto_digest_smartlist(digest, digest_len, chunks, "", digest_alg); @@ -2183,7 +2236,7 @@ networkstatus_compute_consensus(smartlist_t *votes, smartlist_add(chunks, signature); if (legacy_id_key_digest && legacy_signing_key) { - smartlist_add(chunks, tor_strdup("directory-signature ")); + smartlist_add_strdup(chunks, "directory-signature "); base16_encode(fingerprint, sizeof(fingerprint), legacy_id_key_digest, DIGEST_LEN); crypto_pk_get_fingerprint(legacy_signing_key, @@ -2496,7 +2549,7 @@ networkstatus_format_signatures(networkstatus_t *consensus, base64_encode(buf, sizeof(buf), sig->signature, sig->signature_len, BASE64_ENCODE_MULTILINE); strlcat(buf, "-----END SIGNATURE-----\n", sizeof(buf)); - smartlist_add(elements, tor_strdup(buf)); + smartlist_add_strdup(elements, buf); } SMARTLIST_FOREACH_END(sig); } SMARTLIST_FOREACH_END(v); @@ -3606,8 +3659,8 @@ dirvote_add_signatures(const char *detached_signatures_body, "Queuing it for the next consensus.", source); if (!pending_consensus_signature_list) pending_consensus_signature_list = smartlist_new(); - smartlist_add(pending_consensus_signature_list, - tor_strdup(detached_signatures_body)); + smartlist_add_strdup(pending_consensus_signature_list, + detached_signatures_body); *msg = "Signature queued"; return 0; } diff --git a/src/or/dirvote.h b/src/or/dirvote.h index efd233ef5f..ac7db69db2 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -55,7 +55,7 @@ #define MIN_SUPPORTED_CONSENSUS_METHOD 13 /** The highest consensus method that we currently support. */ -#define MAX_SUPPORTED_CONSENSUS_METHOD 25 +#define MAX_SUPPORTED_CONSENSUS_METHOD 26 /** Lowest consensus method where microdesc consensuses omit any entry * with no microdesc. */ @@ -111,6 +111,10 @@ * entries. */ #define MIN_METHOD_FOR_RS_PROTOCOLS 25 +/** Lowest consensus method where authorities initialize bandwidth weights to 1 + * instead of 0. See #14881 */ +#define MIN_METHOD_FOR_INIT_BW_WEIGHTS_ONE 26 + /** Default bandwidth to clip unmeasured bandwidths to using method >= * MIN_METHOD_TO_CLIP_UNMEASURED_BW. (This is not a consensus method; do not * get confused with the above macros.) */ @@ -234,6 +238,10 @@ STATIC smartlist_t *dirvote_compute_params(smartlist_t *votes, int method, int total_authorities); STATIC char *compute_consensus_package_lines(smartlist_t *votes); STATIC char *make_consensus_method_list(int low, int high, const char *sep); +STATIC int +networkstatus_compute_bw_weights_v10(smartlist_t *chunks, int64_t G, + int64_t M, int64_t E, int64_t D, + int64_t T, int64_t weight_scale); #endif #endif diff --git a/src/or/dns.c b/src/or/dns.c index 5f9813b912..388104f8da 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -1750,7 +1750,7 @@ wildcard_increment_answer(const char *id) "invalid addresses. Apparently they are hijacking DNS failures. " "I'll try to correct for this by treating future occurrences of " "\"%s\" as 'not found'.", id, *ip, id); - smartlist_add(dns_wildcard_list, tor_strdup(id)); + smartlist_add_strdup(dns_wildcard_list, id); } if (!dns_wildcard_notice_given) control_event_server_status(LOG_NOTICE, "DNS_HIJACKED"); @@ -1774,7 +1774,7 @@ add_wildcarded_test_address(const char *address) n_test_addrs = get_options()->ServerDNSTestAddresses ? smartlist_len(get_options()->ServerDNSTestAddresses) : 0; - smartlist_add(dns_wildcarded_test_address_list, tor_strdup(address)); + smartlist_add_strdup(dns_wildcarded_test_address_list, address); n = smartlist_len(dns_wildcarded_test_address_list); if (n > n_test_addrs/2) { tor_log(dns_wildcarded_test_address_notice_given ? LOG_INFO : LOG_NOTICE, diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index f5a4f2ac0f..c5c0a88b09 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -3,10 +3,22 @@ /** * \file dnsserv.c - * \brief Implements client-side DNS proxy server code. Note: - * this is the DNS Server code, not the Server DNS code. Confused? This code - * runs on client-side, and acts as a DNS server. The code in dns.c, on the - * other hand, runs on Tor servers, and acts as a DNS client. + * \brief Implements client-side DNS proxy server code. + * + * When a user enables the DNSPort configuration option to have their local + * Tor client handle DNS requests, this module handles it. It functions as a + * "DNS Server" on the client side, which client applications use. + * + * Inbound DNS requests are represented as entry_connection_t here (since + * that's how Tor represents client-side streams), which are kept associated + * with an evdns_server_request structure as exposed by Libevent's + * evdns code. + * + * Upon receiving a DNS request, libevent calls our evdns_server_callback() + * function here, which causes this module to create an entry_connection_t + * request as appropriate. Later, when that request is answered, + * connection_edge.c calls dnsserv_resolved() so we can finish up and tell the + * DNS client. **/ #include "or.h" diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 265b6dcda1..b3fa31df7b 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -63,17 +63,42 @@ typedef struct { smartlist_t *socks_args; } bridge_info_t; -/** A list of our chosen entry guards. */ -static smartlist_t *entry_guards = NULL; -/** A value of 1 means that the entry_guards list has changed - * and those changes need to be flushed to disk. */ -static int entry_guards_dirty = 0; +/** All the context for guard selection on a particular client */ + +struct guard_selection_s { + /** + * A value of 1 means that guard_selection_t structures have changed + * and those changes need to be flushed to disk. + * + * XXX we don't know how to flush multiple guard contexts to disk yet; + * fix that as soon as any way to change the default exists, or at least + * make sure this gets set on change. + */ + int dirty; + + /** + * A list of our chosen entry guards, as entry_guard_t structures; this + * preserves the pre-Prop271 behavior. + */ + smartlist_t *chosen_entry_guards; + + /** + * When we try to choose an entry guard, should we parse and add + * config's EntryNodes first? This was formerly a global. + */ + int should_add_entry_nodes; +}; + +static smartlist_t *guard_contexts = NULL; +static guard_selection_t *curr_guard_context = NULL; static void bridge_free(bridge_info_t *bridge); -static const node_t *choose_random_entry_impl(cpath_build_state_t *state, +static const node_t *choose_random_entry_impl(guard_selection_t *gs, + cpath_build_state_t *state, int for_directory, dirinfo_type_t dirtype, int *n_options_out); +static guard_selection_t * guard_selection_new(void); static int num_bridges_usable(void); /* Default number of entry guards in the case where the NumEntryGuards @@ -84,13 +109,52 @@ static int num_bridges_usable(void); #define MIN_N_GUARDS 1 #define MAX_N_GUARDS 10 -/** Return the list of entry guards, creating it if necessary. */ +/** Allocate a new guard_selection_t */ + +static guard_selection_t * +guard_selection_new(void) +{ + guard_selection_t *gs; + + gs = tor_malloc_zero(sizeof(*gs)); + gs->chosen_entry_guards = smartlist_new(); + + return gs; +} + +/** Get current default guard_selection_t, creating it if necessary */ +guard_selection_t * +get_guard_selection_info(void) +{ + if (!guard_contexts) { + guard_contexts = smartlist_new(); + } + + if (!curr_guard_context) { + curr_guard_context = guard_selection_new(); + smartlist_add(guard_contexts, curr_guard_context); + } + + return curr_guard_context; +} + +/** Return the list of entry guards for a guard_selection_t, creating it + * if necessary. */ +const smartlist_t * +get_entry_guards_for_guard_selection(guard_selection_t *gs) +{ + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + + return gs->chosen_entry_guards; +} + +/** Return the list of entry guards for the default guard_selection_t, + * creating it if necessary. */ const smartlist_t * get_entry_guards(void) { - if (! entry_guards) - entry_guards = smartlist_new(); - return entry_guards; + return get_entry_guards_for_guard_selection(get_guard_selection_info()); } /** Check whether the entry guard <b>e</b> is usable, given the directory @@ -286,21 +350,28 @@ entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags, return node; } -/** Return the number of entry guards that we think are usable. */ +/** Return the number of entry guards that we think are usable, in the + * context of the given guard_selection_t */ int -num_live_entry_guards(int for_directory) +num_live_entry_guards_for_guard_selection(guard_selection_t *gs, + int for_directory) { int n = 0; const char *msg; + + tor_assert(gs != NULL); + /* Set the entry node attributes we are interested in. */ entry_is_live_flags_t entry_flags = ENTRY_NEED_CAPACITY; if (!for_directory) { entry_flags |= ENTRY_NEED_DESCRIPTOR; } - if (! entry_guards) + if (!(gs->chosen_entry_guards)) { return 0; - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + } + + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, entry) { if (for_directory && !entry->is_dir_cache) continue; if (entry_is_live(entry, entry_flags, &msg)) @@ -309,27 +380,57 @@ num_live_entry_guards(int for_directory) return n; } +/** Return the number of entry guards that we think are usable, for the + * default guard selection */ +int +num_live_entry_guards(int for_directory) +{ + return num_live_entry_guards_for_guard_selection( + get_guard_selection_info(), for_directory); +} + /** If <b>digest</b> matches the identity of any node in the - * entry_guards list, return that node. Else return NULL. */ + * entry_guards list for the provided guard selection state, + return that node. Else return NULL. */ entry_guard_t * -entry_guard_get_by_id_digest(const char *digest) +entry_guard_get_by_id_digest_for_guard_selection(guard_selection_t *gs, + const char *digest) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, entry, + tor_assert(gs != NULL); + + SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, entry, if (tor_memeq(digest, entry->identity, DIGEST_LEN)) return entry; ); return NULL; } -/** Dump a description of our list of entry guards to the log at level - * <b>severity</b>. */ +/** If <b>digest</b> matches the identity of any node in the + * entry_guards list for the default guard selection state, + return that node. Else return NULL. */ +entry_guard_t * +entry_guard_get_by_id_digest(const char *digest) +{ + return entry_guard_get_by_id_digest_for_guard_selection( + get_guard_selection_info(), digest); +} + +/** Dump a description of our list of entry guards in the given guard + * selection context to the log at level <b>severity</b>. */ static void -log_entry_guards(int severity) +log_entry_guards_for_guard_selection(guard_selection_t *gs, int severity) { smartlist_t *elements = smartlist_new(); char *s; - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) + /* + * TODO this should probably log more info about prop-271 state too + * when it's implemented. + */ + + tor_assert(gs != NULL); + + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { const char *msg = NULL; if (entry_is_live(e, ENTRY_NEED_CAPACITY, &msg)) @@ -386,23 +487,28 @@ control_event_guard_deferred(void) /** Largest amount that we'll backdate chosen_on_date */ #define CHOSEN_ON_DATE_SLOP (30*86400) -/** Add a new (preferably stable and fast) router to our - * entry_guards list. Return a pointer to the router if we succeed, - * or NULL if we can't find any more suitable entries. +/** Add a new (preferably stable and fast) router to our chosen_entry_guards + * list for the supplied guard selection. Return a pointer to the router if + * we succeed, or NULL if we can't find any more suitable entries. * * If <b>chosen</b> is defined, use that one, and if it's not * already in our entry_guards list, put it at the *beginning*. * Else, put the one we pick at the end of the list. */ STATIC const node_t * -add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, +add_an_entry_guard(guard_selection_t *gs, + const node_t *chosen, int reset_status, int prepend, int for_discovery, int for_directory) { const node_t *node; entry_guard_t *entry; + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + if (chosen) { node = chosen; - entry = entry_guard_get_by_id_digest(node->identity); + entry = entry_guard_get_by_id_digest_for_guard_selection(gs, + node->identity); if (entry) { if (reset_status) { entry->bad_since = 0; @@ -428,13 +534,11 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, if (!node) return NULL; } - if (node->using_as_guard) - return NULL; - if (entry_guard_get_by_id_digest(node->identity) != NULL) { + if (entry_guard_get_by_id_digest_for_guard_selection(gs, node->identity) + != NULL) { log_info(LD_CIRC, "I was about to add a duplicate entry guard."); /* This can happen if we choose a guard, then the node goes away, then * comes back. */ - ((node_t*) node)->using_as_guard = 1; return NULL; } entry = tor_malloc_zero(sizeof(entry_guard_t)); @@ -466,14 +570,15 @@ add_an_entry_guard(const node_t *chosen, int reset_status, int prepend, if (!for_discovery) entry->made_contact = 1; - ((node_t*)node)->using_as_guard = 1; if (prepend) - smartlist_insert(entry_guards, 0, entry); + smartlist_insert(gs->chosen_entry_guards, 0, entry); else - smartlist_add(entry_guards, entry); + smartlist_add(gs->chosen_entry_guards, entry); + control_event_guard(entry->nickname, entry->identity, "NEW"); control_event_guard_deferred(); - log_entry_guards(LOG_INFO); + log_entry_guards_for_guard_selection(gs, LOG_INFO); + return node; } @@ -503,20 +608,25 @@ decide_num_guards(const or_options_t *options, int for_directory) /** If the use of entry guards is configured, choose more entry guards * until we have enough in the list. */ static void -pick_entry_guards(const or_options_t *options, int for_directory) +pick_entry_guards(guard_selection_t *gs, + const or_options_t *options, + int for_directory) { int changed = 0; const int num_needed = decide_num_guards(options, for_directory); - tor_assert(entry_guards); + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); - while (num_live_entry_guards(for_directory) < num_needed) { - if (!add_an_entry_guard(NULL, 0, 0, 0, for_directory)) + while (num_live_entry_guards_for_guard_selection(gs, for_directory) + < num_needed) { + if (!add_an_entry_guard(gs, NULL, 0, 0, 0, for_directory)) break; changed = 1; } + if (changed) - entry_guards_changed(); + entry_guards_changed_for_guard_selection(gs); } /** How long (in seconds) do we allow an entry guard to be nonfunctional, @@ -559,19 +669,23 @@ guards_get_lifetime(void) MAX_GUARD_LIFETIME) + CHOSEN_ON_DATE_SLOP; } -/** Remove any entry guard which was selected by an unknown version of Tor, - * or which was selected by a version of Tor that's known to select - * entry guards badly, or which was selected more 2 months ago. */ +/** Remove from a guard selection context any entry guard which was selected + * by an unknown version of Tor, or which was selected by a version of Tor + * that's known to select entry guards badly, or which was selected more 2 + * months ago. */ /* XXXX The "obsolete guards" and "chosen long ago guards" things should * probably be different functions. */ static int -remove_obsolete_entry_guards(time_t now) +remove_obsolete_entry_guards(guard_selection_t *gs, time_t now) { int changed = 0, i; int32_t guard_lifetime = guards_get_lifetime(); - for (i = 0; i < smartlist_len(entry_guards); ++i) { - entry_guard_t *entry = smartlist_get(entry_guards, i); + tor_assert(gs != NULL); + if (!(gs->chosen_entry_guards)) goto done; + + for (i = 0; i < smartlist_len(gs->chosen_entry_guards); ++i) { + entry_guard_t *entry = smartlist_get(gs->chosen_entry_guards, i); const char *ver = entry->chosen_by_version; const char *msg = NULL; tor_version_t v; @@ -598,28 +712,32 @@ remove_obsolete_entry_guards(time_t now) entry->nickname, dbuf, msg, ver?escaped(ver):"none"); control_event_guard(entry->nickname, entry->identity, "DROPPED"); entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, i--); - log_entry_guards(LOG_INFO); + smartlist_del_keeporder(gs->chosen_entry_guards, i--); + log_entry_guards_for_guard_selection(gs, LOG_INFO); changed = 1; } } + done: return changed ? 1 : 0; } -/** Remove all entry guards that have been down or unlisted for so - * long that we don't think they'll come up again. Return 1 if we - * removed any, or 0 if we did nothing. */ +/** Remove all entry guards from this guard selection context that have + * been down or unlisted for so long that we don't think they'll come up + * again. Return 1 if we removed any, or 0 if we did nothing. */ static int -remove_dead_entry_guards(time_t now) +remove_dead_entry_guards(guard_selection_t *gs, time_t now) { char dbuf[HEX_DIGEST_LEN+1]; char tbuf[ISO_TIME_LEN+1]; int i; int changed = 0; - for (i = 0; i < smartlist_len(entry_guards); ) { - entry_guard_t *entry = smartlist_get(entry_guards, i); + tor_assert(gs != NULL); + if (!(gs->chosen_entry_guards)) goto done; + + for (i = 0; i < smartlist_len(gs->chosen_entry_guards); ) { + entry_guard_t *entry = smartlist_get(gs->chosen_entry_guards, i); if (entry->bad_since && ! entry->path_bias_disabled && entry->bad_since + ENTRY_GUARD_REMOVE_AFTER < now) { @@ -631,32 +749,47 @@ remove_dead_entry_guards(time_t now) entry->nickname, dbuf, tbuf); control_event_guard(entry->nickname, entry->identity, "DROPPED"); entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, i); - log_entry_guards(LOG_INFO); + smartlist_del_keeporder(gs->chosen_entry_guards, i); + log_entry_guards_for_guard_selection(gs, LOG_INFO); changed = 1; } else ++i; } + + done: return changed ? 1 : 0; } -/** Remove all currently listed entry guards. So new ones will be chosen. */ +/** Remove all currently listed entry guards for a given guard selection + * context */ void -remove_all_entry_guards(void) +remove_all_entry_guards_for_guard_selection(guard_selection_t *gs) { char dbuf[HEX_DIGEST_LEN+1]; - while (smartlist_len(entry_guards)) { - entry_guard_t *entry = smartlist_get(entry_guards, 0); - base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); - log_info(LD_CIRC, "Entry guard '%s' (%s) has been dropped.", - entry->nickname, dbuf); - control_event_guard(entry->nickname, entry->identity, "DROPPED"); - entry_guard_free(entry); - smartlist_del(entry_guards, 0); + tor_assert(gs != NULL); + + if (gs->chosen_entry_guards) { + while (smartlist_len(gs->chosen_entry_guards)) { + entry_guard_t *entry = smartlist_get(gs->chosen_entry_guards, 0); + base16_encode(dbuf, sizeof(dbuf), entry->identity, DIGEST_LEN); + log_info(LD_CIRC, "Entry guard '%s' (%s) has been dropped.", + entry->nickname, dbuf); + control_event_guard(entry->nickname, entry->identity, "DROPPED"); + entry_guard_free(entry); + smartlist_del(gs->chosen_entry_guards, 0); + } } - log_entry_guards(LOG_INFO); - entry_guards_changed(); + + log_entry_guards_for_guard_selection(gs, LOG_INFO); + entry_guards_changed_for_guard_selection(gs); +} + +/** Remove all currently listed entry guards. So new ones will be chosen. */ +void +remove_all_entry_guards(void) +{ + remove_all_entry_guards_for_guard_selection(get_guard_selection_info()); } /** A new directory or router-status has arrived; update the down/listed @@ -669,19 +802,21 @@ remove_all_entry_guards(void) * think that things are unlisted. */ void -entry_guards_compute_status(const or_options_t *options, time_t now) +entry_guards_compute_status_for_guard_selection(guard_selection_t *gs, + const or_options_t *options, + time_t now) { int changed = 0; digestmap_t *reasons; - if (! entry_guards) + if ((!gs) || !(gs->chosen_entry_guards)) return; if (options->EntryNodes) /* reshuffle the entry guard list if needed */ entry_nodes_should_be_added(); reasons = digestmap_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, entry) { const node_t *r = node_get_by_id(entry->identity); const char *reason = NULL; @@ -695,13 +830,14 @@ entry_guards_compute_status(const or_options_t *options, time_t now) } SMARTLIST_FOREACH_END(entry); - if (remove_dead_entry_guards(now)) + if (remove_dead_entry_guards(gs, now)) changed = 1; - if (remove_obsolete_entry_guards(now)) + if (remove_obsolete_entry_guards(gs, now)) changed = 1; if (changed) { - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, + entry) { const char *reason = digestmap_get(reasons, entry->identity); const char *live_msg = ""; const node_t *r = entry_is_live(entry, ENTRY_NEED_CAPACITY, &live_msg); @@ -716,14 +852,31 @@ entry_guards_compute_status(const or_options_t *options, time_t now) r ? "" : live_msg); } SMARTLIST_FOREACH_END(entry); log_info(LD_CIRC, " (%d/%d entry guards are usable/new)", - num_live_entry_guards(0), smartlist_len(entry_guards)); - log_entry_guards(LOG_INFO); - entry_guards_changed(); + num_live_entry_guards_for_guard_selection(gs, 0), + smartlist_len(gs->chosen_entry_guards)); + log_entry_guards_for_guard_selection(gs, LOG_INFO); + entry_guards_changed_for_guard_selection(gs); } digestmap_free(reasons, NULL); } +/** A new directory or router-status has arrived; update the down/listed + * status of the entry guards. + * + * An entry is 'down' if the directory lists it as nonrunning. + * An entry is 'unlisted' if the directory doesn't include it. + * + * Don't call this on startup; only on a fresh download. Otherwise we'll + * think that things are unlisted. + */ +void +entry_guards_compute_status(const or_options_t *options, time_t now) +{ + entry_guards_compute_status_for_guard_selection(get_guard_selection_info(), + options, now); +} + /** Called when a connection to an OR with the identity digest <b>digest</b> * is established (<b>succeeded</b>==1) or has failed (<b>succeeded</b>==0). * If the OR is an entry, change that entry's up/down status. @@ -736,8 +889,9 @@ entry_guards_compute_status(const or_options_t *options, time_t now) * Too many boolean arguments is a recipe for confusion. */ int -entry_guard_register_connect_status(const char *digest, int succeeded, - int mark_relay_status, time_t now) +entry_guard_register_connect_status_for_guard_selection( + guard_selection_t *gs, const char *digest, int succeeded, + int mark_relay_status, time_t now) { int changed = 0; int refuse_conn = 0; @@ -746,10 +900,11 @@ entry_guard_register_connect_status(const char *digest, int succeeded, int idx = -1; char buf[HEX_DIGEST_LEN+1]; - if (! entry_guards) + if (!(gs) || !(gs->chosen_entry_guards)) { return 0; + } - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { tor_assert(e); if (tor_memeq(e->identity, digest, DIGEST_LEN)) { entry = e; @@ -784,11 +939,12 @@ entry_guard_register_connect_status(const char *digest, int succeeded, "Connection to never-contacted entry guard '%s' (%s) failed. " "Removing from the list. %d/%d entry guards usable/new.", entry->nickname, buf, - num_live_entry_guards(0)-1, smartlist_len(entry_guards)-1); + num_live_entry_guards_for_guard_selection(gs, 0) - 1, + smartlist_len(gs->chosen_entry_guards)-1); control_event_guard(entry->nickname, entry->identity, "DROPPED"); entry_guard_free(entry); - smartlist_del_keeporder(entry_guards, idx); - log_entry_guards(LOG_INFO); + smartlist_del_keeporder(gs->chosen_entry_guards, idx); + log_entry_guards_for_guard_selection(gs, LOG_INFO); changed = 1; } else if (!entry->unreachable_since) { log_info(LD_CIRC, "Unable to connect to entry guard '%s' (%s). " @@ -818,7 +974,7 @@ entry_guard_register_connect_status(const char *digest, int succeeded, * came back? We should give our earlier entries another try too, * and close this connection so we don't use it before we've given * the others a shot. */ - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { if (e == entry) break; if (e->made_contact) { @@ -837,56 +993,68 @@ entry_guard_register_connect_status(const char *digest, int succeeded, "Connected to new entry guard '%s' (%s). Marking earlier " "entry guards up. %d/%d entry guards usable/new.", entry->nickname, buf, - num_live_entry_guards(0), smartlist_len(entry_guards)); - log_entry_guards(LOG_INFO); + num_live_entry_guards_for_guard_selection(gs, 0), + smartlist_len(gs->chosen_entry_guards)); + log_entry_guards_for_guard_selection(gs, LOG_INFO); changed = 1; } } if (changed) - entry_guards_changed(); + entry_guards_changed_for_guard_selection(gs); return refuse_conn ? -1 : 0; } -/** When we try to choose an entry guard, should we parse and add - * config's EntryNodes first? */ -static int should_add_entry_nodes = 0; +/** Called when a connection to an OR with the identity digest <b>digest</b> + * is established (<b>succeeded</b>==1) or has failed (<b>succeeded</b>==0). + * If the OR is an entry, change that entry's up/down status in the default + * guard selection context. + * Return 0 normally, or -1 if we want to tear down the new connection. + * + * If <b>mark_relay_status</b>, also call router_set_status() on this + * relay. + */ +int +entry_guard_register_connect_status(const char *digest, int succeeded, + int mark_relay_status, time_t now) +{ + return entry_guard_register_connect_status_for_guard_selection( + get_guard_selection_info(), digest, succeeded, mark_relay_status, now); +} /** Called when the value of EntryNodes changes in our configuration. */ void -entry_nodes_should_be_added(void) +entry_nodes_should_be_added_for_guard_selection(guard_selection_t *gs) { + tor_assert(gs != NULL); + log_info(LD_CIRC, "EntryNodes config option set. Putting configured " "relays at the front of the entry guard list."); - should_add_entry_nodes = 1; + gs->should_add_entry_nodes = 1; } -/** Update the using_as_guard fields of all the nodes. We do this after we - * remove entry guards from the list: This is the only function that clears - * the using_as_guard field. */ -static void -update_node_guard_status(void) -{ - smartlist_t *nodes = nodelist_get_list(); - SMARTLIST_FOREACH(nodes, node_t *, node, node->using_as_guard = 0); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) { - node_t *node = node_get_mutable_by_id(entry->identity); - if (node) - node->using_as_guard = 1; - } SMARTLIST_FOREACH_END(entry); +/** Called when the value of EntryNodes changes in our configuration. */ +void +entry_nodes_should_be_added(void) +{ + entry_nodes_should_be_added_for_guard_selection( + get_guard_selection_info()); } /** Adjust the entry guards list so that it only contains entries from * EntryNodes, adding new entries from EntryNodes to the list as needed. */ STATIC void -entry_guards_set_from_config(const or_options_t *options) +entry_guards_set_from_config(guard_selection_t *gs, + const or_options_t *options) { smartlist_t *entry_nodes, *worse_entry_nodes, *entry_fps; smartlist_t *old_entry_guards_on_list, *old_entry_guards_not_on_list; const int numentryguards = decide_num_guards(options, 0); - tor_assert(entry_guards); - should_add_entry_nodes = 0; + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + + gs->should_add_entry_nodes = 0; if (!options->EntryNodes) { /* It's possible that a controller set EntryNodes, thus making @@ -915,7 +1083,7 @@ entry_guards_set_from_config(const or_options_t *options) SMARTLIST_FOREACH(entry_nodes, const node_t *,node, smartlist_add(entry_fps, (void*)node->identity)); - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, { + SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, e, { if (smartlist_contains_digest(entry_fps, e->identity)) smartlist_add(old_entry_guards_on_list, e); else @@ -925,7 +1093,8 @@ entry_guards_set_from_config(const or_options_t *options) /* Remove all currently configured guard nodes, excluded nodes, unreachable * nodes, or non-Guard nodes from entry_nodes. */ SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { - if (entry_guard_get_by_id_digest(node->identity)) { + if (entry_guard_get_by_id_digest_for_guard_selection(gs, + node->identity)) { SMARTLIST_DEL_CURRENT(entry_nodes, node); continue; } else if (routerset_contains_node(options->ExcludeNodes, node)) { @@ -942,9 +1111,9 @@ entry_guards_set_from_config(const or_options_t *options) } SMARTLIST_FOREACH_END(node); /* Now build the new entry_guards list. */ - smartlist_clear(entry_guards); + smartlist_clear(gs->chosen_entry_guards); /* First, the previously configured guards that are in EntryNodes. */ - smartlist_add_all(entry_guards, old_entry_guards_on_list); + smartlist_add_all(gs->chosen_entry_guards, old_entry_guards_on_list); /* Next, scramble the rest of EntryNodes, putting the guards first. */ smartlist_shuffle(entry_nodes); smartlist_shuffle(worse_entry_nodes); @@ -952,24 +1121,23 @@ entry_guards_set_from_config(const or_options_t *options) /* Next, the rest of EntryNodes */ SMARTLIST_FOREACH_BEGIN(entry_nodes, const node_t *, node) { - add_an_entry_guard(node, 0, 0, 1, 0); - if (smartlist_len(entry_guards) > numentryguards * 10) + add_an_entry_guard(gs, node, 0, 0, 1, 0); + if (smartlist_len(gs->chosen_entry_guards) > numentryguards * 10) break; } SMARTLIST_FOREACH_END(node); - log_notice(LD_GENERAL, "%d entries in guards", smartlist_len(entry_guards)); + log_notice(LD_GENERAL, "%d entries in guards", + smartlist_len(gs->chosen_entry_guards)); /* Finally, free the remaining previously configured guards that are not in * EntryNodes. */ SMARTLIST_FOREACH(old_entry_guards_not_on_list, entry_guard_t *, e, entry_guard_free(e)); - update_node_guard_status(); - smartlist_free(entry_nodes); smartlist_free(worse_entry_nodes); smartlist_free(entry_fps); smartlist_free(old_entry_guards_on_list); smartlist_free(old_entry_guards_not_on_list); - entry_guards_changed(); + entry_guards_changed_for_guard_selection(gs); } /** Return 0 if we're fine adding arbitrary routers out of the @@ -996,7 +1164,8 @@ entry_list_is_constrained(const or_options_t *options) const node_t * choose_random_entry(cpath_build_state_t *state) { - return choose_random_entry_impl(state, 0, NO_DIRINFO, NULL); + return choose_random_entry_impl(get_guard_selection_info(), + state, 0, NO_DIRINFO, NULL); } /** Pick a live (up and listed) directory guard from entry_guards for @@ -1004,7 +1173,8 @@ choose_random_entry(cpath_build_state_t *state) const node_t * choose_random_dirguard(dirinfo_type_t type) { - return choose_random_entry_impl(NULL, 1, type, NULL); + return choose_random_entry_impl(get_guard_selection_info(), + NULL, 1, type, NULL); } /** Filter <b>all_entry_guards</b> for usable entry guards and put them @@ -1095,7 +1265,8 @@ populate_live_entry_guards(smartlist_t *live_entry_guards, return retval; } -/** Pick a node to be used as the entry guard of a circuit. +/** Pick a node to be used as the entry guard of a circuit, relative to + * a supplied guard selection context. * * If <b>state</b> is set, it contains the information we know about * the upcoming circuit. @@ -1116,7 +1287,8 @@ populate_live_entry_guards(smartlist_t *live_entry_guards, * Helper for choose_random{entry,dirguard}. */ static const node_t * -choose_random_entry_impl(cpath_build_state_t *state, int for_directory, +choose_random_entry_impl(guard_selection_t *gs, + cpath_build_state_t *state, int for_directory, dirinfo_type_t dirinfo_type, int *n_options_out) { const or_options_t *options = get_options(); @@ -1130,18 +1302,18 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, const int num_needed = decide_num_guards(options, for_directory); int retval = 0; + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + if (n_options_out) *n_options_out = 0; - if (!entry_guards) - entry_guards = smartlist_new(); - - if (should_add_entry_nodes) - entry_guards_set_from_config(options); + if (gs->should_add_entry_nodes) + entry_guards_set_from_config(gs, options); if (!entry_list_is_constrained(options) && - smartlist_len(entry_guards) < num_needed) - pick_entry_guards(options, for_directory); + smartlist_len(gs->chosen_entry_guards) < num_needed) + pick_entry_guards(gs, options, for_directory); retry: smartlist_clear(live_entry_guards); @@ -1149,7 +1321,7 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, /* Populate the list of live entry guards so that we pick one of them. */ retval = populate_live_entry_guards(live_entry_guards, - entry_guards, + gs->chosen_entry_guards, chosen_exit, dirinfo_type, for_directory, @@ -1177,9 +1349,9 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, /* XXX if guard doesn't imply fast and stable, then we need * to tell add_an_entry_guard below what we want, or it might * be a long time til we get it. -RD */ - node = add_an_entry_guard(NULL, 0, 0, 1, for_directory); + node = add_an_entry_guard(gs, NULL, 0, 0, 1, for_directory); if (node) { - entry_guards_changed(); + entry_guards_changed_for_guard_selection(gs); /* XXX we start over here in case the new node we added shares * a family with our exit node. There's a chance that we'll just * load up on entry guards here, if the network we're using is @@ -1219,13 +1391,15 @@ choose_random_entry_impl(cpath_build_state_t *state, int for_directory, } /** Parse <b>state</b> and learn about the entry guards it describes. - * If <b>set</b> is true, and there are no errors, replace the global - * entry_list with what we find. + * If <b>set</b> is true, and there are no errors, replace the guard + * list in the provided guard selection context with what we find. * On success, return 0. On failure, alloc into *<b>msg</b> a string * describing the error, and return -1. */ int -entry_guards_parse_state(or_state_t *state, int set, char **msg) +entry_guards_parse_state_for_guard_selection( + guard_selection_t *gs, + or_state_t *state, int set, char **msg) { entry_guard_t *node = NULL; smartlist_t *new_entry_guards = smartlist_new(); @@ -1234,6 +1408,8 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) const char *state_version = state->TorVersion; digestmap_t *added_by = digestmap_new(); + tor_assert(gs != NULL); + *msg = NULL; for (line = state->EntryGuards; line; line = line->next) { if (!strcasecmp(line->key, "EntryGuard")) { @@ -1469,24 +1645,36 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) entry_guard_free(e)); smartlist_free(new_entry_guards); } else { /* !err && set */ - if (entry_guards) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, + if (gs->chosen_entry_guards) { + SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, e, entry_guard_free(e)); - smartlist_free(entry_guards); + smartlist_free(gs->chosen_entry_guards); } - entry_guards = new_entry_guards; - entry_guards_dirty = 0; + gs->chosen_entry_guards = new_entry_guards; + gs->dirty = 0; /* XXX hand new_entry_guards to this func, and move it up a * few lines, so we don't have to re-dirty it */ - if (remove_obsolete_entry_guards(now)) - entry_guards_dirty = 1; - - update_node_guard_status(); + if (remove_obsolete_entry_guards(gs, now)) + gs->dirty = 1; } digestmap_free(added_by, tor_free_); return *msg ? -1 : 0; } +/** Parse <b>state</b> and learn about the entry guards it describes. + * If <b>set</b> is true, and there are no errors, replace the guard + * list in the default guard selection context with what we find. + * On success, return 0. On failure, alloc into *<b>msg</b> a string + * describing the error, and return -1. + */ +int +entry_guards_parse_state(or_state_t *state, int set, char **msg) +{ + return entry_guards_parse_state_for_guard_selection( + get_guard_selection_info(), + state, set, msg); +} + /** How long will we let a change in our guard nodes stay un-saved * when we are trying to avoid disk writes? */ #define SLOW_GUARD_STATE_FLUSH_TIME 600 @@ -1494,15 +1682,18 @@ entry_guards_parse_state(or_state_t *state, int set, char **msg) * when we are not trying to avoid disk writes? */ #define FAST_GUARD_STATE_FLUSH_TIME 30 -/** Our list of entry guards has changed, or some element of one - * of our entry guards has changed. Write the changes to disk within - * the next few minutes. +/** Our list of entry guards has changed for a particular guard selection + * context, or some element of one of our entry guards has changed for one. + * Write the changes to disk within the next few minutes. */ void -entry_guards_changed(void) +entry_guards_changed_for_guard_selection(guard_selection_t *gs) { time_t when; - entry_guards_dirty = 1; + + tor_assert(gs != NULL); + + gs->dirty = 1; if (get_options()->AvoidDiskWrites) when = time(NULL) + SLOW_GUARD_STATE_FLUSH_TIME; @@ -1513,24 +1704,42 @@ entry_guards_changed(void) or_state_mark_dirty(get_or_state(), when); } +/** Our list of entry guards has changed for the default guard selection + * context, or some element of one of our entry guards has changed. Write + * the changes to disk within the next few minutes. + */ +void +entry_guards_changed(void) +{ + entry_guards_changed_for_guard_selection(get_guard_selection_info()); +} + /** If the entry guard info has not changed, do nothing and return. * Otherwise, free the EntryGuards piece of <b>state</b> and create * a new one out of the global entry_guards list, and then mark * <b>state</b> dirty so it will get saved to disk. + * + * XXX this should get totally redesigned around storing multiple + * entry guard contexts. For the initial refactor we'll just + * always use the current default. Fix it as soon as we actually + * have any way that default can change. */ void entry_guards_update_state(or_state_t *state) { config_line_t **next, *line; - if (! entry_guards_dirty) + guard_selection_t *gs = get_guard_selection_info(); + + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + + if (!gs->dirty) return; config_free_lines(state->EntryGuards); next = &state->EntryGuards; *next = NULL; - if (!entry_guards) - entry_guards = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { char dbuf[HEX_DIGEST_LEN+1]; if (!e->made_contact) continue; /* don't write this one to disk */ @@ -1596,7 +1805,7 @@ entry_guards_update_state(or_state_t *state) } SMARTLIST_FOREACH_END(e); if (!get_options()->AvoidDiskWrites) or_state_mark_dirty(get_or_state(), 0); - entry_guards_dirty = 0; + gs->dirty = 0; } /** If <b>question</b> is the string "entry-guards", then dump @@ -1604,12 +1813,20 @@ entry_guards_update_state(or_state_t *state) * the nodes in the global entry_guards list. See control-spec.txt * for details. * For backward compatibility, we also handle the string "helper-nodes". + * + * XXX this should be totally redesigned after prop 271 too, and that's + * going to take some control spec work. * */ int getinfo_helper_entry_guards(control_connection_t *conn, const char *question, char **answer, const char **errmsg) { + guard_selection_t *gs = get_guard_selection_info(); + + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + (void) conn; (void) errmsg; @@ -1618,9 +1835,8 @@ getinfo_helper_entry_guards(control_connection_t *conn, smartlist_t *sl = smartlist_new(); char tbuf[ISO_TIME_LEN+1]; char nbuf[MAX_VERBOSE_NICKNAME_LEN+1]; - if (!entry_guards) - entry_guards = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { const char *status = NULL; time_t when = 0; const node_t *node; @@ -2042,6 +2258,42 @@ bridge_add_from_config(bridge_line_t *bridge_line) smartlist_add(bridge_list, b); } +/** Returns true iff the node is used as a guard in the specified guard + * context */ +int +is_node_used_as_guard_for_guard_selection(guard_selection_t *gs, + const node_t *node) +{ + int res = 0; + + /* + * We used to have a using_as_guard flag in node_t, but it had to go away + * to allow for multiple guard selection contexts. Instead, search the + * guard list for a matching digest. + */ + + tor_assert(gs != NULL); + tor_assert(node != NULL); + + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { + if (tor_memeq(e->identity, node->identity, DIGEST_LEN)) { + res = 1; + break; + } + } SMARTLIST_FOREACH_END(e); + + return res; +} + +/** Returns true iff the node is used as a guard in the default guard + * context */ +MOCK_IMPL(int, +is_node_used_as_guard, (const node_t *node)) +{ + return is_node_used_as_guard_for_guard_selection( + get_guard_selection_info(), node); +} + /** Return true iff <b>routerset</b> contains the bridge <b>bridge</b>. */ static int routerset_contains_bridge(const routerset_t *routerset, @@ -2383,7 +2635,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } - add_an_entry_guard(node, 1, 1, 0, 0); + add_an_entry_guard(get_guard_selection_info(), node, 1, 1, 0, 0); log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); @@ -2419,7 +2671,8 @@ num_bridges_usable(void) { int n_options = 0; tor_assert(get_options()->UseBridges); - (void) choose_random_entry_impl(NULL, 0, 0, &n_options); + (void) choose_random_entry_impl(get_guard_selection_info(), + NULL, 0, 0, &n_options); return n_options; } @@ -2472,9 +2725,12 @@ entries_retry_helper(const or_options_t *options, int act) int any_known = 0; int any_running = 0; int need_bridges = options->UseBridges != 0; - if (!entry_guards) - entry_guards = smartlist_new(); - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { + guard_selection_t *gs = get_guard_selection_info(); + + tor_assert(gs != NULL); + tor_assert(gs->chosen_entry_guards != NULL); + + SMARTLIST_FOREACH_BEGIN(gs->chosen_entry_guards, entry_guard_t *, e) { node = node_get_by_id(e->identity); if (node && node_has_descriptor(node) && node_is_bridge(node) == need_bridges && @@ -2521,25 +2777,20 @@ entries_retry_all(const or_options_t *options) entries_retry_helper(options, 1); } -/** Return true if at least one of our bridges runs a Tor version that can - * provide microdescriptors to us. If not, we'll fall back to asking for - * full descriptors. */ -int -any_bridge_supports_microdescriptors(void) +/** Free one guard selection context */ +static void +guard_selection_free(guard_selection_t *gs) { - const node_t *node; - if (!get_options()->UseBridges || !entry_guards) - return 0; - SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, e) { - node = node_get_by_id(e->identity); - if (node && node->is_running && - node_is_bridge(node) && node_is_a_configured_bridge(node)) { - /* This is one of our current bridges, and we know enough about - * it to know that it will be able to answer our questions. */ - return 1; - } - } SMARTLIST_FOREACH_END(e); - return 0; + if (!gs) return; + + if (gs->chosen_entry_guards) { + SMARTLIST_FOREACH(gs->chosen_entry_guards, entry_guard_t *, e, + entry_guard_free(e)); + smartlist_free(gs->chosen_entry_guards); + gs->chosen_entry_guards = NULL; + } + + tor_free(gs); } /** Release all storage held by the list of entry guards and related @@ -2547,11 +2798,15 @@ any_bridge_supports_microdescriptors(void) void entry_guards_free_all(void) { - if (entry_guards) { - SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e, - entry_guard_free(e)); - smartlist_free(entry_guards); - entry_guards = NULL; + /* Null out the default */ + curr_guard_context = NULL; + /* Free all the guard contexts */ + if (guard_contexts != NULL) { + SMARTLIST_FOREACH_BEGIN(guard_contexts, guard_selection_t *, gs) { + guard_selection_free(gs); + } SMARTLIST_FOREACH_END(gs); + smartlist_free(guard_contexts); + guard_contexts = NULL; } clear_bridge_list(); smartlist_free(bridge_list); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 1021e67d43..00f96916b6 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -16,6 +16,9 @@ /* XXXX NM I would prefer that all of this stuff be private to * entrynodes.c. */ +/* Forward declare for guard_selection_t; entrynodes.c has the real struct */ +typedef struct guard_selection_s guard_selection_t; + /** An entry_guard_t represents our information about a chosen long-term * first hop, known as a "helper" node in the literature. We can't just * use a node_t, since we want to remember these even when we @@ -53,6 +56,14 @@ typedef struct entry_guard_t { time_t last_attempted; /**< 0 if we can connect to this guard, or the time * at which we last failed to connect to it. */ + /** + * @name circpathbias fields + * + * These fields are used in circpathbias.c to try to detect entry + * nodes that are failing circuits at a suspicious frequency. + */ + /**@{*/ + double circ_attempts; /**< Number of circuits this guard has "attempted" */ double circ_successes; /**< Number of successfully built circuits using * this guard as first hop. */ @@ -68,20 +79,30 @@ typedef struct entry_guard_t { double use_attempts; /**< Number of circuits we tried to use with streams */ double use_successes; /**< Number of successfully used circuits using * this guard as first hop. */ + /**@}*/ } entry_guard_t; +entry_guard_t *entry_guard_get_by_id_digest_for_guard_selection( + guard_selection_t *gs, const char *digest); entry_guard_t *entry_guard_get_by_id_digest(const char *digest); +void entry_guards_changed_for_guard_selection(guard_selection_t *gs); void entry_guards_changed(void); +guard_selection_t * get_guard_selection_info(void); +const smartlist_t *get_entry_guards_for_guard_selection( + guard_selection_t *gs); const smartlist_t *get_entry_guards(void); +int num_live_entry_guards_for_guard_selection( + guard_selection_t *gs, + int for_directory); int num_live_entry_guards(int for_directory); #endif #ifdef ENTRYNODES_PRIVATE -STATIC const node_t *add_an_entry_guard(const node_t *chosen, +STATIC const node_t *add_an_entry_guard(guard_selection_t *gs, + const node_t *chosen, int reset_status, int prepend, int for_discovery, int for_directory); - STATIC int populate_live_entry_guards(smartlist_t *live_entry_guards, const smartlist_t *all_entry_guards, const node_t *chosen_exit, @@ -90,7 +111,8 @@ STATIC int populate_live_entry_guards(smartlist_t *live_entry_guards, int need_uptime, int need_capacity); STATIC int decide_num_guards(const or_options_t *options, int for_directory); -STATIC void entry_guards_set_from_config(const or_options_t *options); +STATIC void entry_guards_set_from_config(guard_selection_t *gs, + const or_options_t *options); /** Flags to be passed to entry_is_live() to indicate what kind of * entry nodes we are looking for. */ @@ -109,20 +131,32 @@ STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now); #endif +void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs); void remove_all_entry_guards(void); +void entry_guards_compute_status_for_guard_selection( + guard_selection_t *gs, const or_options_t *options, time_t now); void entry_guards_compute_status(const or_options_t *options, time_t now); +int entry_guard_register_connect_status_for_guard_selection( + guard_selection_t *gs, const char *digest, int succeeded, + int mark_relay_status, time_t now); int entry_guard_register_connect_status(const char *digest, int succeeded, int mark_relay_status, time_t now); +void entry_nodes_should_be_added_for_guard_selection(guard_selection_t *gs); void entry_nodes_should_be_added(void); int entry_list_is_constrained(const or_options_t *options); const node_t *choose_random_entry(cpath_build_state_t *state); const node_t *choose_random_dirguard(dirinfo_type_t t); +int entry_guards_parse_state_for_guard_selection( + guard_selection_t *gs, or_state_t *state, int set, char **msg); int entry_guards_parse_state(or_state_t *state, int set, char **msg); void entry_guards_update_state(or_state_t *state); int getinfo_helper_entry_guards(control_connection_t *conn, const char *question, char **answer, const char **errmsg); +int is_node_used_as_guard_for_guard_selection(guard_selection_t *gs, + const node_t *node); +MOCK_DECL(int, is_node_used_as_guard, (const node_t *node)); void mark_bridge_list(void); void sweep_bridge_list(void); @@ -143,7 +177,6 @@ int any_bridge_descriptors_known(void); int entries_known_but_down(const or_options_t *options); void entries_retry_all(const or_options_t *options); -int any_bridge_supports_microdescriptors(void); const smartlist_t *get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port); diff --git a/src/or/geoip.c b/src/or/geoip.c index ba65dfe56c..74811ea643 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -880,7 +880,7 @@ geoip_get_transport_history(void) /* If it's the first time we see this transport, note it. */ if (val == 1) - smartlist_add(transports_used, tor_strdup(transport_name)); + smartlist_add_strdup(transports_used, transport_name); log_debug(LD_GENERAL, "Client from '%s' with transport '%s'. " "I've now seen %d clients.", diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c new file mode 100644 index 0000000000..b7ff979e5b --- /dev/null +++ b/src/or/hs_cache.c @@ -0,0 +1,385 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cache.c + * \brief Handle hidden service descriptor caches. + **/ + +/* For unit tests.*/ +#define HS_CACHE_PRIVATE + +#include "hs_cache.h" + +#include "or.h" +#include "config.h" +#include "hs_common.h" +#include "hs_descriptor.h" +#include "rendcache.h" + +/* Directory descriptor cache. Map indexed by blinded key. */ +static digest256map_t *hs_cache_v3_dir; + +/* Remove a given descriptor from our cache. */ +static void +remove_v3_desc_as_dir(const hs_cache_dir_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_remove(hs_cache_v3_dir, desc->key); +} + +/* Store a given descriptor in our cache. */ +static void +store_v3_desc_as_dir(hs_cache_dir_descriptor_t *desc) +{ + tor_assert(desc); + digest256map_set(hs_cache_v3_dir, desc->key, desc); +} + +/* Query our cache and return the entry or NULL if not found. */ +static hs_cache_dir_descriptor_t * +lookup_v3_desc_as_dir(const uint8_t *key) +{ + tor_assert(key); + return digest256map_get(hs_cache_v3_dir, key); +} + +/* Free a directory descriptor object. */ +static void +cache_dir_desc_free(hs_cache_dir_descriptor_t *desc) +{ + if (desc == NULL) { + return; + } + hs_desc_plaintext_data_free(desc->plaintext_data); + tor_free(desc->encoded_desc); + tor_free(desc); +} + +/* Helper function: Use by the free all function using the digest256map + * interface to cache entries. */ +static void +cache_dir_desc_free_(void *ptr) +{ + hs_cache_dir_descriptor_t *desc = ptr; + cache_dir_desc_free(desc); +} + +/* Create a new directory cache descriptor object from a encoded descriptor. + * On success, return the heap-allocated cache object, otherwise return NULL if + * we can't decode the descriptor. */ +static hs_cache_dir_descriptor_t * +cache_dir_desc_new(const char *desc) +{ + hs_cache_dir_descriptor_t *dir_desc; + + tor_assert(desc); + + dir_desc = tor_malloc_zero(sizeof(hs_cache_dir_descriptor_t)); + dir_desc->plaintext_data = + tor_malloc_zero(sizeof(hs_desc_plaintext_data_t)); + dir_desc->encoded_desc = tor_strdup(desc); + + if (hs_desc_decode_plaintext(desc, dir_desc->plaintext_data) < 0) { + log_debug(LD_DIR, "Unable to decode descriptor. Rejecting."); + goto err; + } + + /* The blinded pubkey is the indexed key. */ + dir_desc->key = dir_desc->plaintext_data->blinded_kp.pubkey.pubkey; + dir_desc->created_ts = time(NULL); + return dir_desc; + + err: + cache_dir_desc_free(dir_desc); + return NULL; +} + +/* Return the size of a cache entry in bytes. */ +static size_t +cache_get_entry_size(const hs_cache_dir_descriptor_t *entry) +{ + return (sizeof(*entry) + hs_desc_plaintext_obj_size(entry->plaintext_data) + + strlen(entry->encoded_desc)); +} + +/* Try to store a valid version 3 descriptor in the directory cache. Return 0 + * on success else a negative value is returned indicating that we have a + * newer version in our cache. On error, caller is responsible to free the + * given descriptor desc. */ +static int +cache_store_v3_as_dir(hs_cache_dir_descriptor_t *desc) +{ + hs_cache_dir_descriptor_t *cache_entry; + + tor_assert(desc); + + /* Verify if we have an entry in the cache for that key and if yes, check + * if we should replace it? */ + cache_entry = lookup_v3_desc_as_dir(desc->key); + if (cache_entry != NULL) { + /* Only replace descriptor if revision-counter is greater than the one + * in our cache */ + if (cache_entry->plaintext_data->revision_counter >= + desc->plaintext_data->revision_counter) { + log_info(LD_REND, "Descriptor revision counter in our cache is " + "greater or equal than the one we received. " + "Rejecting!"); + goto err; + } + /* We now know that the descriptor we just received is a new one so + * remove the entry we currently have from our cache so we can then + * store the new one. */ + remove_v3_desc_as_dir(cache_entry); + rend_cache_decrement_allocation(cache_get_entry_size(cache_entry)); + cache_dir_desc_free(cache_entry); + } + /* Store the descriptor we just got. We are sure here that either we + * don't have the entry or we have a newer descriptor and the old one + * has been removed from the cache. */ + store_v3_desc_as_dir(desc); + + /* Update our total cache size with this entry for the OOM. This uses the + * old HS protocol cache subsystem for which we are tied with. */ + rend_cache_increment_allocation(cache_get_entry_size(desc)); + + /* XXX: Update HS statistics. We should have specific stats for v3. */ + + return 0; + + err: + return -1; +} + +/* Using the query which is the base64 encoded blinded key of a version 3 + * descriptor, lookup in our directory cache the entry. If found, 1 is + * returned and desc_out is populated with a newly allocated string being the + * encoded descriptor. If not found, 0 is returned and desc_out is untouched. + * On error, a negative value is returned and desc_out is untouched. */ +static int +cache_lookup_v3_as_dir(const char *query, const char **desc_out) +{ + int found = 0; + ed25519_public_key_t blinded_key; + const hs_cache_dir_descriptor_t *entry; + + tor_assert(query); + + /* Decode blinded key using the given query value. */ + if (ed25519_public_from_base64(&blinded_key, query) < 0) { + log_info(LD_REND, "Unable to decode the v3 HSDir query %s.", + safe_str_client(query)); + goto err; + } + + entry = lookup_v3_desc_as_dir(blinded_key.pubkey); + if (entry != NULL) { + found = 1; + if (desc_out) { + *desc_out = entry->encoded_desc; + } + } + + return found; + + err: + return -1; +} + +/* Clean the v3 cache by removing any entry that has expired using the + * <b>global_cutoff</b> value. If <b>global_cutoff</b> is 0, the cleaning + * process will use the lifetime found in the plaintext data section. Return + * the number of bytes cleaned. */ +STATIC size_t +cache_clean_v3_as_dir(time_t now, time_t global_cutoff) +{ + size_t bytes_removed = 0; + + /* Code flow error if this ever happens. */ + tor_assert(global_cutoff >= 0); + + if (!hs_cache_v3_dir) { /* No cache to clean. Just return. */ + return 0; + } + + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_dir, key, + hs_cache_dir_descriptor_t *, entry) { + size_t entry_size; + time_t cutoff = global_cutoff; + if (!cutoff) { + /* Cutoff is the lifetime of the entry found in the descriptor. */ + cutoff = now - entry->plaintext_data->lifetime_sec; + } + + /* If the entry has been created _after_ the cutoff, not expired so + * continue to the next entry in our v3 cache. */ + if (entry->created_ts > cutoff) { + continue; + } + /* Here, our entry has expired, remove and free. */ + MAP_DEL_CURRENT(key); + entry_size = cache_get_entry_size(entry); + bytes_removed += entry_size; + /* Entry is not in the cache anymore, destroy it. */ + cache_dir_desc_free(entry); + /* Update our cache entry allocation size for the OOM. */ + rend_cache_decrement_allocation(entry_size); + /* Logging. */ + { + char key_b64[BASE64_DIGEST256_LEN + 1]; + base64_encode(key_b64, sizeof(key_b64), (const char *) key, + DIGEST256_LEN, 0); + log_info(LD_REND, "Removing v3 descriptor '%s' from HSDir cache", + safe_str_client(key_b64)); + } + } DIGEST256MAP_FOREACH_END; + + return bytes_removed; +} + +/* Given an encoded descriptor, store it in the directory cache depending on + * which version it is. Return a negative value on error. On success, 0 is + * returned. */ +int +hs_cache_store_as_dir(const char *desc) +{ + hs_cache_dir_descriptor_t *dir_desc = NULL; + + tor_assert(desc); + + /* Create a new cache object. This can fail if the descriptor plaintext data + * is unparseable which in this case a log message will be triggered. */ + dir_desc = cache_dir_desc_new(desc); + if (dir_desc == NULL) { + goto err; + } + + /* Call the right function against the descriptor version. At this point, + * we are sure that the descriptor's version is supported else the + * decoding would have failed. */ + switch (dir_desc->plaintext_data->version) { + case HS_VERSION_THREE: + default: + if (cache_store_v3_as_dir(dir_desc) < 0) { + goto err; + } + break; + } + return 0; + + err: + cache_dir_desc_free(dir_desc); + return -1; +} + +/* Using the query, lookup in our directory cache the entry. If found, 1 is + * returned and desc_out is populated with a newly allocated string being + * the encoded descriptor. If not found, 0 is returned and desc_out is + * untouched. On error, a negative value is returned and desc_out is + * untouched. */ +int +hs_cache_lookup_as_dir(uint32_t version, const char *query, + const char **desc_out) +{ + int found; + + tor_assert(query); + /* This should never be called with an unsupported version. */ + tor_assert(hs_desc_is_supported_version(version)); + + switch (version) { + case HS_VERSION_THREE: + default: + found = cache_lookup_v3_as_dir(query, desc_out); + break; + } + + return found; +} + +/* Clean all directory caches using the current time now. */ +void +hs_cache_clean_as_dir(time_t now) +{ + time_t cutoff; + + /* Start with v2 cache cleaning. */ + cutoff = now - rend_cache_max_entry_lifetime(); + rend_cache_clean_v2_descs_as_dir(cutoff); + + /* Now, clean the v3 cache. Set the cutoff to 0 telling the cleanup function + * to compute the cutoff by itself using the lifetime value. */ + cache_clean_v3_as_dir(now, 0); +} + +/* Do a round of OOM cleanup on all directory caches. Return the amount of + * removed bytes. It is possible that the returned value is lower than + * min_remove_bytes if the caches get emptied out so the caller should be + * aware of this. */ +size_t +hs_cache_handle_oom(time_t now, size_t min_remove_bytes) +{ + time_t k; + size_t bytes_removed = 0; + + /* Our OOM handler called with 0 bytes to remove is a code flow error. */ + tor_assert(min_remove_bytes != 0); + + /* The algorithm is as follow. K is the oldest expected descriptor age. + * + * 1) Deallocate all entries from v2 cache that are older than K hours. + * 1.1) If the amount of remove bytes has been reached, stop. + * 2) Deallocate all entries from v3 cache that are older than K hours + * 2.1) If the amount of remove bytes has been reached, stop. + * 3) Set K = K - RendPostPeriod and repeat process until K is < 0. + * + * This ends up being O(Kn). + */ + + /* Set K to the oldest expected age in seconds which is the maximum + * lifetime of a cache entry. We'll use the v2 lifetime because it's much + * bigger than the v3 thus leading to cleaning older descriptors. */ + k = rend_cache_max_entry_lifetime(); + + do { + time_t cutoff; + + /* If K becomes negative, it means we've empty the caches so stop and + * return what we were able to cleanup. */ + if (k < 0) { + break; + } + /* Compute a cutoff value with K and the current time. */ + cutoff = now - k; + + /* Start by cleaning the v2 cache with that cutoff. */ + bytes_removed += rend_cache_clean_v2_descs_as_dir(cutoff); + + if (bytes_removed < min_remove_bytes) { + /* We haven't remove enough bytes so clean v3 cache. */ + bytes_removed += cache_clean_v3_as_dir(now, cutoff); + /* Decrement K by a post period to shorten the cutoff. */ + k -= get_options()->RendPostPeriod; + } + } while (bytes_removed < min_remove_bytes); + + return bytes_removed; +} + +/* Initialize the hidden service cache subsystem. */ +void +hs_cache_init(void) +{ + /* Calling this twice is very wrong code flow. */ + tor_assert(!hs_cache_v3_dir); + hs_cache_v3_dir = digest256map_new(); +} + +/* Cleanup the hidden service cache subsystem. */ +void +hs_cache_free_all(void) +{ + digest256map_free(hs_cache_v3_dir, cache_dir_desc_free_); + hs_cache_v3_dir = NULL; +} + diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h new file mode 100644 index 0000000000..01abb8002f --- /dev/null +++ b/src/or/hs_cache.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_cache.h + * \brief Header file for hs_cache.c + **/ + +#ifndef TOR_HS_CACHE_H +#define TOR_HS_CACHE_H + +#include <stdint.h> + +#include "crypto.h" +#include "crypto_ed25519.h" +#include "hs_common.h" +#include "hs_descriptor.h" +#include "torcert.h" + +/* Descriptor representation on the directory side which is a subset of + * information that the HSDir can decode and serve it. */ +typedef struct hs_cache_dir_descriptor_t { + /* This object is indexed using the blinded pubkey located in the plaintext + * data which is populated only once the descriptor has been successfully + * decoded and validated. This simply points to that pubkey. */ + const uint8_t *key; + + /* When does this entry has been created. Used to expire entries. */ + time_t created_ts; + + /* Descriptor plaintext information. Obviously, we can't decrypt the + * encrypted part of the descriptor. */ + hs_desc_plaintext_data_t *plaintext_data; + + /* Encoded descriptor which is basically in text form. It's a NUL terminated + * string thus safe to strlen(). */ + char *encoded_desc; +} hs_cache_dir_descriptor_t; + +/* Public API */ + +void hs_cache_init(void); +void hs_cache_free_all(void); +void hs_cache_clean_as_dir(time_t now); +size_t hs_cache_handle_oom(time_t now, size_t min_remove_bytes); + +/* Store and Lookup function. They are version agnostic that is depending on + * the requested version of the descriptor, it will be re-routed to the + * right function. */ +int hs_cache_store_as_dir(const char *desc); +int hs_cache_lookup_as_dir(uint32_t version, const char *query, + const char **desc_out); + +#ifdef HS_CACHE_PRIVATE + +STATIC size_t cache_clean_v3_as_dir(time_t now, time_t global_cutoff); + +#endif /* HS_CACHE_PRIVATE */ + +#endif /* TOR_HS_CACHE_H */ + diff --git a/src/or/hs_common.c b/src/or/hs_common.c new file mode 100644 index 0000000000..7dd97e7c7c --- /dev/null +++ b/src/or/hs_common.c @@ -0,0 +1,284 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_common.c + * \brief Contains code shared between different HS protocol version as well + * as useful data structures and accessors used by other subsystems. + * The rendcommon.c should only contains code relating to the v2 + * protocol. + **/ + +#include "or.h" + +#include "config.h" +#include "networkstatus.h" +#include "hs_common.h" +#include "rendcommon.h" + +/* Create a new rend_data_t for a specific given <b>version</b>. + * Return a pointer to the newly allocated data structure. */ +static rend_data_t * +rend_data_alloc(uint32_t version) +{ + rend_data_t *rend_data = NULL; + + switch (version) { + case HS_VERSION_TWO: + { + rend_data_v2_t *v2 = tor_malloc_zero(sizeof(*v2)); + v2->base_.version = HS_VERSION_TWO; + v2->base_.hsdirs_fp = smartlist_new(); + rend_data = &v2->base_; + break; + } + default: + tor_assert(0); + break; + } + + return rend_data; +} + +/** Free all storage associated with <b>data</b> */ +void +rend_data_free(rend_data_t *data) +{ + if (!data) { + return; + } + /* By using our allocation function, this should always be set. */ + tor_assert(data->hsdirs_fp); + /* Cleanup the HSDir identity digest. */ + SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d)); + smartlist_free(data->hsdirs_fp); + /* Depending on the version, cleanup. */ + switch (data->version) { + case HS_VERSION_TWO: + { + rend_data_v2_t *v2_data = TO_REND_DATA_V2(data); + tor_free(v2_data); + break; + } + default: + tor_assert(0); + } +} + +/* Allocate and return a deep copy of <b>data</b>. */ +rend_data_t * +rend_data_dup(const rend_data_t *data) +{ + rend_data_t *data_dup = NULL; + smartlist_t *hsdirs_fp = smartlist_new(); + + tor_assert(data); + tor_assert(data->hsdirs_fp); + + SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp, + smartlist_add(hsdirs_fp, tor_memdup(fp, DIGEST_LEN))); + + switch (data->version) { + case HS_VERSION_TWO: + { + rend_data_v2_t *v2_data = tor_memdup(TO_REND_DATA_V2(data), + sizeof(*v2_data)); + data_dup = &v2_data->base_; + data_dup->hsdirs_fp = hsdirs_fp; + break; + } + default: + tor_assert(0); + break; + } + + return data_dup; +} + +/* Compute the descriptor ID for each HS descriptor replica and save them. A + * valid onion address must be present in the <b>rend_data</b>. + * + * Return 0 on success else -1. */ +static int +compute_desc_id(rend_data_t *rend_data) +{ + int ret = 0; + unsigned replica; + time_t now = time(NULL); + + tor_assert(rend_data); + + switch (rend_data->version) { + case HS_VERSION_TWO: + { + rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data); + /* Compute descriptor ID for each replicas. */ + for (replica = 0; replica < ARRAY_LENGTH(v2_data->descriptor_id); + replica++) { + ret = rend_compute_v2_desc_id(v2_data->descriptor_id[replica], + v2_data->onion_address, + v2_data->descriptor_cookie, + now, replica); + if (ret < 0) { + goto end; + } + } + break; + } + default: + tor_assert(0); + } + + end: + return ret; +} + +/* Allocate and initialize a rend_data_t object for a service using the + * provided arguments. All arguments are optional (can be NULL), except from + * <b>onion_address</b> which MUST be set. The <b>pk_digest</b> is the hash of + * the service private key. The <b>cookie</b> is the rendezvous cookie and + * <b>auth_type</b> is which authentiation this service is configured with. + * + * Return a valid rend_data_t pointer. This only returns a version 2 object of + * rend_data_t. */ +rend_data_t * +rend_data_service_create(const char *onion_address, const char *pk_digest, + const uint8_t *cookie, rend_auth_type_t auth_type) +{ + /* Create a rend_data_t object for version 2. */ + rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO); + rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data); + + /* We need at least one else the call is wrong. */ + tor_assert(onion_address != NULL); + + if (pk_digest) { + memcpy(v2->rend_pk_digest, pk_digest, sizeof(v2->rend_pk_digest)); + } + if (cookie) { + memcpy(rend_data->rend_cookie, cookie, sizeof(rend_data->rend_cookie)); + } + + strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address)); + v2->auth_type = auth_type; + + return rend_data; +} + +/* Allocate and initialize a rend_data_t object for a client request using the + * given arguments. Either an onion address or a descriptor ID is needed. Both + * can be given but in this case only the onion address will be used to make + * the descriptor fetch. The <b>cookie</b> is the rendezvous cookie and + * <b>auth_type</b> is which authentiation the service is configured with. + * + * Return a valid rend_data_t pointer or NULL on error meaning the + * descriptor IDs couldn't be computed from the given data. */ +rend_data_t * +rend_data_client_create(const char *onion_address, const char *desc_id, + const char *cookie, rend_auth_type_t auth_type) +{ + /* Create a rend_data_t object for version 2. */ + rend_data_t *rend_data = rend_data_alloc(HS_VERSION_TWO); + rend_data_v2_t *v2= TO_REND_DATA_V2(rend_data); + + /* We need at least one else the call is wrong. */ + tor_assert(onion_address != NULL || desc_id != NULL); + + if (cookie) { + memcpy(v2->descriptor_cookie, cookie, sizeof(v2->descriptor_cookie)); + } + if (desc_id) { + memcpy(v2->desc_id_fetch, desc_id, sizeof(v2->desc_id_fetch)); + } + if (onion_address) { + strlcpy(v2->onion_address, onion_address, sizeof(v2->onion_address)); + if (compute_desc_id(rend_data) < 0) { + goto error; + } + } + + v2->auth_type = auth_type; + + return rend_data; + + error: + rend_data_free(rend_data); + return NULL; +} + +/* Return the onion address from the rend data. Depending on the version, + * the size of the address can vary but it's always NUL terminated. */ +const char * +rend_data_get_address(const rend_data_t *rend_data) +{ + tor_assert(rend_data); + + switch (rend_data->version) { + case HS_VERSION_TWO: + return TO_REND_DATA_V2(rend_data)->onion_address; + default: + /* We should always have a supported version. */ + tor_assert(0); + } +} + +/* Return the descriptor ID for a specific replica number from the rend + * data. The returned data is a binary digest and depending on the version its + * size can vary. The size of the descriptor ID is put in <b>len_out</b> if + * non NULL. */ +const char * +rend_data_get_desc_id(const rend_data_t *rend_data, uint8_t replica, + size_t *len_out) +{ + tor_assert(rend_data); + + switch (rend_data->version) { + case HS_VERSION_TWO: + tor_assert(replica < REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS); + if (len_out) { + *len_out = DIGEST_LEN; + } + return TO_REND_DATA_V2(rend_data)->descriptor_id[replica]; + default: + /* We should always have a supported version. */ + tor_assert(0); + } +} + +/* Return the public key digest using the given <b>rend_data</b>. The size of + * the digest is put in <b>len_out</b> (if set) which can differ depending on + * the version. */ +const uint8_t * +rend_data_get_pk_digest(const rend_data_t *rend_data, size_t *len_out) +{ + tor_assert(rend_data); + + switch (rend_data->version) { + case HS_VERSION_TWO: + { + const rend_data_v2_t *v2_data = TO_REND_DATA_V2(rend_data); + if (len_out) { + *len_out = sizeof(v2_data->rend_pk_digest); + } + return (const uint8_t *) v2_data->rend_pk_digest; + } + default: + /* We should always have a supported version. */ + tor_assert(0); + } +} + +/* Return true iff the Onion Services protocol version 3 is enabled. This only + * considers the consensus parameter. If the parameter is not found, the + * default is that it's enabled. */ +int +hs_v3_protocol_is_enabled(void) +{ + /* This consensus param controls if the the onion services version 3 is + * enabled or not which is the first version of the next generation + * (proposal 224). If this option is set to 0, the tor daemon won't support + * the protocol as either a relay, directory, service or client. By default, + * it's enabled if the parameter is not found. */ + return networkstatus_get_param(NULL, "EnableOnionServicesV3", 1, 0, 1); +} + diff --git a/src/or/hs_common.h b/src/or/hs_common.h new file mode 100644 index 0000000000..2502f35ad4 --- /dev/null +++ b/src/or/hs_common.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_common.h + * \brief Header file containing common data for the whole HS subsytem. + **/ + +#ifndef TOR_HS_COMMON_H +#define TOR_HS_COMMON_H + +#include "or.h" + +/* Protocol version 2. Use this instead of hardcoding "2" in the code base, + * this adds a clearer semantic to the value when used. */ +#define HS_VERSION_TWO 2 +/* Version 3 of the protocol (prop224). */ +#define HS_VERSION_THREE 3 + +void rend_data_free(rend_data_t *data); +rend_data_t *rend_data_dup(const rend_data_t *data); +rend_data_t *rend_data_client_create(const char *onion_address, + const char *desc_id, + const char *cookie, + rend_auth_type_t auth_type); +rend_data_t *rend_data_service_create(const char *onion_address, + const char *pk_digest, + const uint8_t *cookie, + rend_auth_type_t auth_type); +const char *rend_data_get_address(const rend_data_t *rend_data); +const char *rend_data_get_desc_id(const rend_data_t *rend_data, + uint8_t replica, size_t *len_out); +const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, + size_t *len_out); + +int hs_v3_protocol_is_enabled(void); + +#endif /* TOR_HS_COMMON_H */ + diff --git a/src/or/hs_descriptor.c b/src/or/hs_descriptor.c new file mode 100644 index 0000000000..37aa1d745e --- /dev/null +++ b/src/or/hs_descriptor.c @@ -0,0 +1,1901 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_descriptor.c + * \brief Handle hidden service descriptor encoding/decoding. + **/ + +/* For unit tests.*/ +#define HS_DESCRIPTOR_PRIVATE + +#include "hs_descriptor.h" + +#include "or.h" +#include "ed25519_cert.h" /* Trunnel interface. */ +#include "parsecommon.h" +#include "rendcache.h" +#include "torcert.h" /* tor_cert_encode_ed22519() */ + +/* Constant string value used for the descriptor format. */ +#define str_hs_desc "hs-descriptor" +#define str_desc_cert "descriptor-signing-key-cert" +#define str_rev_counter "revision-counter" +#define str_encrypted "encrypted" +#define str_signature "signature" +#define str_lifetime "descriptor-lifetime" +/* Constant string value for the encrypted part of the descriptor. */ +#define str_create2_formats "create2-formats" +#define str_auth_required "authentication-required" +#define str_single_onion "single-onion-service" +#define str_intro_point "introduction-point" +#define str_ip_auth_key "auth-key" +#define str_ip_enc_key "enc-key" +#define str_ip_enc_key_cert "enc-key-certification" +#define str_intro_point_start "\n" str_intro_point " " +/* Constant string value for the construction to encrypt the encrypted data + * section. */ +#define str_enc_hsdir_data "hsdir-encrypted-data" +/* Prefix required to compute/verify HS desc signatures */ +#define str_desc_sig_prefix "Tor onion service descriptor sig v3" + +/* Authentication supported types. */ +static const struct { + hs_desc_auth_type_t type; + const char *identifier; +} auth_types[] = { + { HS_DESC_AUTH_PASSWORD, "password" }, + { HS_DESC_AUTH_ED25519, "ed25519" }, + /* Indicate end of array. */ + { 0, NULL } +}; + +/* Descriptor ruleset. */ +static token_rule_t hs_desc_v3_token_table[] = { + T1_START(str_hs_desc, R_HS_DESCRIPTOR, EQ(1), NO_OBJ), + T1(str_lifetime, R3_DESC_LIFETIME, EQ(1), NO_OBJ), + T1(str_desc_cert, R3_DESC_SIGNING_CERT, NO_ARGS, NEED_OBJ), + T1(str_rev_counter, R3_REVISION_COUNTER, EQ(1), NO_OBJ), + T1(str_encrypted, R3_ENCRYPTED, NO_ARGS, NEED_OBJ), + T1_END(str_signature, R3_SIGNATURE, EQ(1), NO_OBJ), + END_OF_TABLE +}; + +/* Descriptor ruleset for the encrypted section. */ +static token_rule_t hs_desc_encrypted_v3_token_table[] = { + T1_START(str_create2_formats, R3_CREATE2_FORMATS, CONCAT_ARGS, NO_OBJ), + T01(str_auth_required, R3_AUTHENTICATION_REQUIRED, ARGS, NO_OBJ), + T01(str_single_onion, R3_SINGLE_ONION_SERVICE, ARGS, NO_OBJ), + END_OF_TABLE +}; + +/* Descriptor ruleset for the introduction points section. */ +static token_rule_t hs_desc_intro_point_v3_token_table[] = { + T1_START(str_intro_point, R3_INTRODUCTION_POINT, EQ(1), NO_OBJ), + T1(str_ip_auth_key, R3_INTRO_AUTH_KEY, NO_ARGS, NEED_OBJ), + T1(str_ip_enc_key, R3_INTRO_ENC_KEY, ARGS, OBJ_OK), + T1_END(str_ip_enc_key_cert, R3_INTRO_ENC_KEY_CERTIFICATION, + NO_ARGS, NEED_OBJ), + END_OF_TABLE +}; + +/* Free a descriptor intro point object. */ +STATIC void +desc_intro_point_free(hs_desc_intro_point_t *ip) +{ + if (!ip) { + return; + } + if (ip->link_specifiers) { + SMARTLIST_FOREACH(ip->link_specifiers, hs_desc_link_specifier_t *, + ls, tor_free(ls)); + smartlist_free(ip->link_specifiers); + } + tor_cert_free(ip->auth_key_cert); + if (ip->enc_key_type == HS_DESC_KEY_TYPE_LEGACY) { + crypto_pk_free(ip->enc_key.legacy); + } + tor_free(ip); +} + +/* Free the content of the plaintext section of a descriptor. */ +static void +desc_plaintext_data_free_contents(hs_desc_plaintext_data_t *desc) +{ + if (!desc) { + return; + } + + if (desc->encrypted_blob) { + tor_free(desc->encrypted_blob); + } + tor_cert_free(desc->signing_key_cert); + + memwipe(desc, 0, sizeof(*desc)); +} + +/* Free the content of the encrypted section of a descriptor. */ +static void +desc_encrypted_data_free_contents(hs_desc_encrypted_data_t *desc) +{ + if (!desc) { + return; + } + + if (desc->auth_types) { + SMARTLIST_FOREACH(desc->auth_types, char *, a, tor_free(a)); + smartlist_free(desc->auth_types); + } + if (desc->intro_points) { + SMARTLIST_FOREACH(desc->intro_points, hs_desc_intro_point_t *, ip, + desc_intro_point_free(ip)); + smartlist_free(desc->intro_points); + } + memwipe(desc, 0, sizeof(*desc)); +} + +/* === ENCODING === */ + +/* Encode the given link specifier objects into a newly allocated string. + * This can't fail so caller can always assume a valid string being + * returned. */ +STATIC char * +encode_link_specifiers(const smartlist_t *specs) +{ + char *encoded_b64 = NULL; + link_specifier_list_t *lslist = link_specifier_list_new(); + + tor_assert(specs); + /* No link specifiers is a code flow error, can't happen. */ + tor_assert(smartlist_len(specs) > 0); + tor_assert(smartlist_len(specs) <= UINT8_MAX); + + link_specifier_list_set_n_spec(lslist, smartlist_len(specs)); + + SMARTLIST_FOREACH_BEGIN(specs, const hs_desc_link_specifier_t *, + spec) { + link_specifier_t *ls = link_specifier_new(); + link_specifier_set_ls_type(ls, spec->type); + + switch (spec->type) { + case LS_IPV4: + link_specifier_set_un_ipv4_addr(ls, + tor_addr_to_ipv4h(&spec->u.ap.addr)); + link_specifier_set_un_ipv4_port(ls, spec->u.ap.port); + /* Four bytes IPv4 and two bytes port. */ + link_specifier_set_ls_len(ls, sizeof(spec->u.ap.addr.addr.in_addr) + + sizeof(spec->u.ap.port)); + break; + case LS_IPV6: + { + size_t addr_len = link_specifier_getlen_un_ipv6_addr(ls); + const uint8_t *in6_addr = tor_addr_to_in6_addr8(&spec->u.ap.addr); + uint8_t *ipv6_array = link_specifier_getarray_un_ipv6_addr(ls); + memcpy(ipv6_array, in6_addr, addr_len); + link_specifier_set_un_ipv6_port(ls, spec->u.ap.port); + /* Sixteen bytes IPv6 and two bytes port. */ + link_specifier_set_ls_len(ls, addr_len + sizeof(spec->u.ap.port)); + break; + } + case LS_LEGACY_ID: + { + size_t legacy_id_len = link_specifier_getlen_un_legacy_id(ls); + uint8_t *legacy_id_array = link_specifier_getarray_un_legacy_id(ls); + memcpy(legacy_id_array, spec->u.legacy_id, legacy_id_len); + link_specifier_set_ls_len(ls, legacy_id_len); + break; + } + default: + tor_assert(0); + } + + link_specifier_list_add_spec(lslist, ls); + } SMARTLIST_FOREACH_END(spec); + + { + uint8_t *encoded; + ssize_t encoded_len, encoded_b64_len, ret; + + encoded_len = link_specifier_list_encoded_len(lslist); + tor_assert(encoded_len > 0); + encoded = tor_malloc_zero(encoded_len); + ret = link_specifier_list_encode(encoded, encoded_len, lslist); + tor_assert(ret == encoded_len); + + /* Base64 encode our binary format. Add extra NUL byte for the base64 + * encoded value. */ + encoded_b64_len = base64_encode_size(encoded_len, 0) + 1; + encoded_b64 = tor_malloc_zero(encoded_b64_len); + ret = base64_encode(encoded_b64, encoded_b64_len, (const char *) encoded, + encoded_len, 0); + tor_assert(ret == (encoded_b64_len - 1)); + tor_free(encoded); + } + + link_specifier_list_free(lslist); + return encoded_b64; +} + +/* Encode an introduction point encryption key and return a newly allocated + * string with it. On failure, return NULL. */ +static char * +encode_enc_key(const ed25519_keypair_t *sig_key, + const hs_desc_intro_point_t *ip) +{ + char *encoded = NULL; + time_t now = time(NULL); + + tor_assert(sig_key); + tor_assert(ip); + + switch (ip->enc_key_type) { + case HS_DESC_KEY_TYPE_LEGACY: + { + char *key_str, b64_cert[256]; + ssize_t cert_len; + size_t key_str_len; + uint8_t *cert_data = NULL; + + /* Create cross certification cert. */ + cert_len = tor_make_rsa_ed25519_crosscert(&sig_key->pubkey, + ip->enc_key.legacy, + now + HS_DESC_CERT_LIFETIME, + &cert_data); + if (cert_len < 0) { + log_warn(LD_REND, "Unable to create legacy crosscert."); + goto err; + } + /* Encode cross cert. */ + if (base64_encode(b64_cert, sizeof(b64_cert), (const char *) cert_data, + cert_len, BASE64_ENCODE_MULTILINE) < 0) { + tor_free(cert_data); + log_warn(LD_REND, "Unable to encode legacy crosscert."); + goto err; + } + tor_free(cert_data); + /* Convert the encryption key to a string. */ + if (crypto_pk_write_public_key_to_string(ip->enc_key.legacy, &key_str, + &key_str_len) < 0) { + log_warn(LD_REND, "Unable to encode legacy encryption key."); + goto err; + } + tor_asprintf(&encoded, + "%s legacy\n%s" /* Newline is added by the call above. */ + "%s\n" + "-----BEGIN CROSSCERT-----\n" + "%s" + "-----END CROSSCERT-----", + str_ip_enc_key, key_str, + str_ip_enc_key_cert, b64_cert); + tor_free(key_str); + break; + } + case HS_DESC_KEY_TYPE_CURVE25519: + { + int signbit, ret; + char *encoded_cert, key_fp_b64[CURVE25519_BASE64_PADDED_LEN + 1]; + ed25519_keypair_t curve_kp; + + if (ed25519_keypair_from_curve25519_keypair(&curve_kp, &signbit, + &ip->enc_key.curve25519)) { + goto err; + } + tor_cert_t *cross_cert = tor_cert_create(&curve_kp, + CERT_TYPE_CROSS_HS_IP_KEYS, + &sig_key->pubkey, now, + HS_DESC_CERT_LIFETIME, + CERT_FLAG_INCLUDE_SIGNING_KEY); + memwipe(&curve_kp, 0, sizeof(curve_kp)); + if (!cross_cert) { + goto err; + } + ret = tor_cert_encode_ed22519(cross_cert, &encoded_cert); + tor_cert_free(cross_cert); + if (ret) { + goto err; + } + if (curve25519_public_to_base64(key_fp_b64, + &ip->enc_key.curve25519.pubkey) < 0) { + tor_free(encoded_cert); + goto err; + } + tor_asprintf(&encoded, + "%s ntor %s\n" + "%s\n%s", + str_ip_enc_key, key_fp_b64, + str_ip_enc_key_cert, encoded_cert); + tor_free(encoded_cert); + break; + } + default: + tor_assert(0); + } + + err: + return encoded; +} + +/* Encode an introduction point object and return a newly allocated string + * with it. On failure, return NULL. */ +static char * +encode_intro_point(const ed25519_keypair_t *sig_key, + const hs_desc_intro_point_t *ip) +{ + char *encoded_ip = NULL; + smartlist_t *lines = smartlist_new(); + + tor_assert(ip); + tor_assert(sig_key); + + /* Encode link specifier. */ + { + char *ls_str = encode_link_specifiers(ip->link_specifiers); + smartlist_add_asprintf(lines, "%s %s", str_intro_point, ls_str); + tor_free(ls_str); + } + + /* Authentication key encoding. */ + { + char *encoded_cert; + if (tor_cert_encode_ed22519(ip->auth_key_cert, &encoded_cert) < 0) { + goto err; + } + smartlist_add_asprintf(lines, "%s\n%s", str_ip_auth_key, encoded_cert); + tor_free(encoded_cert); + } + + /* Encryption key encoding. */ + { + char *encoded_enc_key = encode_enc_key(sig_key, ip); + if (encoded_enc_key == NULL) { + goto err; + } + smartlist_add_asprintf(lines, "%s", encoded_enc_key); + tor_free(encoded_enc_key); + } + + /* Join them all in one blob of text. */ + encoded_ip = smartlist_join_strings(lines, "\n", 1, NULL); + + err: + SMARTLIST_FOREACH(lines, char *, l, tor_free(l)); + smartlist_free(lines); + return encoded_ip; +} + +/* Using a given decriptor object, build the secret input needed for the + * KDF and put it in the dst pointer which is an already allocated buffer + * of size dstlen. */ +static void +build_secret_input(const hs_descriptor_t *desc, uint8_t *dst, size_t dstlen) +{ + size_t offset = 0; + + tor_assert(desc); + tor_assert(dst); + tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN <= dstlen); + + /* XXX use the destination length as the memcpy length */ + /* Copy blinded public key. */ + memcpy(dst, desc->plaintext_data.blinded_kp.pubkey.pubkey, + sizeof(desc->plaintext_data.blinded_kp.pubkey.pubkey)); + offset += sizeof(desc->plaintext_data.blinded_kp.pubkey.pubkey); + /* Copy subcredential. */ + memcpy(dst + offset, desc->subcredential, sizeof(desc->subcredential)); + offset += sizeof(desc->subcredential); + /* Copy revision counter value. */ + set_uint64(dst + offset, tor_ntohll(desc->plaintext_data.revision_counter)); + offset += sizeof(uint64_t); + tor_assert(HS_DESC_ENCRYPTED_SECRET_INPUT_LEN == offset); +} + +/* Do the KDF construction and put the resulting data in key_out which is of + * key_out_len length. It uses SHAKE-256 as specified in the spec. */ +static void +build_kdf_key(const hs_descriptor_t *desc, + const uint8_t *salt, size_t salt_len, + uint8_t *key_out, size_t key_out_len) +{ + uint8_t secret_input[HS_DESC_ENCRYPTED_SECRET_INPUT_LEN]; + crypto_xof_t *xof; + + tor_assert(desc); + tor_assert(salt); + tor_assert(key_out); + + /* Build the secret input for the KDF computation. */ + build_secret_input(desc, secret_input, sizeof(secret_input)); + + xof = crypto_xof_new(); + /* Feed our KDF. [SHAKE it like a polaroid picture --Yawning]. */ + crypto_xof_add_bytes(xof, secret_input, sizeof(secret_input)); + crypto_xof_add_bytes(xof, salt, salt_len); + crypto_xof_add_bytes(xof, (const uint8_t *) str_enc_hsdir_data, + strlen(str_enc_hsdir_data)); + /* Eat from our KDF. */ + crypto_xof_squeeze_bytes(xof, key_out, key_out_len); + crypto_xof_free(xof); + memwipe(secret_input, 0, sizeof(secret_input)); +} + +/* Using the given descriptor and salt, run it through our KDF function and + * then extract a secret key in key_out, the IV in iv_out and MAC in mac_out. + * This function can't fail. */ +static void +build_secret_key_iv_mac(const hs_descriptor_t *desc, + const uint8_t *salt, size_t salt_len, + uint8_t *key_out, size_t key_len, + uint8_t *iv_out, size_t iv_len, + uint8_t *mac_out, size_t mac_len) +{ + size_t offset = 0; + uint8_t kdf_key[HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN]; + + tor_assert(desc); + tor_assert(salt); + tor_assert(key_out); + tor_assert(iv_out); + tor_assert(mac_out); + + build_kdf_key(desc, salt, salt_len, kdf_key, sizeof(kdf_key)); + /* Copy the bytes we need for both the secret key and IV. */ + memcpy(key_out, kdf_key, key_len); + offset += key_len; + memcpy(iv_out, kdf_key + offset, iv_len); + offset += iv_len; + memcpy(mac_out, kdf_key + offset, mac_len); + /* Extra precaution to make sure we are not out of bound. */ + tor_assert((offset + mac_len) == sizeof(kdf_key)); + memwipe(kdf_key, 0, sizeof(kdf_key)); +} + +/* Using a key, salt and encrypted payload, build a MAC and put it in mac_out. + * We use SHA3-256 for the MAC computation. + * This function can't fail. */ +static void +build_mac(const uint8_t *mac_key, size_t mac_key_len, + const uint8_t *salt, size_t salt_len, + const uint8_t *encrypted, size_t encrypted_len, + uint8_t *mac_out, size_t mac_len) +{ + crypto_digest_t *digest; + + const uint64_t mac_len_netorder = tor_htonll(mac_key_len); + const uint64_t salt_len_netorder = tor_htonll(salt_len); + + tor_assert(mac_key); + tor_assert(salt); + tor_assert(encrypted); + tor_assert(mac_out); + + digest = crypto_digest256_new(DIGEST_SHA3_256); + /* As specified in section 2.5 of proposal 224, first add the mac key + * then add the salt first and then the encrypted section. */ + + crypto_digest_add_bytes(digest, (const char *) &mac_len_netorder, 8); + crypto_digest_add_bytes(digest, (const char *) mac_key, mac_key_len); + crypto_digest_add_bytes(digest, (const char *) &salt_len_netorder, 8); + crypto_digest_add_bytes(digest, (const char *) salt, salt_len); + crypto_digest_add_bytes(digest, (const char *) encrypted, encrypted_len); + crypto_digest_get_digest(digest, (char *) mac_out, mac_len); + crypto_digest_free(digest); +} + +/* Given a source length, return the new size including padding for the + * plaintext encryption. */ +static size_t +compute_padded_plaintext_length(size_t plaintext_len) +{ + size_t plaintext_padded_len; + + /* Make sure we won't overflow. */ + tor_assert(plaintext_len <= + (SIZE_T_CEILING - HS_DESC_PLAINTEXT_PADDING_MULTIPLE)); + + /* Get the extra length we need to add. For example, if srclen is 234 bytes, + * this will expand to (2 * 128) == 256 thus an extra 22 bytes. */ + plaintext_padded_len = CEIL_DIV(plaintext_len, + HS_DESC_PLAINTEXT_PADDING_MULTIPLE) * + HS_DESC_PLAINTEXT_PADDING_MULTIPLE; + /* Can never be extra careful. Make sure we are _really_ padded. */ + tor_assert(!(plaintext_padded_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE)); + return plaintext_padded_len; +} + +/* Given a buffer, pad it up to the encrypted section padding requirement. Set + * the newly allocated string in padded_out and return the length of the + * padded buffer. */ +STATIC size_t +build_plaintext_padding(const char *plaintext, size_t plaintext_len, + uint8_t **padded_out) +{ + size_t padded_len; + uint8_t *padded; + + tor_assert(plaintext); + tor_assert(padded_out); + + /* Allocate the final length including padding. */ + padded_len = compute_padded_plaintext_length(plaintext_len); + tor_assert(padded_len >= plaintext_len); + padded = tor_malloc_zero(padded_len); + + memcpy(padded, plaintext, plaintext_len); + *padded_out = padded; + return padded_len; +} + +/* Using a key, IV and plaintext data of length plaintext_len, create the + * encrypted section by encrypting it and setting encrypted_out with the + * data. Return size of the encrypted data buffer. */ +static size_t +build_encrypted(const uint8_t *key, const uint8_t *iv, const char *plaintext, + size_t plaintext_len, uint8_t **encrypted_out) +{ + size_t encrypted_len; + uint8_t *padded_plaintext, *encrypted; + crypto_cipher_t *cipher; + + tor_assert(key); + tor_assert(iv); + tor_assert(plaintext); + tor_assert(encrypted_out); + + /* This creates a cipher for AES128. It can't fail. */ + cipher = crypto_cipher_new_with_iv((const char *) key, (const char *) iv); + /* This can't fail. */ + encrypted_len = build_plaintext_padding(plaintext, plaintext_len, + &padded_plaintext); + /* Extra precautions that we have a valie padding length. */ + tor_assert(encrypted_len <= HS_DESC_PADDED_PLAINTEXT_MAX_LEN); + tor_assert(!(encrypted_len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE)); + /* We use a stream cipher so the encrypted length will be the same as the + * plaintext padded length. */ + encrypted = tor_malloc_zero(encrypted_len); + /* This can't fail. */ + crypto_cipher_encrypt(cipher, (char *) encrypted, + (const char *) padded_plaintext, encrypted_len); + *encrypted_out = encrypted; + /* Cleanup. */ + crypto_cipher_free(cipher); + tor_free(padded_plaintext); + return encrypted_len; +} + +/* Encrypt the given plaintext buffer and using the descriptor to get the + * keys. Set encrypted_out with the encrypted data and return the length of + * it. */ +static size_t +encrypt_descriptor_data(const hs_descriptor_t *desc, const char *plaintext, + char **encrypted_out) +{ + char *final_blob; + size_t encrypted_len, final_blob_len, offset = 0; + uint8_t *encrypted; + uint8_t salt[HS_DESC_ENCRYPTED_SALT_LEN]; + uint8_t secret_key[CIPHER_KEY_LEN], secret_iv[CIPHER_IV_LEN]; + uint8_t mac_key[DIGEST256_LEN], mac[DIGEST256_LEN]; + + tor_assert(desc); + tor_assert(plaintext); + tor_assert(encrypted_out); + + /* Get our salt. The returned bytes are already hashed. */ + crypto_strongest_rand(salt, sizeof(salt)); + + /* KDF construction resulting in a key from which the secret key, IV and MAC + * key are extracted which is what we need for the encryption. */ + build_secret_key_iv_mac(desc, salt, sizeof(salt), + secret_key, sizeof(secret_key), + secret_iv, sizeof(secret_iv), + mac_key, sizeof(mac_key)); + + /* Build the encrypted part that is do the actual encryption. */ + encrypted_len = build_encrypted(secret_key, secret_iv, plaintext, + strlen(plaintext), &encrypted); + memwipe(secret_key, 0, sizeof(secret_key)); + memwipe(secret_iv, 0, sizeof(secret_iv)); + /* This construction is specified in section 2.5 of proposal 224. */ + final_blob_len = sizeof(salt) + encrypted_len + DIGEST256_LEN; + final_blob = tor_malloc_zero(final_blob_len); + + /* Build the MAC. */ + build_mac(mac_key, sizeof(mac_key), salt, sizeof(salt), + encrypted, encrypted_len, mac, sizeof(mac)); + memwipe(mac_key, 0, sizeof(mac_key)); + + /* The salt is the first value. */ + memcpy(final_blob, salt, sizeof(salt)); + offset = sizeof(salt); + /* Second value is the encrypted data. */ + memcpy(final_blob + offset, encrypted, encrypted_len); + offset += encrypted_len; + /* Third value is the MAC. */ + memcpy(final_blob + offset, mac, sizeof(mac)); + offset += sizeof(mac); + /* Cleanup the buffers. */ + memwipe(salt, 0, sizeof(salt)); + memwipe(encrypted, 0, encrypted_len); + tor_free(encrypted); + /* Extra precaution. */ + tor_assert(offset == final_blob_len); + + *encrypted_out = final_blob; + return final_blob_len; +} + +/* Take care of encoding the encrypted data section and then encrypting it + * with the descriptor's key. A newly allocated NUL terminated string pointer + * containing the encrypted encoded blob is put in encrypted_blob_out. Return + * 0 on success else a negative value. */ +static int +encode_encrypted_data(const hs_descriptor_t *desc, + char **encrypted_blob_out) +{ + int ret = -1; + char *encoded_str, *encrypted_blob; + smartlist_t *lines = smartlist_new(); + + tor_assert(desc); + tor_assert(encrypted_blob_out); + + /* Build the start of the section prior to the introduction points. */ + { + if (!desc->encrypted_data.create2_ntor) { + log_err(LD_BUG, "HS desc doesn't have recognized handshake type."); + goto err; + } + smartlist_add_asprintf(lines, "%s %d\n", str_create2_formats, + ONION_HANDSHAKE_TYPE_NTOR); + + if (desc->encrypted_data.auth_types && + smartlist_len(desc->encrypted_data.auth_types)) { + /* Put the authentication-required line. */ + char *buf = smartlist_join_strings(desc->encrypted_data.auth_types, " ", + 0, NULL); + smartlist_add_asprintf(lines, "%s %s\n", str_auth_required, buf); + tor_free(buf); + } + + if (desc->encrypted_data.single_onion_service) { + smartlist_add_asprintf(lines, "%s\n", str_single_onion); + } + } + + /* Build the introduction point(s) section. */ + SMARTLIST_FOREACH_BEGIN(desc->encrypted_data.intro_points, + const hs_desc_intro_point_t *, ip) { + char *encoded_ip = encode_intro_point(&desc->plaintext_data.signing_kp, + ip); + if (encoded_ip == NULL) { + log_err(LD_BUG, "HS desc intro point is malformed."); + goto err; + } + smartlist_add(lines, encoded_ip); + } SMARTLIST_FOREACH_END(ip); + + /* Build the entire encrypted data section into one encoded plaintext and + * then encrypt it. */ + encoded_str = smartlist_join_strings(lines, "", 0, NULL); + + /* Encrypt the section into an encrypted blob that we'll base64 encode + * before returning it. */ + { + char *enc_b64; + ssize_t enc_b64_len, ret_len, enc_len; + + enc_len = encrypt_descriptor_data(desc, encoded_str, &encrypted_blob); + tor_free(encoded_str); + /* Get the encoded size plus a NUL terminating byte. */ + enc_b64_len = base64_encode_size(enc_len, BASE64_ENCODE_MULTILINE) + 1; + enc_b64 = tor_malloc_zero(enc_b64_len); + /* Base64 the encrypted blob before returning it. */ + ret_len = base64_encode(enc_b64, enc_b64_len, encrypted_blob, enc_len, + BASE64_ENCODE_MULTILINE); + /* Return length doesn't count the NUL byte. */ + tor_assert(ret_len == (enc_b64_len - 1)); + tor_free(encrypted_blob); + *encrypted_blob_out = enc_b64; + } + /* Success! */ + ret = 0; + + err: + SMARTLIST_FOREACH(lines, char *, l, tor_free(l)); + smartlist_free(lines); + return ret; +} + +/* Encode a v3 HS descriptor. Return 0 on success and set encoded_out to the + * newly allocated string of the encoded descriptor. On error, -1 is returned + * and encoded_out is untouched. */ +static int +desc_encode_v3(const hs_descriptor_t *desc, char **encoded_out) +{ + int ret = -1; + char *encoded_str = NULL; + size_t encoded_len; + smartlist_t *lines = smartlist_new(); + + tor_assert(desc); + tor_assert(encoded_out); + tor_assert(desc->plaintext_data.version == 3); + + /* Build the non-encrypted values. */ + { + char *encoded_cert; + /* Encode certificate then create the first line of the descriptor. */ + if (desc->plaintext_data.signing_key_cert->cert_type + != CERT_TYPE_SIGNING_HS_DESC) { + log_err(LD_BUG, "HS descriptor signing key has an unexpected cert type " + "(%d)", (int) desc->plaintext_data.signing_key_cert->cert_type); + goto err; + } + if (tor_cert_encode_ed22519(desc->plaintext_data.signing_key_cert, + &encoded_cert) < 0) { + /* The function will print error logs. */ + goto err; + } + /* Create the hs descriptor line. */ + smartlist_add_asprintf(lines, "%s %" PRIu32, str_hs_desc, + desc->plaintext_data.version); + /* Add the descriptor lifetime line (in minutes). */ + smartlist_add_asprintf(lines, "%s %" PRIu32, str_lifetime, + desc->plaintext_data.lifetime_sec / 60); + /* Create the descriptor certificate line. */ + smartlist_add_asprintf(lines, "%s\n%s", str_desc_cert, encoded_cert); + tor_free(encoded_cert); + /* Create the revision counter line. */ + smartlist_add_asprintf(lines, "%s %" PRIu64, str_rev_counter, + desc->plaintext_data.revision_counter); + } + + /* Build the encrypted data section. */ + { + char *enc_b64_blob=NULL; + if (encode_encrypted_data(desc, &enc_b64_blob) < 0) { + goto err; + } + smartlist_add_asprintf(lines, + "%s\n" + "-----BEGIN MESSAGE-----\n" + "%s" + "-----END MESSAGE-----", + str_encrypted, enc_b64_blob); + tor_free(enc_b64_blob); + } + + /* Join all lines in one string so we can generate a signature and append + * it to the descriptor. */ + encoded_str = smartlist_join_strings(lines, "\n", 1, &encoded_len); + + /* Sign all fields of the descriptor with our short term signing key. */ + { + ed25519_signature_t sig; + char ed_sig_b64[ED25519_SIG_BASE64_LEN + 1]; + if (ed25519_sign_prefixed(&sig, + (const uint8_t *) encoded_str, encoded_len, + str_desc_sig_prefix, + &desc->plaintext_data.signing_kp) < 0) { + log_warn(LD_BUG, "Can't sign encoded HS descriptor!"); + tor_free(encoded_str); + goto err; + } + if (ed25519_signature_to_base64(ed_sig_b64, &sig) < 0) { + log_warn(LD_BUG, "Can't base64 encode descriptor signature!"); + tor_free(encoded_str); + goto err; + } + /* Create the signature line. */ + smartlist_add_asprintf(lines, "%s %s", str_signature, ed_sig_b64); + } + /* Free previous string that we used so compute the signature. */ + tor_free(encoded_str); + encoded_str = smartlist_join_strings(lines, "\n", 1, NULL); + *encoded_out = encoded_str; + + /* XXX: Trigger a control port event. */ + + /* Success! */ + ret = 0; + + err: + SMARTLIST_FOREACH(lines, char *, l, tor_free(l)); + smartlist_free(lines); + return ret; +} + +/* === DECODING === */ + +/* Given an encoded string of the link specifiers, return a newly allocated + * list of decoded link specifiers. Return NULL on error. */ +STATIC smartlist_t * +decode_link_specifiers(const char *encoded) +{ + int decoded_len; + size_t encoded_len, i; + uint8_t *decoded; + smartlist_t *results = NULL; + link_specifier_list_t *specs = NULL; + + tor_assert(encoded); + + encoded_len = strlen(encoded); + decoded = tor_malloc(encoded_len); + decoded_len = base64_decode((char *) decoded, encoded_len, encoded, + encoded_len); + if (decoded_len < 0) { + goto err; + } + + if (link_specifier_list_parse(&specs, decoded, + (size_t) decoded_len) < decoded_len) { + goto err; + } + tor_assert(specs); + results = smartlist_new(); + + for (i = 0; i < link_specifier_list_getlen_spec(specs); i++) { + hs_desc_link_specifier_t *hs_spec; + link_specifier_t *ls = link_specifier_list_get_spec(specs, i); + tor_assert(ls); + + hs_spec = tor_malloc_zero(sizeof(*hs_spec)); + hs_spec->type = link_specifier_get_ls_type(ls); + switch (hs_spec->type) { + case LS_IPV4: + tor_addr_from_ipv4h(&hs_spec->u.ap.addr, + link_specifier_get_un_ipv4_addr(ls)); + hs_spec->u.ap.port = link_specifier_get_un_ipv4_port(ls); + break; + case LS_IPV6: + tor_addr_from_ipv6_bytes(&hs_spec->u.ap.addr, (const char *) + link_specifier_getarray_un_ipv6_addr(ls)); + hs_spec->u.ap.port = link_specifier_get_un_ipv6_port(ls); + break; + case LS_LEGACY_ID: + /* Both are known at compile time so let's make sure they are the same + * else we can copy memory out of bound. */ + tor_assert(link_specifier_getlen_un_legacy_id(ls) == + sizeof(hs_spec->u.legacy_id)); + memcpy(hs_spec->u.legacy_id, link_specifier_getarray_un_legacy_id(ls), + sizeof(hs_spec->u.legacy_id)); + break; + default: + goto err; + } + + smartlist_add(results, hs_spec); + } + + goto done; + err: + if (results) { + SMARTLIST_FOREACH(results, hs_desc_link_specifier_t *, s, tor_free(s)); + smartlist_free(results); + results = NULL; + } + done: + link_specifier_list_free(specs); + tor_free(decoded); + return results; +} + +/* Given a list of authentication types, decode it and put it in the encrypted + * data section. Return 1 if we at least know one of the type or 0 if we know + * none of them. */ +static int +decode_auth_type(hs_desc_encrypted_data_t *desc, const char *list) +{ + int match = 0; + + tor_assert(desc); + tor_assert(list); + + desc->auth_types = smartlist_new(); + smartlist_split_string(desc->auth_types, list, " ", 0, 0); + + /* Validate the types that we at least know about one. */ + SMARTLIST_FOREACH_BEGIN(desc->auth_types, const char *, auth) { + for (int idx = 0; auth_types[idx].identifier; idx++) { + if (!strncmp(auth, auth_types[idx].identifier, + strlen(auth_types[idx].identifier))) { + match = 1; + break; + } + } + } SMARTLIST_FOREACH_END(auth); + + return match; +} + +/* Parse a space-delimited list of integers representing CREATE2 formats into + * the bitfield in hs_desc_encrypted_data_t. Ignore unrecognized values. */ +static void +decode_create2_list(hs_desc_encrypted_data_t *desc, const char *list) +{ + smartlist_t *tokens; + + tor_assert(desc); + tor_assert(list); + + tokens = smartlist_new(); + smartlist_split_string(tokens, list, " ", 0, 0); + + SMARTLIST_FOREACH_BEGIN(tokens, char *, s) { + int ok; + unsigned long type = tor_parse_ulong(s, 10, 1, UINT16_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_REND, "Unparseable value %s in create2 list", escaped(s)); + continue; + } + switch (type) { + case ONION_HANDSHAKE_TYPE_NTOR: + desc->create2_ntor = 1; + break; + default: + /* We deliberately ignore unsupported handshake types */ + continue; + } + } SMARTLIST_FOREACH_END(s); + + SMARTLIST_FOREACH(tokens, char *, s, tor_free(s)); + smartlist_free(tokens); +} + +/* Given a certificate, validate the certificate for certain conditions which + * are if the given type matches the cert's one, if the signing key is + * included and if the that key was actually used to sign the certificate. + * + * Return 1 iff if all conditions pass or 0 if one of them fails. */ +STATIC int +cert_is_valid(tor_cert_t *cert, uint8_t type, const char *log_obj_type) +{ + tor_assert(log_obj_type); + + if (cert == NULL) { + log_warn(LD_REND, "Certificate for %s couldn't be parsed.", log_obj_type); + goto err; + } + if (cert->cert_type != type) { + log_warn(LD_REND, "Invalid cert type %02x for %s.", cert->cert_type, + log_obj_type); + goto err; + } + /* All certificate must have its signing key included. */ + if (!cert->signing_key_included) { + log_warn(LD_REND, "Signing key is NOT included for %s.", log_obj_type); + goto err; + } + /* The following will not only check if the signature matches but also the + * expiration date and overall validity. */ + if (tor_cert_checksig(cert, &cert->signing_key, time(NULL)) < 0) { + log_warn(LD_REND, "Invalid signature for %s.", log_obj_type); + goto err; + } + + return 1; + err: + return 0; +} + +/* Given some binary data, try to parse it to get a certificate object. If we + * have a valid cert, validate it using the given wanted type. On error, print + * a log using the err_msg has the certificate identifier adding semantic to + * the log and cert_out is set to NULL. On success, 0 is returned and cert_out + * points to a newly allocated certificate object. */ +static int +cert_parse_and_validate(tor_cert_t **cert_out, const char *data, + size_t data_len, unsigned int cert_type_wanted, + const char *err_msg) +{ + tor_cert_t *cert; + + tor_assert(cert_out); + tor_assert(data); + tor_assert(err_msg); + + /* Parse certificate. */ + cert = tor_cert_parse((const uint8_t *) data, data_len); + if (!cert) { + log_warn(LD_REND, "Certificate for %s couldn't be parsed.", err_msg); + goto err; + } + + /* Validate certificate. */ + if (!cert_is_valid(cert, cert_type_wanted, err_msg)) { + goto err; + } + + *cert_out = cert; + return 0; + + err: + tor_cert_free(cert); + *cert_out = NULL; + return -1; +} + +/* Return true iff the given length of the encrypted data of a descriptor + * passes validation. */ +STATIC int +encrypted_data_length_is_valid(size_t len) +{ + /* Check for the minimum length possible. */ + if (len < HS_DESC_ENCRYPTED_MIN_LEN) { + log_warn(LD_REND, "Length of descriptor's encrypted data is too small. " + "Got %lu but minimum value is %d", + (unsigned long)len, HS_DESC_ENCRYPTED_MIN_LEN); + goto err; + } + + /* Encrypted data has the salt and MAC concatenated to it so remove those + * from the validation calculation. */ + len -= HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN; + + /* Check that it's aligned on the block size of the crypto algorithm. */ + if (len % HS_DESC_PLAINTEXT_PADDING_MULTIPLE) { + log_warn(LD_REND, "Length of descriptor's encrypted data is invalid. " + "Got %lu which is not a multiple of %d.", + (unsigned long) len, HS_DESC_PLAINTEXT_PADDING_MULTIPLE); + goto err; + } + + /* XXX: Check maximum size. Will strongly depends on the maximum intro point + * allowed we decide on and probably if they will all have to use the legacy + * key which is bigger than the ed25519 key. */ + + return 1; + err: + return 0; +} + +/* Decrypt the encrypted section of the descriptor using the given descriptor + * object desc. A newly allocated NUL terminated string is put in + * decrypted_out. Return the length of decrypted_out on success else 0 is + * returned and decrypted_out is set to NULL. */ +static size_t +desc_decrypt_data_v3(const hs_descriptor_t *desc, char **decrypted_out) +{ + uint8_t *decrypted = NULL; + uint8_t secret_key[CIPHER_KEY_LEN], secret_iv[CIPHER_IV_LEN]; + uint8_t mac_key[DIGEST256_LEN], our_mac[DIGEST256_LEN]; + const uint8_t *salt, *encrypted, *desc_mac; + size_t encrypted_len, result_len = 0; + + tor_assert(decrypted_out); + tor_assert(desc); + tor_assert(desc->plaintext_data.encrypted_blob); + + /* Construction is as follow: SALT | ENCRYPTED_DATA | MAC */ + if (!encrypted_data_length_is_valid( + desc->plaintext_data.encrypted_blob_size)) { + goto err; + } + + /* Start of the blob thus the salt. */ + salt = desc->plaintext_data.encrypted_blob; + /* Next is the encrypted data. */ + encrypted = desc->plaintext_data.encrypted_blob + + HS_DESC_ENCRYPTED_SALT_LEN; + encrypted_len = desc->plaintext_data.encrypted_blob_size - + (HS_DESC_ENCRYPTED_SALT_LEN + DIGEST256_LEN); + + /* At the very end is the MAC. Make sure it's of the right size. */ + { + desc_mac = encrypted + encrypted_len; + size_t desc_mac_size = desc->plaintext_data.encrypted_blob_size - + (desc_mac - desc->plaintext_data.encrypted_blob); + if (desc_mac_size != DIGEST256_LEN) { + log_warn(LD_REND, "Service descriptor MAC length of encrypted data " + "is invalid (%lu, expected %u)", + (unsigned long) desc_mac_size, DIGEST256_LEN); + goto err; + } + } + + /* KDF construction resulting in a key from which the secret key, IV and MAC + * key are extracted which is what we need for the decryption. */ + build_secret_key_iv_mac(desc, salt, HS_DESC_ENCRYPTED_SALT_LEN, + secret_key, sizeof(secret_key), + secret_iv, sizeof(secret_iv), + mac_key, sizeof(mac_key)); + + /* Build MAC. */ + build_mac(mac_key, sizeof(mac_key), salt, HS_DESC_ENCRYPTED_SALT_LEN, + encrypted, encrypted_len, our_mac, sizeof(our_mac)); + memwipe(mac_key, 0, sizeof(mac_key)); + /* Verify MAC; MAC is H(mac_key || salt || encrypted) + * + * This is a critical check that is making sure the computed MAC matches the + * one in the descriptor. */ + if (!tor_memeq(our_mac, desc_mac, sizeof(our_mac))) { + log_warn(LD_REND, "Encrypted service descriptor MAC check failed"); + goto err; + } + + { + /* Decrypt. Here we are assured that the encrypted length is valid for + * decryption. */ + crypto_cipher_t *cipher; + cipher = crypto_cipher_new_with_iv((const char *) secret_key, + (const char *) secret_iv); + /* Extra byte for the NUL terminated byte. */ + decrypted = tor_malloc_zero(encrypted_len + 1); + crypto_cipher_decrypt(cipher, (char *) decrypted, + (const char *) encrypted, encrypted_len); + crypto_cipher_free(cipher); + } + + { + /* Adjust length to remove NULL padding bytes */ + uint8_t *end = memchr(decrypted, 0, encrypted_len); + result_len = encrypted_len; + if (end) { + result_len = end - decrypted; + } + } + + /* Make sure to NUL terminate the string. */ + decrypted[encrypted_len] = '\0'; + *decrypted_out = (char *) decrypted; + goto done; + + err: + if (decrypted) { + tor_free(decrypted); + } + *decrypted_out = NULL; + result_len = 0; + + done: + memwipe(secret_key, 0, sizeof(secret_key)); + memwipe(secret_iv, 0, sizeof(secret_iv)); + return result_len; +} + +/* Given the start of a section and the end of it, decode a single + * introduction point from that section. Return a newly allocated introduction + * point object containing the decoded data. Return NULL if the section can't + * be decoded. */ +STATIC hs_desc_intro_point_t * +decode_introduction_point(const hs_descriptor_t *desc, const char *start) +{ + hs_desc_intro_point_t *ip = NULL; + memarea_t *area = NULL; + smartlist_t *tokens = NULL; + tor_cert_t *cross_cert = NULL; + const directory_token_t *tok; + + tor_assert(desc); + tor_assert(start); + + area = memarea_new(); + tokens = smartlist_new(); + if (tokenize_string(area, start, start + strlen(start), + tokens, hs_desc_intro_point_v3_token_table, 0) < 0) { + log_warn(LD_REND, "Introduction point is not parseable"); + goto err; + } + + /* Ok we seem to have a well formed section containing enough tokens to + * parse. Allocate our IP object and try to populate it. */ + ip = tor_malloc_zero(sizeof(hs_desc_intro_point_t)); + + /* "introduction-point" SP link-specifiers NL */ + tok = find_by_keyword(tokens, R3_INTRODUCTION_POINT); + tor_assert(tok->n_args == 1); + ip->link_specifiers = decode_link_specifiers(tok->args[0]); + if (!ip->link_specifiers) { + log_warn(LD_REND, "Introduction point has invalid link specifiers"); + goto err; + } + + /* "auth-key" NL certificate NL */ + tok = find_by_keyword(tokens, R3_INTRO_AUTH_KEY); + tor_assert(tok->object_body); + if (strcmp(tok->object_type, "ED25519 CERT")) { + log_warn(LD_REND, "Unexpected object type for introduction auth key"); + goto err; + } + + /* Parse cert and do some validation. */ + if (cert_parse_and_validate(&ip->auth_key_cert, tok->object_body, + tok->object_size, CERT_TYPE_AUTH_HS_IP_KEY, + "introduction point auth-key") < 0) { + goto err; + } + + /* Exactly one "enc-key" ... */ + tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY); + if (!strcmp(tok->args[0], "ntor")) { + /* "enc-key" SP "ntor" SP key NL */ + if (tok->n_args != 2 || tok->object_body) { + log_warn(LD_REND, "Introduction point ntor encryption key is invalid"); + goto err; + } + + if (curve25519_public_from_base64(&ip->enc_key.curve25519.pubkey, + tok->args[1]) < 0) { + log_warn(LD_REND, "Introduction point ntor encryption key is invalid"); + goto err; + } + ip->enc_key_type = HS_DESC_KEY_TYPE_CURVE25519; + } else if (!strcmp(tok->args[0], "legacy")) { + /* "enc-key" SP "legacy" NL key NL */ + if (!tok->key) { + log_warn(LD_REND, "Introduction point legacy encryption key is " + "invalid"); + goto err; + } + ip->enc_key.legacy = crypto_pk_dup_key(tok->key); + ip->enc_key_type = HS_DESC_KEY_TYPE_LEGACY; + } else { + /* Unknown key type so we can't use that introduction point. */ + log_warn(LD_REND, "Introduction point encryption key is unrecognized."); + goto err; + } + + /* "enc-key-certification" NL certificate NL */ + tok = find_by_keyword(tokens, R3_INTRO_ENC_KEY_CERTIFICATION); + tor_assert(tok->object_body); + /* Do the cross certification. */ + switch (ip->enc_key_type) { + case HS_DESC_KEY_TYPE_CURVE25519: + { + if (strcmp(tok->object_type, "ED25519 CERT")) { + log_warn(LD_REND, "Introduction point ntor encryption key " + "cross-certification has an unknown format."); + goto err; + } + if (cert_parse_and_validate(&cross_cert, tok->object_body, + tok->object_size, CERT_TYPE_CROSS_HS_IP_KEYS, + "introduction point enc-key-certification") < 0) { + goto err; + } + break; + } + case HS_DESC_KEY_TYPE_LEGACY: + if (strcmp(tok->object_type, "CROSSCERT")) { + log_warn(LD_REND, "Introduction point legacy encryption key " + "cross-certification has an unknown format."); + goto err; + } + if (rsa_ed25519_crosscert_check((const uint8_t *) tok->object_body, + tok->object_size, ip->enc_key.legacy, + &desc->plaintext_data.signing_key_cert->signed_key, + approx_time()-86400)) { + log_warn(LD_REND, "Unable to check cross-certification on the " + "introduction point legacy encryption key."); + goto err; + } + break; + default: + tor_assert(0); + break; + } + /* It is successfully cross certified. Flag the object. */ + ip->cross_certified = 1; + goto done; + + err: + desc_intro_point_free(ip); + ip = NULL; + + done: + tor_cert_free(cross_cert); + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + memarea_drop_all(area); + + return ip; +} + +/* Given a descriptor string at <b>data</b>, decode all possible introduction + * points that we can find. Add the introduction point object to desc_enc as we + * find them. Return 0 on success. + * + * On error, a negative value is returned. It is possible that some intro + * point object have been added to the desc_enc, they should be considered + * invalid. One single bad encoded introduction point will make this function + * return an error. */ +STATIC int +decode_intro_points(const hs_descriptor_t *desc, + hs_desc_encrypted_data_t *desc_enc, + const char *data) +{ + int retval = -1; + smartlist_t *chunked_desc = smartlist_new(); + smartlist_t *intro_points = smartlist_new(); + + tor_assert(desc); + tor_assert(desc_enc); + tor_assert(data); + tor_assert(desc_enc->intro_points); + + /* Take the desc string, and extract the intro point substrings out of it */ + { + /* Split the descriptor string using the intro point header as delimiter */ + smartlist_split_string(chunked_desc, data, str_intro_point_start, 0, 0); + + /* Check if there are actually any intro points included. The first chunk + * should be other descriptor fields (e.g. create2-formats), so it's not an + * intro point. */ + if (smartlist_len(chunked_desc) < 2) { + goto done; + } + } + + /* Take the intro point substrings, and prepare them for parsing */ + { + int i = 0; + /* Prepend the introduction-point header to all the chunks, since + smartlist_split_string() devoured it. */ + SMARTLIST_FOREACH_BEGIN(chunked_desc, char *, chunk) { + /* Ignore first chunk. It's other descriptor fields. */ + if (i++ == 0) { + continue; + } + + smartlist_add_asprintf(intro_points, "%s %s", str_intro_point, chunk); + } SMARTLIST_FOREACH_END(chunk); + } + + /* Parse the intro points! */ + SMARTLIST_FOREACH_BEGIN(intro_points, const char *, intro_point) { + hs_desc_intro_point_t *ip = decode_introduction_point(desc, intro_point); + if (!ip) { + /* Malformed introduction point section. Stop right away, this + * descriptor shouldn't be used. */ + goto err; + } + smartlist_add(desc_enc->intro_points, ip); + } SMARTLIST_FOREACH_END(intro_point); + + done: + retval = 0; + + err: + SMARTLIST_FOREACH(chunked_desc, char *, a, tor_free(a)); + smartlist_free(chunked_desc); + SMARTLIST_FOREACH(intro_points, char *, a, tor_free(a)); + smartlist_free(intro_points); + return retval; +} +/* Return 1 iff the given base64 encoded signature in b64_sig from the encoded + * descriptor in encoded_desc validates the descriptor content. */ +STATIC int +desc_sig_is_valid(const char *b64_sig, const ed25519_keypair_t *signing_kp, + const char *encoded_desc, size_t encoded_len) +{ + int ret = 0; + ed25519_signature_t sig; + const char *sig_start; + + tor_assert(b64_sig); + tor_assert(signing_kp); + tor_assert(encoded_desc); + /* Verifying nothing won't end well :). */ + tor_assert(encoded_len > 0); + + /* Signature length check. */ + if (strlen(b64_sig) != ED25519_SIG_BASE64_LEN) { + log_warn(LD_REND, "Service descriptor has an invalid signature length." + "Exptected %d but got %lu", + ED25519_SIG_BASE64_LEN, (unsigned long) strlen(b64_sig)); + goto err; + } + + /* First, convert base64 blob to an ed25519 signature. */ + if (ed25519_signature_from_base64(&sig, b64_sig) != 0) { + log_warn(LD_REND, "Service descriptor does not contain a valid " + "signature"); + goto err; + } + + /* Find the start of signature. */ + sig_start = tor_memstr(encoded_desc, encoded_len, "\n" str_signature); + /* Getting here means the token parsing worked for the signature so if we + * can't find the start of the signature, we have a code flow issue. */ + if (BUG(!sig_start)) { + goto err; + } + /* Skip newline, it has to go in the signature check. */ + sig_start++; + + /* Validate signature with the full body of the descriptor. */ + if (ed25519_checksig_prefixed(&sig, + (const uint8_t *) encoded_desc, + sig_start - encoded_desc, + str_desc_sig_prefix, + &signing_kp->pubkey) != 0) { + log_warn(LD_REND, "Invalid signature on service descriptor"); + goto err; + } + /* Valid signature! All is good. */ + ret = 1; + + err: + return ret; +} + +/* Decode descriptor plaintext data for version 3. Given a list of tokens, an + * allocated plaintext object that will be populated and the encoded + * descriptor with its length. The last one is needed for signature + * verification. Unknown tokens are simply ignored so this won't error on + * unknowns but requires that all v3 token be present and valid. + * + * Return 0 on success else a negative value. */ +static int +desc_decode_plaintext_v3(smartlist_t *tokens, + hs_desc_plaintext_data_t *desc, + const char *encoded_desc, size_t encoded_len) +{ + int ok; + directory_token_t *tok; + + tor_assert(tokens); + tor_assert(desc); + /* Version higher could still use this function to decode most of the + * descriptor and then they decode the extra part. */ + tor_assert(desc->version >= 3); + + /* Descriptor lifetime parsing. */ + tok = find_by_keyword(tokens, R3_DESC_LIFETIME); + tor_assert(tok->n_args == 1); + desc->lifetime_sec = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0, + UINT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_REND, "Service descriptor lifetime value is invalid"); + goto err; + } + /* Put it from minute to second. */ + desc->lifetime_sec *= 60; + if (desc->lifetime_sec > HS_DESC_MAX_LIFETIME) { + log_warn(LD_REND, "Service descriptor lifetime is too big. " + "Got %" PRIu32 " but max is %d", + desc->lifetime_sec, HS_DESC_MAX_LIFETIME); + goto err; + } + + /* Descriptor signing certificate. */ + tok = find_by_keyword(tokens, R3_DESC_SIGNING_CERT); + tor_assert(tok->object_body); + /* Expecting a prop220 cert with the signing key extension, which contains + * the blinded public key. */ + if (strcmp(tok->object_type, "ED25519 CERT") != 0) { + log_warn(LD_REND, "Service descriptor signing cert wrong type (%s)", + escaped(tok->object_type)); + goto err; + } + if (cert_parse_and_validate(&desc->signing_key_cert, tok->object_body, + tok->object_size, CERT_TYPE_SIGNING_HS_DESC, + "service descriptor signing key") < 0) { + goto err; + } + + /* Copy the public keys into signing_kp and blinded_kp */ + memcpy(&desc->signing_kp.pubkey, &desc->signing_key_cert->signed_key, + sizeof(ed25519_public_key_t)); + memcpy(&desc->blinded_kp.pubkey, &desc->signing_key_cert->signing_key, + sizeof(ed25519_public_key_t)); + + /* Extract revision counter value. */ + tok = find_by_keyword(tokens, R3_REVISION_COUNTER); + tor_assert(tok->n_args == 1); + desc->revision_counter = tor_parse_uint64(tok->args[0], 10, 0, + UINT64_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_REND, "Service descriptor revision-counter is invalid"); + goto err; + } + + /* Extract the encrypted data section. */ + tok = find_by_keyword(tokens, R3_ENCRYPTED); + tor_assert(tok->object_body); + if (strcmp(tok->object_type, "MESSAGE") != 0) { + log_warn(LD_REND, "Service descriptor encrypted data section is invalid"); + goto err; + } + /* Make sure the length of the encrypted blob is valid. */ + if (!encrypted_data_length_is_valid(tok->object_size)) { + goto err; + } + + /* Copy the encrypted blob to the descriptor object so we can handle it + * latter if needed. */ + desc->encrypted_blob = tor_memdup(tok->object_body, tok->object_size); + desc->encrypted_blob_size = tok->object_size; + + /* Extract signature and verify it. */ + tok = find_by_keyword(tokens, R3_SIGNATURE); + tor_assert(tok->n_args == 1); + /* First arg here is the actual encoded signature. */ + if (!desc_sig_is_valid(tok->args[0], &desc->signing_kp, + encoded_desc, encoded_len)) { + goto err; + } + + return 0; + + err: + return -1; +} + +/* Decode the version 3 encrypted section of the given descriptor desc. The + * desc_encrypted_out will be populated with the decoded data. Return 0 on + * success else -1. */ +static int +desc_decode_encrypted_v3(const hs_descriptor_t *desc, + hs_desc_encrypted_data_t *desc_encrypted_out) +{ + int result = -1; + char *message = NULL; + size_t message_len; + memarea_t *area = NULL; + directory_token_t *tok; + smartlist_t *tokens = NULL; + + tor_assert(desc); + tor_assert(desc_encrypted_out); + + /* Decrypt the encrypted data that is located in the plaintext section in + * the descriptor as a blob of bytes. The following functions will use the + * keys found in the same section. */ + message_len = desc_decrypt_data_v3(desc, &message); + if (!message_len) { + log_warn(LD_REND, "Service descriptor decryption failed."); + goto err; + } + tor_assert(message); + + area = memarea_new(); + tokens = smartlist_new(); + if (tokenize_string(area, message, message + message_len, + tokens, hs_desc_encrypted_v3_token_table, 0) < 0) { + log_warn(LD_REND, "Encrypted service descriptor is not parseable."); + goto err; + } + + /* CREATE2 supported cell format. It's mandatory. */ + tok = find_by_keyword(tokens, R3_CREATE2_FORMATS); + tor_assert(tok); + decode_create2_list(desc_encrypted_out, tok->args[0]); + /* Must support ntor according to the specification */ + if (!desc_encrypted_out->create2_ntor) { + log_warn(LD_REND, "Service create2-formats does not include ntor."); + goto err; + } + + /* Authentication type. It's optional but only once. */ + tok = find_opt_by_keyword(tokens, R3_AUTHENTICATION_REQUIRED); + if (tok) { + if (!decode_auth_type(desc_encrypted_out, tok->args[0])) { + log_warn(LD_REND, "Service descriptor authentication type has " + "invalid entry(ies)."); + goto err; + } + } + + /* Is this service a single onion service? */ + tok = find_opt_by_keyword(tokens, R3_SINGLE_ONION_SERVICE); + if (tok) { + desc_encrypted_out->single_onion_service = 1; + } + + /* Initialize the descriptor's introduction point list before we start + * decoding. Having 0 intro point is valid. Then decode them all. */ + desc_encrypted_out->intro_points = smartlist_new(); + if (decode_intro_points(desc, desc_encrypted_out, message) < 0) { + goto err; + } + /* Validation of maximum introduction points allowed. */ + if (smartlist_len(desc_encrypted_out->intro_points) > MAX_INTRO_POINTS) { + log_warn(LD_REND, "Service descriptor contains too many introduction " + "points. Maximum allowed is %d but we have %d", + MAX_INTRO_POINTS, + smartlist_len(desc_encrypted_out->intro_points)); + goto err; + } + + /* NOTE: Unknown fields are allowed because this function could be used to + * decode other descriptor version. */ + + result = 0; + goto done; + + err: + tor_assert(result < 0); + desc_encrypted_data_free_contents(desc_encrypted_out); + + done: + if (tokens) { + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + } + if (area) { + memarea_drop_all(area); + } + if (message) { + tor_free(message); + } + return result; +} + +/* Table of encrypted decode function version specific. The function are + * indexed by the version number so v3 callback is at index 3 in the array. */ +static int + (*decode_encrypted_handlers[])( + const hs_descriptor_t *desc, + hs_desc_encrypted_data_t *desc_encrypted) = +{ + /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL, + desc_decode_encrypted_v3, +}; + +/* Decode the encrypted data section of the given descriptor and store the + * data in the given encrypted data object. Return 0 on success else a + * negative value on error. */ +int +hs_desc_decode_encrypted(const hs_descriptor_t *desc, + hs_desc_encrypted_data_t *desc_encrypted) +{ + int ret; + uint32_t version; + + tor_assert(desc); + /* Ease our life a bit. */ + version = desc->plaintext_data.version; + tor_assert(desc_encrypted); + /* Calling this function without an encrypted blob to parse is a code flow + * error. The plaintext parsing should never succeed in the first place + * without an encrypted section. */ + tor_assert(desc->plaintext_data.encrypted_blob); + /* Let's make sure we have a supported version as well. By correctly parsing + * the plaintext, this should not fail. */ + if (BUG(!hs_desc_is_supported_version(version))) { + ret = -1; + goto err; + } + /* Extra precaution. Having no handler for the supported version should + * never happened else we forgot to add it but we bumped the version. */ + tor_assert(ARRAY_LENGTH(decode_encrypted_handlers) >= version); + tor_assert(decode_encrypted_handlers[version]); + + /* Run the version specific plaintext decoder. */ + ret = decode_encrypted_handlers[version](desc, desc_encrypted); + if (ret < 0) { + goto err; + } + + err: + return ret; +} + +/* Table of plaintext decode function version specific. The function are + * indexed by the version number so v3 callback is at index 3 in the array. */ +static int + (*decode_plaintext_handlers[])( + smartlist_t *tokens, + hs_desc_plaintext_data_t *desc, + const char *encoded_desc, + size_t encoded_len) = +{ + /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL, + desc_decode_plaintext_v3, +}; + +/* Fully decode the given descriptor plaintext and store the data in the + * plaintext data object. Returns 0 on success else a negative value. */ +int +hs_desc_decode_plaintext(const char *encoded, + hs_desc_plaintext_data_t *plaintext) +{ + int ok = 0, ret = -1; + memarea_t *area = NULL; + smartlist_t *tokens = NULL; + size_t encoded_len; + directory_token_t *tok; + + tor_assert(encoded); + tor_assert(plaintext); + + encoded_len = strlen(encoded); + if (encoded_len >= HS_DESC_MAX_LEN) { + log_warn(LD_REND, "Service descriptor is too big (%lu bytes)", + (unsigned long) encoded_len); + goto err; + } + + area = memarea_new(); + tokens = smartlist_new(); + /* Tokenize the descriptor so we can start to parse it. */ + if (tokenize_string(area, encoded, encoded + encoded_len, tokens, + hs_desc_v3_token_table, 0) < 0) { + log_warn(LD_REND, "Service descriptor is not parseable"); + goto err; + } + + /* Get the version of the descriptor which is the first mandatory field of + * the descriptor. From there, we'll decode the right descriptor version. */ + tok = find_by_keyword(tokens, R_HS_DESCRIPTOR); + tor_assert(tok->n_args == 1); + plaintext->version = (uint32_t) tor_parse_ulong(tok->args[0], 10, 0, + UINT32_MAX, &ok, NULL); + if (!ok) { + log_warn(LD_REND, "Service descriptor has unparseable version %s", + escaped(tok->args[0])); + goto err; + } + if (!hs_desc_is_supported_version(plaintext->version)) { + log_warn(LD_REND, "Service descriptor has unsupported version %" PRIu32, + plaintext->version); + goto err; + } + /* Extra precaution. Having no handler for the supported version should + * never happened else we forgot to add it but we bumped the version. */ + tor_assert(ARRAY_LENGTH(decode_plaintext_handlers) >= plaintext->version); + tor_assert(decode_plaintext_handlers[plaintext->version]); + + /* Run the version specific plaintext decoder. */ + ret = decode_plaintext_handlers[plaintext->version](tokens, plaintext, + encoded, encoded_len); + if (ret < 0) { + goto err; + } + /* Success. Descriptor has been populated with the data. */ + ret = 0; + + err: + if (tokens) { + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_clear(t)); + smartlist_free(tokens); + } + if (area) { + memarea_drop_all(area); + } + return ret; +} + +/* Fully decode an encoded descriptor and set a newly allocated descriptor + * object in desc_out. Subcredentials are used if not NULL else it's ignored. + * + * Return 0 on success. A negative value is returned on error and desc_out is + * set to NULL. */ +int +hs_desc_decode_descriptor(const char *encoded, + const uint8_t *subcredential, + hs_descriptor_t **desc_out) +{ + int ret; + hs_descriptor_t *desc; + + tor_assert(encoded); + + desc = tor_malloc_zero(sizeof(hs_descriptor_t)); + + /* Subcredentials are optional. */ + if (subcredential) { + memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + } + + ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); + if (ret < 0) { + goto err; + } + + ret = hs_desc_decode_encrypted(desc, &desc->encrypted_data); + if (ret < 0) { + goto err; + } + + if (desc_out) { + *desc_out = desc; + } else { + hs_descriptor_free(desc); + } + return ret; + + err: + hs_descriptor_free(desc); + if (desc_out) { + *desc_out = NULL; + } + + tor_assert(ret < 0); + return ret; +} + +/* Table of encode function version specific. The function are indexed by the + * version number so v3 callback is at index 3 in the array. */ +static int + (*encode_handlers[])( + const hs_descriptor_t *desc, + char **encoded_out) = +{ + /* v0 */ NULL, /* v1 */ NULL, /* v2 */ NULL, + desc_encode_v3, +}; + +/* Encode the given descriptor desc. On success, encoded_out points to a newly + * allocated NUL terminated string that contains the encoded descriptor as a + * string. + * + * Return 0 on success and encoded_out is a valid pointer. On error, -1 is + * returned and encoded_out is set to NULL. */ +int +hs_desc_encode_descriptor(const hs_descriptor_t *desc, char **encoded_out) +{ + int ret = -1; + + tor_assert(desc); + tor_assert(encoded_out); + + /* Make sure we support the version of the descriptor format. */ + if (!hs_desc_is_supported_version(desc->plaintext_data.version)) { + goto err; + } + /* Extra precaution. Having no handler for the supported version should + * never happened else we forgot to add it but we bumped the version. */ + tor_assert(ARRAY_LENGTH(encode_handlers) >= desc->plaintext_data.version); + tor_assert(encode_handlers[desc->plaintext_data.version]); + + ret = encode_handlers[desc->plaintext_data.version](desc, encoded_out); + if (ret < 0) { + goto err; + } + + /* Try to decode what we just encoded. Symmetry is nice! */ + ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, NULL); + if (BUG(ret < 0)) { + goto err; + } + + return 0; + + err: + *encoded_out = NULL; + return ret; +} + +/* Free the descriptor plaintext data object. */ +void +hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc) +{ + desc_plaintext_data_free_contents(desc); + tor_free(desc); +} + +/* Free the descriptor encrypted data object. */ +void +hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc) +{ + desc_encrypted_data_free_contents(desc); + tor_free(desc); +} + +/* Free the given descriptor object. */ +void +hs_descriptor_free(hs_descriptor_t *desc) +{ + if (!desc) { + return; + } + + desc_plaintext_data_free_contents(&desc->plaintext_data); + desc_encrypted_data_free_contents(&desc->encrypted_data); + tor_free(desc); +} + +/* Return the size in bytes of the given plaintext data object. A sizeof() is + * not enough because the object contains pointers and the encrypted blob. + * This is particularly useful for our OOM subsystem that tracks the HSDir + * cache size for instance. */ +size_t +hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data) +{ + tor_assert(data); + return (sizeof(*data) + sizeof(*data->signing_key_cert) + + data->encrypted_blob_size); +} + diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h new file mode 100644 index 0000000000..083d353860 --- /dev/null +++ b/src/or/hs_descriptor.h @@ -0,0 +1,241 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_descriptor.h + * \brief Header file for hs_descriptor.c + **/ + +#ifndef TOR_HS_DESCRIPTOR_H +#define TOR_HS_DESCRIPTOR_H + +#include <stdint.h> + +#include "or.h" +#include "address.h" +#include "container.h" +#include "crypto.h" +#include "crypto_ed25519.h" +#include "torcert.h" + +/* The earliest descriptor format version we support. */ +#define HS_DESC_SUPPORTED_FORMAT_VERSION_MIN 3 +/* The latest descriptor format version we support. */ +#define HS_DESC_SUPPORTED_FORMAT_VERSION_MAX 3 + +/* Maximum lifetime of a descriptor in seconds. The value is set at 12 hours + * which is 720 minutes or 43200 seconds. */ +#define HS_DESC_MAX_LIFETIME (12 * 60 * 60) +/* Lifetime of certificate in the descriptor. This defines the lifetime of the + * descriptor signing key and the cross certification cert of that key. */ +#define HS_DESC_CERT_LIFETIME (24 * 60 * 60) +/* Length of the salt needed for the encrypted section of a descriptor. */ +#define HS_DESC_ENCRYPTED_SALT_LEN 16 +/* Length of the secret input needed for the KDF construction which derives + * the encryption key for the encrypted data section of the descriptor. This + * adds up to 68 bytes being the blinded key, hashed subcredential and + * revision counter. */ +#define HS_DESC_ENCRYPTED_SECRET_INPUT_LEN \ + ED25519_PUBKEY_LEN + DIGEST256_LEN + sizeof(uint64_t) +/* Length of the KDF output value which is the length of the secret key, + * the secret IV and MAC key length which is the length of H() output. */ +#define HS_DESC_ENCRYPTED_KDF_OUTPUT_LEN \ + CIPHER_KEY_LEN + CIPHER_IV_LEN + DIGEST256_LEN +/* We need to pad the plaintext version of the encrypted data section before + * encryption and it has to be a multiple of this value. */ +#define HS_DESC_PLAINTEXT_PADDING_MULTIPLE 128 +/* XXX: Let's make sure this makes sense as an upper limit for the padded + * plaintext section. Then we should enforce it as now only an assert will be + * triggered if we are above it. */ +/* Once padded, this is the maximum length in bytes for the plaintext. */ +#define HS_DESC_PADDED_PLAINTEXT_MAX_LEN 8192 +/* Minimum length in bytes of the encrypted portion of the descriptor. */ +#define HS_DESC_ENCRYPTED_MIN_LEN \ + HS_DESC_ENCRYPTED_SALT_LEN + \ + HS_DESC_PLAINTEXT_PADDING_MULTIPLE + DIGEST256_LEN +/* Maximum length in bytes of a full hidden service descriptor. */ +#define HS_DESC_MAX_LEN 32768 // XXX justify +/* The minimum amount of fields a descriptor should contain. The parsing of + * the fields are version specific so the only required field, as a generic + * view of a descriptor, is 1 that is the version field. */ +#define HS_DESC_PLAINTEXT_MIN_FIELDS 1 + +/* Type of authentication in the descriptor. */ +typedef enum { + HS_DESC_AUTH_PASSWORD = 1, + HS_DESC_AUTH_ED25519 = 2, +} hs_desc_auth_type_t; + +/* Type of encryption key in the descriptor. */ +typedef enum { + HS_DESC_KEY_TYPE_LEGACY = 1, + HS_DESC_KEY_TYPE_CURVE25519 = 2, +} hs_desc_key_type_t; + +/* Link specifier object that contains information on how to extend to the + * relay that is the address, port and handshake type. */ +typedef struct hs_desc_link_specifier_t { + /* Indicate the type of link specifier. See trunnel ed25519_cert + * specification. */ + uint8_t type; + + /* It's either an address/port or a legacy identity fingerprint. */ + union { + /* IP address and port of the relay use to extend. */ + tor_addr_port_t ap; + /* Legacy identity. A 20-byte SHA1 identity fingerprint. */ + uint8_t legacy_id[DIGEST_LEN]; + } u; +} hs_desc_link_specifier_t; + +/* Introduction point information located in a descriptor. */ +typedef struct hs_desc_intro_point_t { + /* Link specifier(s) which details how to extend to the relay. This list + * contains hs_desc_link_specifier_t object. It MUST have at least one. */ + smartlist_t *link_specifiers; + + /* Authentication key used to establish the introduction point circuit and + * cross-certifies the blinded public key for the replica thus signed by + * the blinded key and in turn signs it. */ + tor_cert_t *auth_key_cert; + + /* Encryption key type so we know which one to use in the union below. */ + hs_desc_key_type_t enc_key_type; + + /* Keys are mutually exclusive thus the union. */ + union { + /* Encryption key used to encrypt request to hidden service. */ + curve25519_keypair_t curve25519; + + /* Backward compat: RSA 1024 encryption key for legacy purposes. + * Mutually exclusive with enc_key. */ + crypto_pk_t *legacy; + } enc_key; + + /* True iff the introduction point has passed the cross certification. Upon + * decoding an intro point, this must be true. */ + unsigned int cross_certified : 1; +} hs_desc_intro_point_t; + +/* The encrypted data section of a descriptor. Obviously the data in this is + * in plaintext but encrypted once encoded. */ +typedef struct hs_desc_encrypted_data_t { + /* Bitfield of CREATE2 cell supported formats. The only currently supported + * format is ntor. */ + unsigned int create2_ntor : 1; + + /* A list of authentication types that a client must at least support one + * in order to contact the service. Contains NULL terminated strings. */ + smartlist_t *auth_types; + + /* Is this descriptor a single onion service? */ + unsigned int single_onion_service : 1; + + /* A list of intro points. Contains hs_desc_intro_point_t objects. */ + smartlist_t *intro_points; +} hs_desc_encrypted_data_t; + +/* Plaintext data that is unencrypted information of the descriptor. */ +typedef struct hs_desc_plaintext_data_t { + /* Version of the descriptor format. Spec specifies this field as a + * positive integer. */ + uint32_t version; + + /* The lifetime of the descriptor in seconds. */ + uint32_t lifetime_sec; + + /* Certificate with the short-term ed22519 descriptor signing key for the + * replica which is signed by the blinded public key for that replica. */ + tor_cert_t *signing_key_cert; + + /* Signing keypair which is used to sign the descriptor. Same public key + * as in the signing key certificate. */ + ed25519_keypair_t signing_kp; + + /* Blinded keypair used for this descriptor derived from the master + * identity key and generated for a specific replica number. */ + ed25519_keypair_t blinded_kp; + + /* Revision counter is incremented at each upload, regardless of whether + * the descriptor has changed. This avoids leaking whether the descriptor + * has changed. Spec specifies this as a 8 bytes positive integer. */ + uint64_t revision_counter; + + /* Decoding only: The base64-decoded encrypted blob from the descriptor */ + uint8_t *encrypted_blob; + + /* Decoding only: Size of the encrypted_blob */ + size_t encrypted_blob_size; +} hs_desc_plaintext_data_t; + +/* Service descriptor in its decoded form. */ +typedef struct hs_descriptor_t { + /* Contains the plaintext part of the descriptor. */ + hs_desc_plaintext_data_t plaintext_data; + + /* The following contains what's in the encrypted part of the descriptor. + * It's only encrypted in the encoded version of the descriptor thus the + * data contained in that object is in plaintext. */ + hs_desc_encrypted_data_t encrypted_data; + + /* Subcredentials of a service, used by the client and service to decrypt + * the encrypted data. */ + uint8_t subcredential[DIGEST256_LEN]; +} hs_descriptor_t; + +/* Return true iff the given descriptor format version is supported. */ +static inline int +hs_desc_is_supported_version(uint32_t version) +{ + if (version < HS_DESC_SUPPORTED_FORMAT_VERSION_MIN || + version > HS_DESC_SUPPORTED_FORMAT_VERSION_MAX) { + return 0; + } + return 1; +} + +/* Public API. */ + +void hs_descriptor_free(hs_descriptor_t *desc); +void hs_desc_plaintext_data_free(hs_desc_plaintext_data_t *desc); +void hs_desc_encrypted_data_free(hs_desc_encrypted_data_t *desc); + +int hs_desc_encode_descriptor(const hs_descriptor_t *desc, + char **encoded_out); + +int hs_desc_decode_descriptor(const char *encoded, + const uint8_t *subcredential, + hs_descriptor_t **desc_out); +int hs_desc_decode_plaintext(const char *encoded, + hs_desc_plaintext_data_t *plaintext); +int hs_desc_decode_encrypted(const hs_descriptor_t *desc, + hs_desc_encrypted_data_t *desc_out); + +size_t hs_desc_plaintext_obj_size(const hs_desc_plaintext_data_t *data); + +#ifdef HS_DESCRIPTOR_PRIVATE + +/* Encoding. */ +STATIC char *encode_link_specifiers(const smartlist_t *specs); +STATIC size_t build_plaintext_padding(const char *plaintext, + size_t plaintext_len, + uint8_t **padded_out); +/* Decoding. */ +STATIC smartlist_t *decode_link_specifiers(const char *encoded); +STATIC hs_desc_intro_point_t *decode_introduction_point( + const hs_descriptor_t *desc, + const char *text); +STATIC int decode_intro_points(const hs_descriptor_t *desc, + hs_desc_encrypted_data_t *desc_enc, + const char *data); +STATIC int encrypted_data_length_is_valid(size_t len); +STATIC int cert_is_valid(tor_cert_t *cert, uint8_t type, + const char *log_obj_type); +STATIC int desc_sig_is_valid(const char *b64_sig, + const ed25519_keypair_t *signing_kp, + const char *encoded_desc, size_t encoded_len); +STATIC void desc_intro_point_free(hs_desc_intro_point_t *ip); +#endif /* HS_DESCRIPTOR_PRIVATE */ + +#endif /* TOR_HS_DESCRIPTOR_H */ + diff --git a/src/or/include.am b/src/or/include.am index b4554aadb9..10f8b85bdf 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -48,6 +48,9 @@ LIBTOR_A_SOURCES = \ src/or/entrynodes.c \ src/or/ext_orport.c \ src/or/hibernate.c \ + src/or/hs_cache.c \ + src/or/hs_common.c \ + src/or/hs_descriptor.c \ src/or/keypin.c \ src/or/main.c \ src/or/microdesc.c \ @@ -59,6 +62,7 @@ LIBTOR_A_SOURCES = \ src/or/shared_random.c \ src/or/shared_random_state.c \ src/or/transports.c \ + src/or/parsecommon.c \ src/or/periodic.c \ src/or/protover.c \ src/or/policies.c \ @@ -157,6 +161,9 @@ ORHEADERS = \ src/or/geoip.h \ src/or/entrynodes.h \ src/or/hibernate.h \ + src/or/hs_cache.h \ + src/or/hs_common.h \ + src/or/hs_descriptor.h \ src/or/keypin.h \ src/or/main.h \ src/or/microdesc.h \ @@ -171,6 +178,7 @@ ORHEADERS = \ src/or/shared_random.h \ src/or/shared_random_state.h \ src/or/transports.h \ + src/or/parsecommon.h \ src/or/periodic.h \ src/or/policies.h \ src/or/protover.h \ diff --git a/src/or/main.c b/src/or/main.c index 66a8571901..c10f62724a 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -8,6 +8,42 @@ * \file main.c * \brief Toplevel module. Handles signals, multiplexes between * connections, implements main loop, and drives scheduled events. + * + * For the main loop itself; see run_main_loop_once(). It invokes the rest of + * Tor mostly through Libevent callbacks. Libevent callbacks can happen when + * a timer elapses, a signal is received, a socket is ready to read or write, + * or an event is manually activated. + * + * Most events in Tor are driven from these callbacks: + * <ul> + * <li>conn_read_callback() and conn_write_callback() here, which are + * invoked when a socket is ready to read or write respectively. + * <li>signal_callback(), which handles incoming signals. + * </ul> + * Other events are used for specific purposes, or for building more complex + * control structures. If you search for usage of tor_libevent_new(), you + * will find all the events that we construct in Tor. + * + * Tor has numerous housekeeping operations that need to happen + * regularly. They are handled in different ways: + * <ul> + * <li>The most frequent operations are handled after every read or write + * event, at the end of connection_handle_read() and + * connection_handle_write(). + * + * <li>The next most frequent operations happen after each invocation of the + * main loop, in run_main_loop_once(). + * + * <li>Once per second, we run all of the operations listed in + * second_elapsed_callback(), and in its child, run_scheduled_events(). + * + * <li>Once-a-second operations are handled in second_elapsed_callback(). + * + * <li>More infrequent operations take place based on the periodic event + * driver in periodic.c . These are stored in the periodic_events[] + * table. + * </ul> + * **/ #define MAIN_PRIVATE @@ -37,6 +73,7 @@ #include "entrynodes.h" #include "geoip.h" #include "hibernate.h" +#include "hs_cache.h" #include "keypin.h" #include "main.h" #include "microdesc.h" @@ -1423,13 +1460,13 @@ run_scheduled_events(time_t now) pt_configure_remaining_proxies(); } +/* Periodic callback: Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion + * keys, shut down and restart all cpuworkers, and update our descriptor if + * necessary. + */ static int rotate_onion_key_callback(time_t now, const or_options_t *options) { - /* 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys, - * shut down and restart all cpuworkers, and update the directory if - * necessary. - */ if (server_mode(options)) { time_t rotation_time = get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME; if (rotation_time > now) { @@ -1449,6 +1486,9 @@ rotate_onion_key_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } +/* Periodic callback: Every 30 seconds, check whether it's time to make new + * Ed25519 subkeys. + */ static int check_ed_keys_callback(time_t now, const or_options_t *options) { @@ -1466,6 +1506,11 @@ check_ed_keys_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } +/** + * Periodic callback: Every {LAZY,GREEDY}_DESCRIPTOR_RETRY_INTERVAL, + * see about fetching descriptors, microdescriptors, and extrainfo + * documents. + */ static int launch_descriptor_fetches_callback(time_t now, const or_options_t *options) { @@ -1480,6 +1525,10 @@ launch_descriptor_fetches_callback(time_t now, const or_options_t *options) return GREEDY_DESCRIPTOR_RETRY_INTERVAL; } +/** + * Periodic event: Rotate our X.509 certificates and TLS keys once every + * MAX_SSL_KEY_LIFETIME_INTERNAL. + */ static int rotate_x509_certificate_callback(time_t now, const or_options_t *options) { @@ -1505,6 +1554,10 @@ rotate_x509_certificate_callback(time_t now, const or_options_t *options) return MAX_SSL_KEY_LIFETIME_INTERNAL; } +/** + * Periodic callback: once an hour, grab some more entropy from the + * kernel and feed it to our CSPRNG. + **/ static int add_entropy_callback(time_t now, const or_options_t *options) { @@ -1521,6 +1574,10 @@ add_entropy_callback(time_t now, const or_options_t *options) return ENTROPY_INTERVAL; } +/** + * Periodic callback: if we're an authority, make sure we test + * the routers on the network for reachability. + */ static int launch_reachability_tests_callback(time_t now, const or_options_t *options) { @@ -1532,6 +1589,10 @@ launch_reachability_tests_callback(time_t now, const or_options_t *options) return REACHABILITY_TEST_INTERVAL; } +/** + * Periodic callback: if we're an authority, discount the stability + * information (and other rephist information) that's older. + */ static int downrate_stability_callback(time_t now, const or_options_t *options) { @@ -1543,6 +1604,10 @@ downrate_stability_callback(time_t now, const or_options_t *options) return safe_timer_diff(now, next); } +/** + * Periodic callback: if we're an authority, record our measured stability + * information from rephist in an mtbf file. + */ static int save_stability_callback(time_t now, const or_options_t *options) { @@ -1555,6 +1620,10 @@ save_stability_callback(time_t now, const or_options_t *options) return SAVE_STABILITY_INTERVAL; } +/** + * Periodic callback: if we're an authority, check on our authority + * certificate (the one that authenticates our authority signing key). + */ static int check_authority_cert_callback(time_t now, const or_options_t *options) { @@ -1567,6 +1636,10 @@ check_authority_cert_callback(time_t now, const or_options_t *options) return CHECK_V3_CERTIFICATE_INTERVAL; } +/** + * Periodic callback: If our consensus is too old, recalculate whether + * we can actually use it. + */ static int check_expired_networkstatus_callback(time_t now, const or_options_t *options) { @@ -1586,6 +1659,9 @@ check_expired_networkstatus_callback(time_t now, const or_options_t *options) return CHECK_EXPIRED_NS_INTERVAL; } +/** + * Periodic callback: Write statistics to disk if appropriate. + */ static int write_stats_file_callback(time_t now, const or_options_t *options) { @@ -1633,6 +1709,9 @@ write_stats_file_callback(time_t now, const or_options_t *options) return safe_timer_diff(now, next_time_to_write_stats_files); } +/** + * Periodic callback: Write bridge statistics to disk if appropriate. + */ static int record_bridge_stats_callback(time_t now, const or_options_t *options) { @@ -1660,6 +1739,9 @@ record_bridge_stats_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } +/** + * Periodic callback: Clean in-memory caches every once in a while + */ static int clean_caches_callback(time_t now, const or_options_t *options) { @@ -1667,12 +1749,16 @@ clean_caches_callback(time_t now, const or_options_t *options) rep_history_clean(now - options->RephistTrackTime); rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); rend_cache_clean(now, REND_CACHE_TYPE_SERVICE); - rend_cache_clean_v2_descs_as_dir(now, 0); + hs_cache_clean_as_dir(now); microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) return CLEAN_CACHES_INTERVAL; } +/** + * Periodic callback: Clean the cache of failed hidden service lookups + * frequently frequently. + */ static int rend_cache_failure_clean_callback(time_t now, const or_options_t *options) { @@ -1684,20 +1770,21 @@ rend_cache_failure_clean_callback(time_t now, const or_options_t *options) return 30; } +/** + * Periodic callback: If we're a server and initializing dns failed, retry. + */ static int retry_dns_callback(time_t now, const or_options_t *options) { (void)now; #define RETRY_DNS_INTERVAL (10*60) - /* If we're a server and initializing dns failed, retry periodically. */ if (server_mode(options) && has_dns_init_failed()) dns_init(); return RETRY_DNS_INTERVAL; } - /* 2. Periodically, we consider force-uploading our descriptor - * (if we've passed our internal checks). */ - +/** Periodic callback: consider rebuilding or and re-uploading our descriptor + * (if we've passed our internal checks). */ static int check_descriptor_callback(time_t now, const or_options_t *options) { @@ -1724,6 +1811,11 @@ check_descriptor_callback(time_t now, const or_options_t *options) return CHECK_DESCRIPTOR_INTERVAL; } +/** + * Periodic callback: check whether we're reachable (as a relay), and + * whether our bandwidth has changed enough that we need to + * publish a new descriptor. + */ static int check_for_reachability_bw_callback(time_t now, const or_options_t *options) { @@ -1760,13 +1852,13 @@ check_for_reachability_bw_callback(time_t now, const or_options_t *options) return CHECK_DESCRIPTOR_INTERVAL; } +/** + * Periodic event: once a minute, (or every second if TestingTorNetwork, or + * during client bootstrap), check whether we want to download any + * networkstatus documents. */ static int fetch_networkstatus_callback(time_t now, const or_options_t *options) { - /* 2c. Every minute (or every second if TestingTorNetwork, or during - * client bootstrap), check whether we want to download any networkstatus - * documents. */ - /* How often do we check whether we should download network status * documents? */ const int we_are_bootstrapping = networkstatus_consensus_is_bootstrapping( @@ -1788,12 +1880,13 @@ fetch_networkstatus_callback(time_t now, const or_options_t *options) return networkstatus_dl_check_interval; } +/** + * Periodic callback: Every 60 seconds, we relaunch listeners if any died. */ static int retry_listeners_callback(time_t now, const or_options_t *options) { (void)now; (void)options; - /* 3d. And every 60 seconds, we relaunch listeners if any died. */ if (!net_is_disabled()) { retry_all_listeners(NULL, NULL, 0); return 60; @@ -1801,6 +1894,9 @@ retry_listeners_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } +/** + * Periodic callback: as a server, see if we have any old unused circuits + * that should be expired */ static int expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options) { @@ -1810,6 +1906,10 @@ expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options) return 11; } +/** + * Periodic event: if we're an exit, see if our DNS server is telling us + * obvious lies. + */ static int check_dns_honesty_callback(time_t now, const or_options_t *options) { @@ -1832,6 +1932,10 @@ check_dns_honesty_callback(time_t now, const or_options_t *options) return 12*3600 + crypto_rand_int(12*3600); } +/** + * Periodic callback: if we're the bridge authority, write a networkstatus + * file to disk. + */ static int write_bridge_ns_callback(time_t now, const or_options_t *options) { @@ -1844,6 +1948,9 @@ write_bridge_ns_callback(time_t now, const or_options_t *options) return PERIODIC_EVENT_NO_UPDATE; } +/** + * Periodic callback: poke the tor-fw-helper app if we're using one. + */ static int check_fw_helper_app_callback(time_t now, const or_options_t *options) { @@ -1867,7 +1974,9 @@ check_fw_helper_app_callback(time_t now, const or_options_t *options) return PORT_FORWARDING_CHECK_INTERVAL; } -/** Callback to write heartbeat message in the logs. */ +/** + * Periodic callback: write the heartbeat message in the logs. + */ static int heartbeat_callback(time_t now, const or_options_t *options) { @@ -2373,19 +2482,26 @@ run_main_loop_once(void) /* Make it easier to tell whether libevent failure is our fault or not. */ errno = 0; #endif - /* All active linked conns should get their read events activated. */ + + /* All active linked conns should get their read events activated, + * so that libevent knows to run their callbacks. */ SMARTLIST_FOREACH(active_linked_connection_lst, connection_t *, conn, event_active(conn->read_event, EV_READ, 1)); called_loop_once = smartlist_len(active_linked_connection_lst) ? 1 : 0; + /* Make sure we know (about) what time it is. */ update_approx_time(time(NULL)); - /* poll until we have an event, or the second ends, or until we have - * some active linked connections to trigger events for. */ + /* Here it is: the main loop. Here we tell Libevent to poll until we have + * an event, or the second ends, or until we have some active linked + * connections to trigger events for. Libevent will wait till one + * of these happens, then run all the appropriate callbacks. */ loop_result = event_base_loop(tor_libevent_get_base(), called_loop_once ? EVLOOP_ONCE : 0); - /* let catch() handle things like ^c, and otherwise don't worry about it */ + /* Oh, the loop failed. That might be an error that we need to + * catch, but more likely, it's just an interrupted poll() call or something, + * and we should try again. */ if (loop_result < 0) { int e = tor_socket_errno(-1); /* let the program survive things like ^z */ @@ -2408,9 +2524,17 @@ run_main_loop_once(void) } } - /* This will be pretty fast if nothing new is pending. Note that this gets - * called once per libevent loop, which will make it happen once per group - * of events that fire, or once per second. */ + /* And here is where we put callbacks that happen "every time the event loop + * runs." They must be very fast, or else the whole Tor process will get + * slowed down. + * + * Note that this gets called once per libevent loop, which will make it + * happen once per group of events that fire, or once per second. */ + + /* If there are any pending client connections, try attaching them to + * circuits (if we can.) This will be pretty fast if nothing new is + * pending. + */ connection_ap_attach_pending(0); return 1; @@ -2971,6 +3095,7 @@ tor_free_all(int postfork) rend_service_free_all(); rend_cache_free_all(); rend_service_authorization_free_all(); + hs_cache_free_all(); rep_hist_free_all(); dns_free_all(); clear_pending_onions(); diff --git a/src/or/microdesc.c b/src/or/microdesc.c index a81dc54628..140117f683 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -917,20 +917,9 @@ update_microdescs_from_networkstatus(time_t now) int we_use_microdescriptors_for_circuits(const or_options_t *options) { - int ret = options->UseMicrodescriptors; - if (ret == -1) { - /* UseMicrodescriptors is "auto"; we need to decide: */ - /* If we are configured to use bridges and none of our bridges - * know what a microdescriptor is, the answer is no. */ - if (options->UseBridges && !any_bridge_supports_microdescriptors()) - return 0; - /* Otherwise, we decide that we'll use microdescriptors iff we are - * not a server, and we're not autofetching everything. */ - /* XXXX++ what does not being a server have to do with it? also there's - * a partitioning issue here where bridges differ from clients. */ - ret = !server_mode(options) && !options->FetchUselessDescriptors; - } - return ret; + if (options->UseMicrodescriptors == 0) + return 0; /* the user explicitly picked no */ + return 1; /* yes and auto both mean yes */ } /** Return true iff we should try to download microdescriptors at all. */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 2d39c90380..bfb36413ce 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -6,8 +6,34 @@ /** * \file networkstatus.c - * \brief Functions and structures for handling network status documents as a - * client or cache. + * \brief Functions and structures for handling networkstatus documents as a + * client or as a directory cache. + * + * A consensus networkstatus object is created by the directory + * authorities. It authenticates a set of network parameters--most + * importantly, the list of all the relays in the network. This list + * of relays is represented as an array of routerstatus_t objects. + * + * There are currently two flavors of consensus. With the older "NS" + * flavor, each relay is associated with a digest of its router + * descriptor. Tor instances that use this consensus keep the list of + * router descriptors as routerinfo_t objects stored and managed in + * routerlist.c. With the newer "microdesc" flavor, each relay is + * associated with a digest of the microdescriptor that the authorities + * made for it. These are stored and managed in microdesc.c. Information + * about the router is divided between the the networkstatus and the + * microdescriptor according to the general rule that microdescriptors + * should hold information that changes much less frequently than the + * information in the networkstatus. + * + * Modern clients use microdescriptor networkstatuses. Directory caches + * need to keep both kinds of networkstatus document, so they can serve them. + * + * This module manages fetching, holding, storing, updating, and + * validating networkstatus objects. The download-and-validate process + * is slightly complicated by the fact that the keys you need to + * validate a consensus are stored in the authority certificates, which + * you might not have yet when you download the consensus. */ #define NETWORKSTATUS_PRIVATE @@ -788,8 +814,11 @@ networkstatus_nickname_is_unnamed(const char *nickname) #define NONAUTHORITY_NS_CACHE_INTERVAL (60*60) /** Return true iff, given the options listed in <b>options</b>, <b>flavor</b> - * is the flavor of a consensus networkstatus that we would like to fetch. */ -static int + * is the flavor of a consensus networkstatus that we would like to fetch. + * + * For certificate fetches, use we_want_to_fetch_unknown_auth_certs, and + * for serving fetched documents, use directory_caches_dir_info. */ +int we_want_to_fetch_flavor(const or_options_t *options, int flavor) { if (flavor < 0 || flavor > N_CONSENSUS_FLAVORS) { @@ -811,6 +840,29 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor) return flavor == usable_consensus_flavor(); } +/** Return true iff, given the options listed in <b>options</b>, we would like + * to fetch and store unknown authority certificates. + * + * For consensus and descriptor fetches, use we_want_to_fetch_flavor, and + * for serving fetched certificates, use directory_caches_unknown_auth_certs. + */ +int +we_want_to_fetch_unknown_auth_certs(const or_options_t *options) +{ + if (authdir_mode_v3(options) || + directory_caches_unknown_auth_certs((options))) { + /* We want to serve all certs to others, regardless if we would use + * them ourselves. */ + return 1; + } + if (options->FetchUselessDescriptors) { + /* Unknown certificates are definitely useless. */ + return 1; + } + /* Otherwise, don't fetch unknown certificates. */ + return 0; +} + /** How long will we hang onto a possibly live consensus for which we're * fetching certs before we check whether there is a better one? */ #define DELAY_WHILE_FETCHING_CERTS (20*60) @@ -1702,9 +1754,9 @@ networkstatus_set_current_consensus(const char *consensus, } if (flav != usable_consensus_flavor() && - !directory_caches_dir_info(options)) { - /* This consensus is totally boring to us: we won't use it, and we won't - * serve it. Drop it. */ + !we_want_to_fetch_flavor(options, flav)) { + /* This consensus is totally boring to us: we won't use it, we didn't want + * it, and we won't serve it. Drop it. */ goto done; } @@ -1906,7 +1958,7 @@ networkstatus_set_current_consensus(const char *consensus, download_status_failed(&consensus_dl_status[flav], 0); } - if (directory_caches_dir_info(options)) { + if (we_want_to_fetch_flavor(options, flav)) { dirserv_set_cached_consensus_networkstatus(consensus, flavor, &c->digests, @@ -2355,9 +2407,9 @@ int client_would_use_router(const routerstatus_t *rs, time_t now, const or_options_t *options) { - if (!rs->is_flagged_running && !options->FetchUselessDescriptors) { + if (!rs->is_flagged_running) { /* If we had this router descriptor, we wouldn't even bother using it. - * But, if we want to have a complete list, fetch it anyway. */ + * (Fetching and storing depends on by we_want_to_fetch_flavor().) */ return 0; } if (rs->published_on + options->TestingEstimatedDescriptorPropagationTime diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index 71f36b69ed..454356e0bb 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -66,6 +66,8 @@ const routerstatus_t *router_get_consensus_status_by_nickname( int warn_if_unnamed); const char *networkstatus_get_router_digest_by_nickname(const char *nickname); int networkstatus_nickname_is_unnamed(const char *nickname); +int we_want_to_fetch_flavor(const or_options_t *options, int flavor); +int we_want_to_fetch_unknown_auth_certs(const or_options_t *options); void networkstatus_consensus_download_failed(int status_code, const char *flavname); void update_consensus_networkstatus_fetch_time(time_t now); diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 070e2e9e0d..2802d5b9e0 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -10,6 +10,32 @@ * \brief Structures and functions for tracking what we know about the routers * on the Tor network, and correlating information from networkstatus, * routerinfo, and microdescs. + * + * The key structure here is node_t: that's the canonical way to refer + * to a Tor relay that we might want to build a circuit through. Every + * node_t has either a routerinfo_t, or a routerstatus_t from the current + * networkstatus consensus. If it has a routerstatus_t, it will also + * need to have a microdesc_t before you can use it for circuits. + * + * The nodelist_t is a global singleton that maps identities to node_t + * objects. Access them with the node_get_*() functions. The nodelist_t + * is maintained by calls throughout the codebase + * + * Generally, other code should not have to reach inside a node_t to + * see what information it has. Instead, you should call one of the + * many accessor functions that works on a generic node_t. If there + * isn't one that does what you need, it's better to make such a function, + * and then use it. + * + * For historical reasons, some of the functions that select a node_t + * from the list of all usable node_t objects are in the routerlist.c + * module, since they originally selected a routerinfo_t. (TODO: They + * should move!) + * + * (TODO: Perhaps someday we should abstract the remaining ways of + * talking about a relay to also be node_t instances. Those would be + * routerstatus_t as used for directory requests, and dir_server_t as + * used for authorities and fallback directories.) */ #include "or.h" diff --git a/src/or/or.h b/src/or/or.h index 66717792b4..eb94f63d5e 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -114,6 +114,9 @@ #define NON_ANONYMOUS_MODE_ENABLED 1 #endif +/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */ +#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_)) + /** Length of longest allowable configured nickname. */ #define MAX_NICKNAME_LEN 19 /** Length of a router identity encoded as a hexadecimal digest, plus @@ -779,6 +782,24 @@ typedef struct rend_service_authorization_t { * establishment. Not all fields contain data depending on where this struct * is used. */ typedef struct rend_data_t { + /* Hidden service protocol version of this base object. */ + uint32_t version; + + /** List of HSDir fingerprints on which this request has been sent to. This + * contains binary identity digest of the directory of size DIGEST_LEN. */ + smartlist_t *hsdirs_fp; + + /** Rendezvous cookie used by both, client and service. */ + char rend_cookie[REND_COOKIE_LEN]; + + /** Number of streams associated with this rendezvous circuit. */ + int nr_streams; +} rend_data_t; + +typedef struct rend_data_v2_t { + /* Rendezvous base data. */ + rend_data_t base_; + /** Onion address (without the .onion part) that a client requests. */ char onion_address[REND_SERVICE_ID_LEN_BASE32+1]; @@ -800,17 +821,16 @@ typedef struct rend_data_t { /** Hash of the hidden service's PK used by a service. */ char rend_pk_digest[DIGEST_LEN]; +} rend_data_v2_t; - /** Rendezvous cookie used by both, client and service. */ - char rend_cookie[REND_COOKIE_LEN]; - - /** List of HSDir fingerprints on which this request has been sent to. - * This contains binary identity digest of the directory. */ - smartlist_t *hsdirs_fp; - - /** Number of streams associated with this rendezvous circuit. */ - int nr_streams; -} rend_data_t; +/* From a base rend_data_t object <b>d</d>, return the v2 object. */ +static inline +rend_data_v2_t *TO_REND_DATA_V2(const rend_data_t *d) +{ + tor_assert(d); + tor_assert(d->version == 2); + return DOWNCAST(rend_data_v2_t, d); +} /** Time interval for tracking replays of DH public keys received in * INTRODUCE2 cells. Used only to avoid launching multiple @@ -1348,13 +1368,30 @@ typedef struct listener_connection_t { #define OR_CERT_TYPE_RSA_ED_CROSSCERT 7 /**@}*/ -/** The one currently supported type of AUTHENTICATE cell. It contains +/** The first supported type of AUTHENTICATE cell. It contains * a bunch of structures signed with an RSA1024 key. The signed * structures include a HMAC using negotiated TLS secrets, and a digest * of all cells sent or received before the AUTHENTICATE cell (including * the random server-generated AUTH_CHALLENGE cell). */ #define AUTHTYPE_RSA_SHA256_TLSSECRET 1 +/** As AUTHTYPE_RSA_SHA256_TLSSECRET, but instead of using the + * negotiated TLS secrets, uses exported keying material from the TLS + * session as described in RFC 5705. + * + * Not used by today's tors, since everything that supports this + * also supports ED25519_SHA3_5705, which is better. + **/ +#define AUTHTYPE_RSA_SHA256_RFC5705 2 +/** As AUTHTYPE_RSA_SHA256_RFC5705, but uses an Ed25519 identity key to + * authenticate. */ +#define AUTHTYPE_ED25519_SHA256_RFC5705 3 +/* + * NOTE: authchallenge_type_is_better() relies on these AUTHTYPE codes + * being sorted in order of preference. If we someday add one with + * a higher numerical value that we don't like as much, we should revise + * authchallenge_type_is_better(). + */ /** The length of the part of the AUTHENTICATE cell body that the client and * server can generate independently (when using RSA_SHA256_TLSSECRET). It @@ -1365,6 +1402,34 @@ typedef struct listener_connection_t { * signs. */ #define V3_AUTH_BODY_LEN (V3_AUTH_FIXED_PART_LEN + 8 + 16) +/** Structure to hold all the certificates we've received on an OR connection + */ +typedef struct or_handshake_certs_t { + /** True iff we originated this connection. */ + int started_here; + /** The cert for the 'auth' RSA key that's supposed to sign the AUTHENTICATE + * cell. Signed with the RSA identity key. */ + tor_x509_cert_t *auth_cert; + /** The cert for the 'link' RSA key that was used to negotiate the TLS + * connection. Signed with the RSA identity key. */ + tor_x509_cert_t *link_cert; + /** A self-signed identity certificate: the RSA identity key signed + * with itself. */ + tor_x509_cert_t *id_cert; + /** The Ed25519 signing key, signed with the Ed25519 identity key. */ + struct tor_cert_st *ed_id_sign; + /** A digest of the X509 link certificate for the TLS connection, signed + * with the Ed25519 siging key. */ + struct tor_cert_st *ed_sign_link; + /** The Ed25519 authentication key (that's supposed to sign an AUTHENTICATE + * cell) , signed with the Ed25519 siging key. */ + struct tor_cert_st *ed_sign_auth; + /** The Ed25519 identity key, crosssigned with the RSA identity key. */ + uint8_t *ed_rsa_crosscert; + /** The length of <b>ed_rsa_crosscert</b> in bytes */ + size_t ed_rsa_crosscert_len; +} or_handshake_certs_t; + /** Stores flags and information related to the portion of a v2/v3 Tor OR * connection handshake that happens after the TLS handshake is finished. */ @@ -1385,6 +1450,8 @@ typedef struct or_handshake_state_t { /* True iff we've received valid authentication to some identity. */ unsigned int authenticated : 1; + unsigned int authenticated_rsa : 1; + unsigned int authenticated_ed25519 : 1; /* True iff we have sent a netinfo cell */ unsigned int sent_netinfo : 1; @@ -1402,9 +1469,12 @@ typedef struct or_handshake_state_t { unsigned int digest_received_data : 1; /**@}*/ - /** Identity digest that we have received and authenticated for our peer + /** Identity RSA digest that we have received and authenticated for our peer * on this connection. */ - uint8_t authenticated_peer_id[DIGEST_LEN]; + uint8_t authenticated_rsa_peer_id[DIGEST_LEN]; + /** Identity Ed25519 public key that we have received and authenticated for + * our peer on this connection. */ + ed25519_public_key_t authenticated_ed25519_peer_id; /** Digests of the cells that we have sent or received as part of a V3 * handshake. Used for making and checking AUTHENTICATE cells. @@ -1417,14 +1487,8 @@ typedef struct or_handshake_state_t { /** Certificates that a connection initiator sent us in a CERTS cell; we're * holding on to them until we get an AUTHENTICATE cell. - * - * @{ */ - /** The cert for the key that's supposed to sign the AUTHENTICATE cell */ - tor_x509_cert_t *auth_cert; - /** A self-signed identity certificate */ - tor_x509_cert_t *id_cert; - /**@}*/ + or_handshake_certs_t *certs; } or_handshake_state_t; /** Length of Extended ORPort connection identifier. */ @@ -1761,8 +1825,6 @@ typedef struct control_connection_t { /** Cast a connection_t subtype pointer to a connection_t **/ #define TO_CONN(c) (&(((c)->base_))) -/** Helper macro: Given a pointer to to.base_, of type from*, return &to. */ -#define DOWNCAST(to, ptr) ((to*)SUBTYPE_P(ptr, to, base_)) /** Cast a entry_connection_t subtype pointer to a edge_connection_t **/ #define ENTRY_TO_EDGE_CONN(c) (&(((c))->edge_)) @@ -2203,6 +2265,10 @@ typedef struct routerstatus_t { * accept EXTEND2 cells */ unsigned int supports_extend2_cells:1; + /** True iff this router has a protocol list that allows it to negotiate + * ed25519 identity keys on a link handshake. */ + unsigned int supports_ed25519_link_handshake:1; + unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with @@ -2365,9 +2431,6 @@ typedef struct node_t { /** Local info: we treat this node as if it rejects everything */ unsigned int rejects_all:1; - /** Local info: this node is in our list of guards */ - unsigned int using_as_guard:1; - /* Local info: derived. */ /** True if the IPv6 OR port is preferred over the IPv4 OR port. diff --git a/src/or/parsecommon.c b/src/or/parsecommon.c new file mode 100644 index 0000000000..6622d7d671 --- /dev/null +++ b/src/or/parsecommon.c @@ -0,0 +1,450 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file parsecommon.c + * \brief Common code to parse and validate various type of descriptors. + **/ + +#include "parsecommon.h" +#include "torlog.h" +#include "util_format.h" + +#define MIN_ANNOTATION A_PURPOSE +#define MAX_ANNOTATION A_UNKNOWN_ + +#define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz) +#define ALLOC(sz) memarea_alloc(area,sz) +#define STRDUP(str) memarea_strdup(area,str) +#define STRNDUP(str,n) memarea_strndup(area,(str),(n)) + +#define RET_ERR(msg) \ + STMT_BEGIN \ + if (tok) token_clear(tok); \ + tok = ALLOC_ZERO(sizeof(directory_token_t)); \ + tok->tp = ERR_; \ + tok->error = STRDUP(msg); \ + goto done_tokenizing; \ + STMT_END + +/** Free all resources allocated for <b>tok</b> */ +void +token_clear(directory_token_t *tok) +{ + if (tok->key) + crypto_pk_free(tok->key); +} + +/** Read all tokens from a string between <b>start</b> and <b>end</b>, and add + * them to <b>out</b>. Parse according to the token rules in <b>table</b>. + * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the + * entire string. + */ +int +tokenize_string(memarea_t *area, + const char *start, const char *end, smartlist_t *out, + token_rule_t *table, int flags) +{ + const char **s; + directory_token_t *tok = NULL; + int counts[NIL_]; + int i; + int first_nonannotation; + int prev_len = smartlist_len(out); + tor_assert(area); + + s = &start; + if (!end) { + end = start+strlen(start); + } else { + /* it's only meaningful to check for nuls if we got an end-of-string ptr */ + if (memchr(start, '\0', end-start)) { + log_warn(LD_DIR, "parse error: internal NUL character."); + return -1; + } + } + for (i = 0; i < NIL_; ++i) + counts[i] = 0; + + SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]); + + while (*s < end && (!tok || tok->tp != EOF_)) { + tok = get_next_token(area, s, end, table); + if (tok->tp == ERR_) { + log_warn(LD_DIR, "parse error: %s", tok->error); + token_clear(tok); + return -1; + } + ++counts[tok->tp]; + smartlist_add(out, tok); + *s = eat_whitespace_eos(*s, end); + } + + if (flags & TS_NOCHECK) + return 0; + + if ((flags & TS_ANNOTATIONS_OK)) { + first_nonannotation = -1; + for (i = 0; i < smartlist_len(out); ++i) { + tok = smartlist_get(out, i); + if (tok->tp < MIN_ANNOTATION || tok->tp > MAX_ANNOTATION) { + first_nonannotation = i; + break; + } + } + if (first_nonannotation < 0) { + log_warn(LD_DIR, "parse error: item contains only annotations"); + return -1; + } + for (i=first_nonannotation; i < smartlist_len(out); ++i) { + tok = smartlist_get(out, i); + if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) { + log_warn(LD_DIR, "parse error: Annotations mixed with keywords"); + return -1; + } + } + if ((flags & TS_NO_NEW_ANNOTATIONS)) { + if (first_nonannotation != prev_len) { + log_warn(LD_DIR, "parse error: Unexpected annotations."); + return -1; + } + } + } else { + for (i=0; i < smartlist_len(out); ++i) { + tok = smartlist_get(out, i); + if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) { + log_warn(LD_DIR, "parse error: no annotations allowed."); + return -1; + } + } + first_nonannotation = 0; + } + for (i = 0; table[i].t; ++i) { + if (counts[table[i].v] < table[i].min_cnt) { + log_warn(LD_DIR, "Parse error: missing %s element.", table[i].t); + return -1; + } + if (counts[table[i].v] > table[i].max_cnt) { + log_warn(LD_DIR, "Parse error: too many %s elements.", table[i].t); + return -1; + } + if (table[i].pos & AT_START) { + if (smartlist_len(out) < 1 || + (tok = smartlist_get(out, first_nonannotation))->tp != table[i].v) { + log_warn(LD_DIR, "Parse error: first item is not %s.", table[i].t); + return -1; + } + } + if (table[i].pos & AT_END) { + if (smartlist_len(out) < 1 || + (tok = smartlist_get(out, smartlist_len(out)-1))->tp != table[i].v) { + log_warn(LD_DIR, "Parse error: last item is not %s.", table[i].t); + return -1; + } + } + } + return 0; +} + +/** Helper: parse space-separated arguments from the string <b>s</b> ending at + * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the + * number of parsed elements into the n_args field of <b>tok</b>. Allocate + * all storage in <b>area</b>. Return the number of arguments parsed, or + * return -1 if there was an insanely high number of arguments. */ +static inline int +get_token_arguments(memarea_t *area, directory_token_t *tok, + const char *s, const char *eol) +{ +/** Largest number of arguments we'll accept to any token, ever. */ +#define MAX_ARGS 512 + char *mem = memarea_strndup(area, s, eol-s); + char *cp = mem; + int j = 0; + char *args[MAX_ARGS]; + while (*cp) { + if (j == MAX_ARGS) + return -1; + args[j++] = cp; + cp = (char*)find_whitespace(cp); + if (!cp || !*cp) + break; /* End of the line. */ + *cp++ = '\0'; + cp = (char*)eat_whitespace(cp); + } + tok->n_args = j; + tok->args = memarea_memdup(area, args, j*sizeof(char*)); + return j; +#undef MAX_ARGS +} + +/** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys + * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>. + * Return <b>tok</b> on success, or a new ERR_ token if the token didn't + * conform to the syntax we wanted. + **/ +static inline directory_token_t * +token_check_object(memarea_t *area, const char *kwd, + directory_token_t *tok, obj_syntax o_syn) +{ + char ebuf[128]; + switch (o_syn) { + case NO_OBJ: + /* No object is allowed for this token. */ + if (tok->object_body) { + tor_snprintf(ebuf, sizeof(ebuf), "Unexpected object for %s", kwd); + RET_ERR(ebuf); + } + if (tok->key) { + tor_snprintf(ebuf, sizeof(ebuf), "Unexpected public key for %s", kwd); + RET_ERR(ebuf); + } + break; + case NEED_OBJ: + /* There must be a (non-key) object. */ + if (!tok->object_body) { + tor_snprintf(ebuf, sizeof(ebuf), "Missing object for %s", kwd); + RET_ERR(ebuf); + } + break; + case NEED_KEY_1024: /* There must be a 1024-bit public key. */ + case NEED_SKEY_1024: /* There must be a 1024-bit private key. */ + if (tok->key && crypto_pk_num_bits(tok->key) != PK_BYTES*8) { + tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits", + kwd, crypto_pk_num_bits(tok->key)); + RET_ERR(ebuf); + } + /* fall through */ + case NEED_KEY: /* There must be some kind of key. */ + if (!tok->key) { + tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd); + RET_ERR(ebuf); + } + if (o_syn != NEED_SKEY_1024) { + if (crypto_pk_key_is_private(tok->key)) { + tor_snprintf(ebuf, sizeof(ebuf), + "Private key given for %s, which wants a public key", kwd); + RET_ERR(ebuf); + } + } else { /* o_syn == NEED_SKEY_1024 */ + if (!crypto_pk_key_is_private(tok->key)) { + tor_snprintf(ebuf, sizeof(ebuf), + "Public key given for %s, which wants a private key", kwd); + RET_ERR(ebuf); + } + } + break; + case OBJ_OK: + /* Anything goes with this token. */ + break; + } + + done_tokenizing: + return tok; +} + +/** Helper function: read the next token from *s, advance *s to the end of the + * token, and return the parsed token. Parse *<b>s</b> according to the list + * of tokens in <b>table</b>. + */ +directory_token_t * +get_next_token(memarea_t *area, + const char **s, const char *eos, token_rule_t *table) +{ + /** Reject any object at least this big; it is probably an overflow, an + * attack, a bug, or some other nonsense. */ +#define MAX_UNPARSED_OBJECT_SIZE (128*1024) + /** Reject any line at least this big; it is probably an overflow, an + * attack, a bug, or some other nonsense. */ +#define MAX_LINE_LENGTH (128*1024) + + const char *next, *eol, *obstart; + size_t obname_len; + int i; + directory_token_t *tok; + obj_syntax o_syn = NO_OBJ; + char ebuf[128]; + const char *kwd = ""; + + tor_assert(area); + tok = ALLOC_ZERO(sizeof(directory_token_t)); + tok->tp = ERR_; + + /* Set *s to first token, eol to end-of-line, next to after first token */ + *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */ + tor_assert(eos >= *s); + eol = memchr(*s, '\n', eos-*s); + if (!eol) + eol = eos; + if (eol - *s > MAX_LINE_LENGTH) { + RET_ERR("Line far too long"); + } + + next = find_whitespace_eos(*s, eol); + + if (!strcmp_len(*s, "opt", next-*s)) { + /* Skip past an "opt" at the start of the line. */ + *s = eat_whitespace_eos_no_nl(next, eol); + next = find_whitespace_eos(*s, eol); + } else if (*s == eos) { /* If no "opt", and end-of-line, line is invalid */ + RET_ERR("Unexpected EOF"); + } + + /* Search the table for the appropriate entry. (I tried a binary search + * instead, but it wasn't any faster.) */ + for (i = 0; table[i].t ; ++i) { + if (!strcmp_len(*s, table[i].t, next-*s)) { + /* We've found the keyword. */ + kwd = table[i].t; + tok->tp = table[i].v; + o_syn = table[i].os; + *s = eat_whitespace_eos_no_nl(next, eol); + /* We go ahead whether there are arguments or not, so that tok->args is + * always set if we want arguments. */ + if (table[i].concat_args) { + /* The keyword takes the line as a single argument */ + tok->args = ALLOC(sizeof(char*)); + tok->args[0] = STRNDUP(*s,eol-*s); /* Grab everything on line */ + tok->n_args = 1; + } else { + /* This keyword takes multiple arguments. */ + if (get_token_arguments(area, tok, *s, eol)<0) { + tor_snprintf(ebuf, sizeof(ebuf),"Far too many arguments to %s", kwd); + RET_ERR(ebuf); + } + *s = eol; + } + if (tok->n_args < table[i].min_args) { + tor_snprintf(ebuf, sizeof(ebuf), "Too few arguments to %s", kwd); + RET_ERR(ebuf); + } else if (tok->n_args > table[i].max_args) { + tor_snprintf(ebuf, sizeof(ebuf), "Too many arguments to %s", kwd); + RET_ERR(ebuf); + } + break; + } + } + + if (tok->tp == ERR_) { + /* No keyword matched; call it an "K_opt" or "A_unrecognized" */ + if (**s == '@') + tok->tp = A_UNKNOWN_; + else + tok->tp = K_OPT; + tok->args = ALLOC(sizeof(char*)); + tok->args[0] = STRNDUP(*s, eol-*s); + tok->n_args = 1; + o_syn = OBJ_OK; + } + + /* Check whether there's an object present */ + *s = eat_whitespace_eos(eol, eos); /* Scan from end of first line */ + tor_assert(eos >= *s); + eol = memchr(*s, '\n', eos-*s); + if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */ + goto check_object; + + obstart = *s; /* Set obstart to start of object spec */ + if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */ + strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */ + (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */ + RET_ERR("Malformed object: bad begin line"); + } + tok->object_type = STRNDUP(*s+11, eol-*s-16); + obname_len = eol-*s-16; /* store objname length here to avoid a strlen() */ + *s = eol+1; /* Set *s to possible start of object data (could be eos) */ + + /* Go to the end of the object */ + next = tor_memstr(*s, eos-*s, "-----END "); + if (!next) { + RET_ERR("Malformed object: missing object end line"); + } + tor_assert(eos >= next); + eol = memchr(next, '\n', eos-next); + if (!eol) /* end-of-line marker, or eos if there's no '\n' */ + eol = eos; + /* Validate the ending tag, which should be 9 + NAME + 5 + eol */ + if ((size_t)(eol-next) != 9+obname_len+5 || + strcmp_len(next+9, tok->object_type, obname_len) || + strcmp_len(eol-5, "-----", 5)) { + tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s", + tok->object_type); + ebuf[sizeof(ebuf)-1] = '\0'; + RET_ERR(ebuf); + } + if (next - *s > MAX_UNPARSED_OBJECT_SIZE) + RET_ERR("Couldn't parse object: missing footer or object much too big."); + + if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */ + tok->key = crypto_pk_new(); + if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart)) + RET_ERR("Couldn't parse public key."); + } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */ + tok->key = crypto_pk_new(); + if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart)) + RET_ERR("Couldn't parse private key."); + } else { /* If it's something else, try to base64-decode it */ + int r; + tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */ + r = base64_decode(tok->object_body, next-*s, *s, next-*s); + if (r<0) + RET_ERR("Malformed object: bad base64-encoded data"); + tok->object_size = r; + } + *s = eol; + + check_object: + tok = token_check_object(area, kwd, tok, o_syn); + + done_tokenizing: + return tok; + +#undef RET_ERR +#undef ALLOC +#undef ALLOC_ZERO +#undef STRDUP +#undef STRNDUP +} + +/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail + * with an assert if no such keyword is found. + */ +directory_token_t * +find_by_keyword_(smartlist_t *s, directory_keyword keyword, + const char *keyword_as_string) +{ + directory_token_t *tok = find_opt_by_keyword(s, keyword); + if (PREDICT_UNLIKELY(!tok)) { + log_err(LD_BUG, "Missing %s [%d] in directory object that should have " + "been validated. Internal error.", keyword_as_string, (int)keyword); + tor_assert(tok); + } + return tok; +} + +/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return + * NULL if no such keyword is found. + */ +directory_token_t * +find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) +{ + SMARTLIST_FOREACH(s, directory_token_t *, t, if (t->tp == keyword) return t); + return NULL; +} + +/** If there are any directory_token_t entries in <b>s</b> whose keyword is + * <b>k</b>, return a newly allocated smartlist_t containing all such entries, + * in the same order in which they occur in <b>s</b>. Otherwise return + * NULL. */ +smartlist_t * +find_all_by_keyword(smartlist_t *s, directory_keyword k) +{ + smartlist_t *out = NULL; + SMARTLIST_FOREACH(s, directory_token_t *, t, + if (t->tp == k) { + if (!out) + out = smartlist_new(); + smartlist_add(out, t); + }); + return out; +} + diff --git a/src/or/parsecommon.h b/src/or/parsecommon.h new file mode 100644 index 0000000000..3019df63eb --- /dev/null +++ b/src/or/parsecommon.h @@ -0,0 +1,315 @@ +/* Copyright (c) 2016, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file parsecommon.h + * \brief Header file for parsecommon.c + **/ + +#ifndef TOR_PARSECOMMON_H +#define TOR_PARSECOMMON_H + +#include "container.h" +#include "crypto.h" +#include "memarea.h" + +/** Enumeration of possible token types. The ones starting with K_ correspond +* to directory 'keywords'. A_ is for an annotation, R or C is related to +* hidden services, ERR_ is an error in the tokenizing process, EOF_ is an +* end-of-file marker, and NIL_ is used to encode not-a-token. +*/ +typedef enum { + K_ACCEPT = 0, + K_ACCEPT6, + K_DIRECTORY_SIGNATURE, + K_RECOMMENDED_SOFTWARE, + K_REJECT, + K_REJECT6, + K_ROUTER, + K_SIGNED_DIRECTORY, + K_SIGNING_KEY, + K_ONION_KEY, + K_ONION_KEY_NTOR, + K_ROUTER_SIGNATURE, + K_PUBLISHED, + K_RUNNING_ROUTERS, + K_ROUTER_STATUS, + K_PLATFORM, + K_PROTO, + K_OPT, + K_BANDWIDTH, + K_CONTACT, + K_NETWORK_STATUS, + K_UPTIME, + K_DIR_SIGNING_KEY, + K_FAMILY, + K_FINGERPRINT, + K_HIBERNATING, + K_READ_HISTORY, + K_WRITE_HISTORY, + K_NETWORK_STATUS_VERSION, + K_DIR_SOURCE, + K_DIR_OPTIONS, + K_CLIENT_VERSIONS, + K_SERVER_VERSIONS, + K_RECOMMENDED_CLIENT_PROTOCOLS, + K_RECOMMENDED_RELAY_PROTOCOLS, + K_REQUIRED_CLIENT_PROTOCOLS, + K_REQUIRED_RELAY_PROTOCOLS, + K_OR_ADDRESS, + K_ID, + K_P, + K_P6, + K_R, + K_A, + K_S, + K_V, + K_W, + K_M, + K_EXTRA_INFO, + K_EXTRA_INFO_DIGEST, + K_CACHES_EXTRA_INFO, + K_HIDDEN_SERVICE_DIR, + K_ALLOW_SINGLE_HOP_EXITS, + K_IPV6_POLICY, + K_ROUTER_SIG_ED25519, + K_IDENTITY_ED25519, + K_MASTER_KEY_ED25519, + K_ONION_KEY_CROSSCERT, + K_NTOR_ONION_KEY_CROSSCERT, + + K_DIRREQ_END, + K_DIRREQ_V2_IPS, + K_DIRREQ_V3_IPS, + K_DIRREQ_V2_REQS, + K_DIRREQ_V3_REQS, + K_DIRREQ_V2_SHARE, + K_DIRREQ_V3_SHARE, + K_DIRREQ_V2_RESP, + K_DIRREQ_V3_RESP, + K_DIRREQ_V2_DIR, + K_DIRREQ_V3_DIR, + K_DIRREQ_V2_TUN, + K_DIRREQ_V3_TUN, + K_ENTRY_END, + K_ENTRY_IPS, + K_CELL_END, + K_CELL_PROCESSED, + K_CELL_QUEUED, + K_CELL_TIME, + K_CELL_CIRCS, + K_EXIT_END, + K_EXIT_WRITTEN, + K_EXIT_READ, + K_EXIT_OPENED, + + K_DIR_KEY_CERTIFICATE_VERSION, + K_DIR_IDENTITY_KEY, + K_DIR_KEY_PUBLISHED, + K_DIR_KEY_EXPIRES, + K_DIR_KEY_CERTIFICATION, + K_DIR_KEY_CROSSCERT, + K_DIR_ADDRESS, + K_DIR_TUNNELLED, + + K_VOTE_STATUS, + K_VALID_AFTER, + K_FRESH_UNTIL, + K_VALID_UNTIL, + K_VOTING_DELAY, + + K_KNOWN_FLAGS, + K_PARAMS, + K_BW_WEIGHTS, + K_VOTE_DIGEST, + K_CONSENSUS_DIGEST, + K_ADDITIONAL_DIGEST, + K_ADDITIONAL_SIGNATURE, + K_CONSENSUS_METHODS, + K_CONSENSUS_METHOD, + K_LEGACY_DIR_KEY, + K_DIRECTORY_FOOTER, + K_SIGNING_CERT_ED, + K_SR_FLAG, + K_COMMIT, + K_PREVIOUS_SRV, + K_CURRENT_SRV, + K_PACKAGE, + + A_PURPOSE, + A_LAST_LISTED, + A_UNKNOWN_, + + R_RENDEZVOUS_SERVICE_DESCRIPTOR, + R_VERSION, + R_PERMANENT_KEY, + R_SECRET_ID_PART, + R_PUBLICATION_TIME, + R_PROTOCOL_VERSIONS, + R_INTRODUCTION_POINTS, + R_SIGNATURE, + + R_HS_DESCRIPTOR, /* From version 3, this MUST be generic to all future + descriptor versions thus making it R_. */ + R3_DESC_LIFETIME, + R3_DESC_SIGNING_CERT, + R3_REVISION_COUNTER, + R3_ENCRYPTED, + R3_SIGNATURE, + R3_CREATE2_FORMATS, + R3_AUTHENTICATION_REQUIRED, + R3_SINGLE_ONION_SERVICE, + R3_INTRODUCTION_POINT, + R3_INTRO_AUTH_KEY, + R3_INTRO_ENC_KEY, + R3_INTRO_ENC_KEY_CERTIFICATION, + + R_IPO_IDENTIFIER, + R_IPO_IP_ADDRESS, + R_IPO_ONION_PORT, + R_IPO_ONION_KEY, + R_IPO_SERVICE_KEY, + + C_CLIENT_NAME, + C_DESCRIPTOR_COOKIE, + C_CLIENT_KEY, + + ERR_, + EOF_, + NIL_ +} directory_keyword; + +/** Structure to hold a single directory token. + * + * We parse a directory by breaking it into "tokens", each consisting + * of a keyword, a line full of arguments, and a binary object. The + * arguments and object are both optional, depending on the keyword + * type. + * + * This structure is only allocated in memareas; do not allocate it on + * the heap, or token_clear() won't work. + */ +typedef struct directory_token_t { + directory_keyword tp; /**< Type of the token. */ + int n_args:30; /**< Number of elements in args */ + char **args; /**< Array of arguments from keyword line. */ + + char *object_type; /**< -----BEGIN [object_type]-----*/ + size_t object_size; /**< Bytes in object_body */ + char *object_body; /**< Contents of object, base64-decoded. */ + + crypto_pk_t *key; /**< For public keys only. Heap-allocated. */ + + char *error; /**< For ERR_ tokens only. */ +} directory_token_t; + +/** We use a table of rules to decide how to parse each token type. */ + +/** Rules for whether the keyword needs an object. */ +typedef enum { + NO_OBJ, /**< No object, ever. */ + NEED_OBJ, /**< Object is required. */ + NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */ + NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */ + NEED_KEY, /**< Object is required, and must be a public key. */ + OBJ_OK, /**< Object is optional. */ +} obj_syntax; + +#define AT_START 1 +#define AT_END 2 + +#define TS_ANNOTATIONS_OK 1 +#define TS_NOCHECK 2 +#define TS_NO_NEW_ANNOTATIONS 4 + +/** + * @name macros for defining token rules + * + * Helper macros to define token tables. 's' is a string, 't' is a + * directory_keyword, 'a' is a trio of argument multiplicities, and 'o' is an + * object syntax. + */ +/**@{*/ + +/** Appears to indicate the end of a table. */ +#define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 } +/** An item with no restrictions: used for obsolete document types */ +#define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 } +/** An item with no restrictions on multiplicity or location. */ +#define T0N(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 } +/** An item that must appear exactly once */ +#define T1(s,t,a,o) { s, t, a, o, 1, 1, 0, 0 } +/** An item that must appear exactly once, at the start of the document */ +#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START, 0 } +/** An item that must appear exactly once, at the end of the document */ +#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END, 0 } +/** An item that must appear one or more times */ +#define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0, 0 } +/** An item that must appear no more than once */ +#define T01(s,t,a,o) { s, t, a, o, 0, 1, 0, 0 } +/** An annotation that must appear no more than once */ +#define A01(s,t,a,o) { s, t, a, o, 0, 1, 0, 1 } + +/** Argument multiplicity: any number of arguments. */ +#define ARGS 0,INT_MAX,0 +/** Argument multiplicity: no arguments. */ +#define NO_ARGS 0,0,0 +/** Argument multiplicity: concatenate all arguments. */ +#define CONCAT_ARGS 1,1,1 +/** Argument multiplicity: at least <b>n</b> arguments. */ +#define GE(n) n,INT_MAX,0 +/** Argument multiplicity: exactly <b>n</b> arguments. */ +#define EQ(n) n,n,0 +/**@}*/ + +/** Determines the parsing rules for a single token type. */ +typedef struct token_rule_t { + /** The string value of the keyword identifying the type of item. */ + const char *t; + /** The corresponding directory_keyword enum. */ + directory_keyword v; + /** Minimum number of arguments for this item */ + int min_args; + /** Maximum number of arguments for this item */ + int max_args; + /** If true, we concatenate all arguments for this item into a single + * string. */ + int concat_args; + /** Requirements on object syntax for this item. */ + obj_syntax os; + /** Lowest number of times this item may appear in a document. */ + int min_cnt; + /** Highest number of times this item may appear in a document. */ + int max_cnt; + /** One or more of AT_START/AT_END to limit where the item may appear in a + * document. */ + int pos; + /** True iff this token is an annotation. */ + int is_annotation; +} token_rule_t; + +void token_clear(directory_token_t *tok); + +int tokenize_string(memarea_t *area, + const char *start, const char *end, + smartlist_t *out, + token_rule_t *table, + int flags); +directory_token_t *get_next_token(memarea_t *area, + const char **s, + const char *eos, + token_rule_t *table); + +directory_token_t *find_by_keyword_(smartlist_t *s, + directory_keyword keyword, + const char *keyword_str); + +#define find_by_keyword(s, keyword) \ + find_by_keyword_((s), (keyword), #keyword) + +directory_token_t *find_opt_by_keyword(smartlist_t *s, + directory_keyword keyword); +smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k); + +#endif /* TOR_PARSECOMMON_H */ + diff --git a/src/or/policies.c b/src/or/policies.c index 227e168d9d..f4c0cddbcc 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -6,6 +6,13 @@ /** * \file policies.c * \brief Code to parse and use address policies and exit policies. + * + * We have two key kinds of address policy: full and compressed. A full + * policy is an array of accept/reject patterns, to be applied in order. + * A short policy is simply a list of ports. This module handles both + * kinds, including generic functions to apply them to addresses, and + * also including code to manage the global policies that we apply to + * incoming and outgoing connections. **/ #define POLICIES_PRIVATE @@ -2460,9 +2467,9 @@ policy_summarize(smartlist_t *policy, sa_family_t family) tor_snprintf(buf, sizeof(buf), "%d-%d", start_prt, AT(i)->prt_max); if (AT(i)->accepted) - smartlist_add(accepts, tor_strdup(buf)); + smartlist_add_strdup(accepts, buf); else - smartlist_add(rejects, tor_strdup(buf)); + smartlist_add_strdup(rejects, buf); if (last) break; @@ -2643,7 +2650,7 @@ write_short_policy(const short_policy_t *policy) smartlist_add_asprintf(sl, "%d-%d", e->min_port, e->max_port); } if (i < policy->n_entries-1) - smartlist_add(sl, tor_strdup(",")); + smartlist_add_strdup(sl, ","); } answer = smartlist_join_strings(sl, "", 0, NULL); SMARTLIST_FOREACH(sl, char *, a, tor_free(a)); diff --git a/src/or/protover.c b/src/or/protover.c index 0a4d4fb8fd..ceaf2d5ccf 100644 --- a/src/or/protover.c +++ b/src/or/protover.c @@ -293,7 +293,7 @@ protover_get_supported_protocols(void) "HSIntro=3 " "HSRend=1-2 " "Link=1-4 " - "LinkAuth=1 " + "LinkAuth=1,3 " "Microdesc=1-2 " "Relay=1-2"; } @@ -348,7 +348,7 @@ encode_protocol_list(const smartlist_t *sl) const char *separator = ""; smartlist_t *chunks = smartlist_new(); SMARTLIST_FOREACH_BEGIN(sl, const proto_entry_t *, ent) { - smartlist_add(chunks, tor_strdup(separator)); + smartlist_add_strdup(chunks, separator); proto_entry_encode_into(chunks, ent); @@ -477,7 +477,7 @@ contract_protocol_list(const smartlist_t *proto_strings) smartlist_sort(lst, cmp_single_ent_by_version); if (! first_entry) - smartlist_add(chunks, tor_strdup(" ")); + smartlist_add_strdup(chunks, " "); /* We're going to construct this entry from the ranges. */ proto_entry_t *entry = tor_malloc_zero(sizeof(proto_entry_t)); diff --git a/src/or/relay.c b/src/or/relay.c index 1794215378..8d48239e47 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -8,6 +8,41 @@ * \file relay.c * \brief Handle relay cell encryption/decryption, plus packaging and * receiving from circuits, plus queuing on circuits. + * + * This is a core modules that makes Tor work. It's responsible for + * dealing with RELAY cells (the ones that travel more than one hop along a + * circuit), by: + * <ul> + * <li>constructing relays cells, + * <li>encrypting relay cells, + * <li>decrypting relay cells, + * <li>demultiplexing relay cells as they arrive on a connection, + * <li>queueing relay cells for retransmission, + * <li>or handling relay cells that are for us to receive (as an exit or a + * client). + * </ul> + * + * RELAY cells are generated throughout the code at the client or relay side, + * using relay_send_command_from_edge() or one of the functions like + * connection_edge_send_command() that calls it. Of particular interest is + * connection_edge_package_raw_inbuf(), which takes information that has + * arrived on an edge connection socket, and packages it as a RELAY_DATA cell + * -- this is how information is actually sent across the Tor network. The + * cryptography for these functions is handled deep in + * circuit_package_relay_cell(), which either adds a single layer of + * encryption (if we're an exit), or multiple layers (if we're the origin of + * the circuit). After construction and encryption, the RELAY cells are + * passed to append_cell_to_circuit_queue(), which queues them for + * transmission and tells the circuitmux (see circuitmux.c) that the circuit + * is waiting to send something. + * + * Incoming RELAY cells arrive at circuit_receive_relay_cell(), called from + * command.c. There they are decrypted and, if they are for us, are passed to + * connection_edge_process_relay_cell(). If they're not for us, they're + * re-queued for retransmission again with append_cell_to_circuit_queue(). + * + * The connection_edge_process_relay_cell() function handles all the different + * types of relay cells, launching requests or transmitting data as needed. **/ #define RELAY_PRIVATE @@ -25,6 +60,7 @@ #include "connection_or.h" #include "control.h" #include "geoip.h" +#include "hs_cache.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -575,14 +611,14 @@ relay_send_command_from_edge_(streamid_t stream_id, circuit_t *circ, memset(&cell, 0, sizeof(cell_t)); cell.command = CELL_RELAY; - if (cpath_layer) { + if (CIRCUIT_IS_ORIGIN(circ)) { + tor_assert(cpath_layer); cell.circ_id = circ->n_circ_id; cell_direction = CELL_DIRECTION_OUT; - } else if (! CIRCUIT_IS_ORIGIN(circ)) { + } else { + tor_assert(! cpath_layer); cell.circ_id = TO_OR_CIRCUIT(circ)->p_circ_id; cell_direction = CELL_DIRECTION_IN; - } else { - return -1; } memset(&rh, 0, sizeof(rh)); @@ -2404,9 +2440,7 @@ cell_queues_check_size(void) if (rend_cache_total > get_options()->MaxMemInQueues / 5) { const size_t bytes_to_remove = rend_cache_total - (size_t)(get_options()->MaxMemInQueues / 10); - rend_cache_clean_v2_descs_as_dir(time(NULL), bytes_to_remove); - alloc -= rend_cache_total; - alloc += rend_cache_get_total_allocation(); + alloc -= hs_cache_handle_oom(time(NULL), bytes_to_remove); } circuits_handle_oom(alloc); return 1; diff --git a/src/or/rendcache.c b/src/or/rendcache.c index e61a96b677..bf43407289 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -86,7 +86,7 @@ rend_cache_get_total_allocation(void) } /** Decrement the total bytes attributed to the rendezvous cache by n. */ -STATIC void +void rend_cache_decrement_allocation(size_t n) { static int have_underflowed = 0; @@ -103,7 +103,7 @@ rend_cache_decrement_allocation(size_t n) } /** Increase the total bytes attributed to the rendezvous cache by n. */ -STATIC void +void rend_cache_increment_allocation(size_t n) { static int have_overflowed = 0; @@ -462,45 +462,36 @@ rend_cache_intro_failure_note(rend_intro_point_failure_t failure, } /** Remove all old v2 descriptors and those for which this hidden service - * directory is not responsible for any more. - * - * If at all possible, remove at least <b>force_remove</b> bytes of data. - */ -void -rend_cache_clean_v2_descs_as_dir(time_t now, size_t force_remove) + * directory is not responsible for any more. The cutoff is the time limit for + * which we want to keep the cache entry. In other words, any entry created + * before will be removed. */ +size_t +rend_cache_clean_v2_descs_as_dir(time_t cutoff) { digestmap_iter_t *iter; - time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; - const int LAST_SERVED_CUTOFF_STEP = 1800; - time_t last_served_cutoff = cutoff; size_t bytes_removed = 0; - do { - for (iter = digestmap_iter_init(rend_cache_v2_dir); - !digestmap_iter_done(iter); ) { - const char *key; - void *val; - rend_cache_entry_t *ent; - digestmap_iter_get(iter, &key, &val); - ent = val; - if (ent->parsed->timestamp < cutoff || - ent->last_served < last_served_cutoff) { - char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; - base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN); - log_info(LD_REND, "Removing descriptor with ID '%s' from cache", - safe_str_client(key_base32)); - bytes_removed += rend_cache_entry_allocation(ent); - iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter); - rend_cache_entry_free(ent); - } else { - iter = digestmap_iter_next(rend_cache_v2_dir, iter); - } + + for (iter = digestmap_iter_init(rend_cache_v2_dir); + !digestmap_iter_done(iter); ) { + const char *key; + void *val; + rend_cache_entry_t *ent; + digestmap_iter_get(iter, &key, &val); + ent = val; + if (ent->parsed->timestamp < cutoff) { + char key_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + base32_encode(key_base32, sizeof(key_base32), key, DIGEST_LEN); + log_info(LD_REND, "Removing descriptor with ID '%s' from cache", + safe_str_client(key_base32)); + bytes_removed += rend_cache_entry_allocation(ent); + iter = digestmap_iter_next_rmv(rend_cache_v2_dir, iter); + rend_cache_entry_free(ent); + } else { + iter = digestmap_iter_next(rend_cache_v2_dir, iter); } + } - /* In case we didn't remove enough bytes, advance the cutoff a little. */ - last_served_cutoff += LAST_SERVED_CUTOFF_STEP; - if (last_served_cutoff > now) - break; - } while (bytes_removed < force_remove); + return bytes_removed; } /** Lookup in the client cache the given service ID <b>query</b> for @@ -849,6 +840,8 @@ rend_cache_store_v2_desc_as_client(const char *desc, char want_desc_id[DIGEST_LEN]; rend_cache_entry_t *e; int retval = -1; + rend_data_v2_t *rend_data = TO_REND_DATA_V2(rend_query); + tor_assert(rend_cache); tor_assert(desc); tor_assert(desc_id_base32); @@ -874,11 +867,11 @@ rend_cache_store_v2_desc_as_client(const char *desc, log_warn(LD_REND, "Couldn't compute service ID."); goto err; } - if (rend_query->onion_address[0] != '\0' && - strcmp(rend_query->onion_address, service_id)) { + if (rend_data->onion_address[0] != '\0' && + strcmp(rend_data->onion_address, service_id)) { log_warn(LD_REND, "Received service descriptor for service ID %s; " "expected descriptor for service ID %s.", - service_id, safe_str(rend_query->onion_address)); + service_id, safe_str(rend_data->onion_address)); goto err; } if (tor_memneq(desc_id, want_desc_id, DIGEST_LEN)) { @@ -890,14 +883,14 @@ rend_cache_store_v2_desc_as_client(const char *desc, /* Decode/decrypt introduction points. */ if (intro_content && intro_size > 0) { int n_intro_points; - if (rend_query->auth_type != REND_NO_AUTH && - !tor_mem_is_zero(rend_query->descriptor_cookie, - sizeof(rend_query->descriptor_cookie))) { + if (rend_data->auth_type != REND_NO_AUTH && + !tor_mem_is_zero(rend_data->descriptor_cookie, + sizeof(rend_data->descriptor_cookie))) { char *ipos_decrypted = NULL; size_t ipos_decrypted_size; if (rend_decrypt_introduction_points(&ipos_decrypted, &ipos_decrypted_size, - rend_query->descriptor_cookie, + rend_data->descriptor_cookie, intro_content, intro_size) < 0) { log_warn(LD_REND, "Failed to decrypt introduction points. We are " diff --git a/src/or/rendcache.h b/src/or/rendcache.h index 270b614c38..746f142fcc 100644 --- a/src/or/rendcache.h +++ b/src/or/rendcache.h @@ -53,10 +53,17 @@ typedef enum { REND_CACHE_TYPE_SERVICE = 2, } rend_cache_type_t; +/* Return maximum lifetime in seconds of a cache entry. */ +static inline time_t +rend_cache_max_entry_lifetime(void) +{ + return REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW; +} + void rend_cache_init(void); void rend_cache_clean(time_t now, rend_cache_type_t cache_type); void rend_cache_failure_clean(time_t now); -void rend_cache_clean_v2_descs_as_dir(time_t now, size_t min_to_remove); +size_t rend_cache_clean_v2_descs_as_dir(time_t cutoff); void rend_cache_purge(void); void rend_cache_free_all(void); int rend_cache_lookup_entry(const char *query, int version, @@ -77,6 +84,8 @@ void rend_cache_intro_failure_note(rend_intro_point_failure_t failure, const uint8_t *identity, const char *service_id); void rend_cache_failure_purge(void); +void rend_cache_decrement_allocation(size_t n); +void rend_cache_increment_allocation(size_t n); #ifdef RENDCACHE_PRIVATE @@ -89,8 +98,6 @@ STATIC int cache_failure_intro_lookup(const uint8_t *identity, const char *service_id, rend_cache_failure_intro_t **intro_entry); -STATIC void rend_cache_decrement_allocation(size_t n); -STATIC void rend_cache_increment_allocation(size_t n); STATIC rend_cache_failure_intro_t *rend_cache_failure_intro_entry_new( rend_intro_point_failure_t failure); STATIC rend_cache_failure_t *rend_cache_failure_entry_new(void); diff --git a/src/or/rendclient.c b/src/or/rendclient.c index a93bc94a9c..b0dcf52507 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -16,6 +16,7 @@ #include "connection.h" #include "connection_edge.h" #include "directory.h" +#include "hs_common.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -104,7 +105,7 @@ rend_client_reextend_intro_circuit(origin_circuit_t *circ) if (!extend_info) { log_warn(LD_REND, "No usable introduction points left for %s. Closing.", - safe_str_client(circ->rend_data->onion_address)); + safe_str_client(rend_data_get_address(circ->rend_data))); circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); return -1; } @@ -144,18 +145,19 @@ rend_client_send_introduction(origin_circuit_t *introcirc, off_t dh_offset; crypto_pk_t *intro_key = NULL; int status = 0; + const char *onion_address; tor_assert(introcirc->base_.purpose == CIRCUIT_PURPOSE_C_INTRODUCING); tor_assert(rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY); tor_assert(introcirc->rend_data); tor_assert(rendcirc->rend_data); - tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address, - rendcirc->rend_data->onion_address)); + tor_assert(!rend_cmp_service_ids(rend_data_get_address(introcirc->rend_data), + rend_data_get_address(rendcirc->rend_data))); assert_circ_anonymity_ok(introcirc, options); assert_circ_anonymity_ok(rendcirc, options); + onion_address = rend_data_get_address(introcirc->rend_data); - r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1, - &entry); + r = rend_cache_lookup_entry(onion_address, -1, &entry); /* An invalid onion address is not possible else we have a big issue. */ tor_assert(r != -EINVAL); if (r < 0 || !rend_client_any_intro_points_usable(entry)) { @@ -164,14 +166,13 @@ rend_client_send_introduction(origin_circuit_t *introcirc, log_info(LD_REND, "query %s didn't have valid rend desc in cache. " "Refetching descriptor.", - safe_str_client(introcirc->rend_data->onion_address)); + safe_str_client(onion_address)); rend_client_refetch_v2_renddesc(introcirc->rend_data); { connection_t *conn; while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, - AP_CONN_STATE_CIRCUIT_WAIT, - introcirc->rend_data->onion_address))) { + AP_CONN_STATE_CIRCUIT_WAIT, onion_address))) { connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); conn->state = AP_CONN_STATE_RENDDESC_WAIT; } @@ -195,7 +196,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, log_info(LD_REND, "Could not find intro key for %s at %s; we " "have a v2 rend desc with %d intro points. " "Trying a different intro point...", - safe_str_client(introcirc->rend_data->onion_address), + safe_str_client(onion_address), safe_str_client(extend_info_describe( introcirc->build_state->chosen_exit)), smartlist_len(entry->parsed->intro_nodes)); @@ -235,11 +236,12 @@ rend_client_send_introduction(origin_circuit_t *introcirc, /* If version is 3, write (optional) auth data and timestamp. */ if (entry->parsed->protocols & (1<<3)) { tmp[0] = 3; /* version 3 of the cell format */ - tmp[1] = (uint8_t)introcirc->rend_data->auth_type; /* auth type, if any */ + /* auth type, if any */ + tmp[1] = (uint8_t) TO_REND_DATA_V2(introcirc->rend_data)->auth_type; v3_shift = 1; - if (introcirc->rend_data->auth_type != REND_NO_AUTH) { + if (tmp[1] != REND_NO_AUTH) { set_uint16(tmp+2, htons(REND_DESC_COOKIE_LEN)); - memcpy(tmp+4, introcirc->rend_data->descriptor_cookie, + memcpy(tmp+4, TO_REND_DATA_V2(introcirc->rend_data)->descriptor_cookie, REND_DESC_COOKIE_LEN); v3_shift += 2+REND_DESC_COOKIE_LEN; } @@ -359,7 +361,7 @@ rend_client_rendcirc_has_opened(origin_circuit_t *circ) * Called to close other intro circuits we launched in parallel. */ static void -rend_client_close_other_intros(const char *onion_address) +rend_client_close_other_intros(const uint8_t *rend_pk_digest) { /* abort parallel intro circs, if any */ SMARTLIST_FOREACH_BEGIN(circuit_get_global_list(), circuit_t *, c) { @@ -368,8 +370,7 @@ rend_client_close_other_intros(const char *onion_address) !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c); if (oc->rend_data && - !rend_cmp_service_ids(onion_address, - oc->rend_data->onion_address)) { + rend_circuit_pk_digest_eq(oc, rend_pk_digest)) { log_info(LD_REND|LD_CIRC, "Closing introduction circuit %d that we " "built in parallel (Purpose %d).", oc->global_identifier, c->purpose); @@ -431,7 +432,8 @@ rend_client_introduction_acked(origin_circuit_t *circ, circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_FINISHED); /* close any other intros launched in parallel */ - rend_client_close_other_intros(circ->rend_data->onion_address); + rend_client_close_other_intros(rend_data_get_pk_digest(circ->rend_data, + NULL)); } else { /* It's a NAK; the introduction point didn't relay our request. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_C_INTRODUCING); @@ -440,7 +442,7 @@ rend_client_introduction_acked(origin_circuit_t *circ, * If none remain, refetch the service descriptor. */ log_info(LD_REND, "Got nack for %s from %s...", - safe_str_client(circ->rend_data->onion_address), + safe_str_client(rend_data_get_address(circ->rend_data)), safe_str_client(extend_info_describe(circ->build_state->chosen_exit))); if (rend_client_report_intro_point_failure(circ->build_state->chosen_exit, circ->rend_data, @@ -694,13 +696,15 @@ pick_hsdir(const char *desc_id, const char *desc_id_base32) * in the case that no hidden service directory is left to ask for the * descriptor, return 0, and in case of a failure -1. */ static int -directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query, +directory_get_from_hs_dir(const char *desc_id, + const rend_data_t *rend_query, routerstatus_t *rs_hsdir) { routerstatus_t *hs_dir = rs_hsdir; char *hsdir_fp; char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; char descriptor_cookie_base64[3*REND_DESC_COOKIE_LEN_BASE64]; + const rend_data_v2_t *rend_data; #ifdef ENABLE_TOR2WEB_MODE const int tor2web_mode = get_options()->Tor2webMode; const int how_to_fetch = tor2web_mode ? DIRIND_ONEHOP : DIRIND_ANONYMOUS; @@ -709,6 +713,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query, #endif tor_assert(desc_id); + tor_assert(rend_query); + rend_data = TO_REND_DATA_V2(rend_query); base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_id, DIGEST_LEN); @@ -731,10 +737,11 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query, /* Encode descriptor cookie for logging purposes. Also, if the cookie is * malformed, no fetch is triggered thus this needs to be done before the * fetch request. */ - if (rend_query->auth_type != REND_NO_AUTH) { + if (rend_data->auth_type != REND_NO_AUTH) { if (base64_encode(descriptor_cookie_base64, sizeof(descriptor_cookie_base64), - rend_query->descriptor_cookie, REND_DESC_COOKIE_LEN, + rend_data->descriptor_cookie, + REND_DESC_COOKIE_LEN, 0)<0) { log_warn(LD_BUG, "Could not base64-encode descriptor cookie."); return 0; @@ -760,9 +767,9 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query, "service '%s' with descriptor ID '%s', auth type %d, " "and descriptor cookie '%s' to hidden service " "directory %s", - rend_query->onion_address, desc_id_base32, - rend_query->auth_type, - (rend_query->auth_type == REND_NO_AUTH ? "[none]" : + rend_data->onion_address, desc_id_base32, + rend_data->auth_type, + (rend_data->auth_type == REND_NO_AUTH ? "[none]" : escaped_safe_str_client(descriptor_cookie_base64)), routerstatus_describe(hs_dir)); control_event_hs_descriptor_requested(rend_query, @@ -777,8 +784,8 @@ directory_get_from_hs_dir(const char *desc_id, const rend_data_t *rend_query, * On success, 1 is returned. If no hidden service is left to ask, return 0. * On error, -1 is returned. */ static int -fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query, - smartlist_t *hsdirs) +fetch_v2_desc_by_descid(const char *desc_id, + const rend_data_t *rend_query, smartlist_t *hsdirs) { int ret; @@ -811,13 +818,12 @@ fetch_v2_desc_by_descid(const char *desc_id, const rend_data_t *rend_query, * On success, 1 is returned. If no hidden service is left to ask, return 0. * On error, -1 is returned. */ static int -fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs) +fetch_v2_desc_by_addr(rend_data_t *rend_query, smartlist_t *hsdirs) { char descriptor_id[DIGEST_LEN]; int replicas_left_to_try[REND_NUMBER_OF_NON_CONSECUTIVE_REPLICAS]; int i, tries_left, ret; - - tor_assert(query); + rend_data_v2_t *rend_data = TO_REND_DATA_V2(rend_query); /* Randomly iterate over the replicas until a descriptor can be fetched * from one of the consecutive nodes, or no options are left. */ @@ -831,9 +837,10 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs) int chosen_replica = replicas_left_to_try[rand_val]; replicas_left_to_try[rand_val] = replicas_left_to_try[--tries_left]; - ret = rend_compute_v2_desc_id(descriptor_id, query->onion_address, - query->auth_type == REND_STEALTH_AUTH ? - query->descriptor_cookie : NULL, + ret = rend_compute_v2_desc_id(descriptor_id, + rend_data->onion_address, + rend_data->auth_type == REND_STEALTH_AUTH ? + rend_data->descriptor_cookie : NULL, time(NULL), chosen_replica); if (ret < 0) { /* Normally, on failure the descriptor_id is untouched but let's be @@ -841,18 +848,18 @@ fetch_v2_desc_by_addr(rend_data_t *query, smartlist_t *hsdirs) goto end; } - if (tor_memcmp(descriptor_id, query->descriptor_id[chosen_replica], + if (tor_memcmp(descriptor_id, rend_data->descriptor_id[chosen_replica], sizeof(descriptor_id)) != 0) { /* Not equal from what we currently have so purge the last hid serv * request cache and update the descriptor ID with the new value. */ purge_hid_serv_from_last_hid_serv_requests( - query->descriptor_id[chosen_replica]); - memcpy(query->descriptor_id[chosen_replica], descriptor_id, - sizeof(query->descriptor_id[chosen_replica])); + rend_data->descriptor_id[chosen_replica]); + memcpy(rend_data->descriptor_id[chosen_replica], descriptor_id, + sizeof(rend_data->descriptor_id[chosen_replica])); } /* Trigger the fetch with the computed descriptor ID. */ - ret = fetch_v2_desc_by_descid(descriptor_id, query, hsdirs); + ret = fetch_v2_desc_by_descid(descriptor_id, rend_query, hsdirs); if (ret != 0) { /* Either on success or failure, as long as we tried a fetch we are * done here. */ @@ -880,16 +887,23 @@ int rend_client_fetch_v2_desc(rend_data_t *query, smartlist_t *hsdirs) { int ret; + rend_data_v2_t *rend_data; + const char *onion_address; tor_assert(query); + /* Get the version 2 data structure of the query. */ + rend_data = TO_REND_DATA_V2(query); + onion_address = rend_data_get_address(query); + /* Depending on what's available in the rend data query object, we will * trigger a fetch by HS address or using a descriptor ID. */ - if (query->onion_address[0] != '\0') { + if (onion_address[0] != '\0') { ret = fetch_v2_desc_by_addr(query, hsdirs); - } else if (!tor_digest_is_zero(query->desc_id_fetch)) { - ret = fetch_v2_desc_by_descid(query->desc_id_fetch, query, hsdirs); + } else if (!tor_digest_is_zero(rend_data->desc_id_fetch)) { + ret = fetch_v2_desc_by_descid(rend_data->desc_id_fetch, query, + hsdirs); } else { /* Query data is invalid. */ ret = -1; @@ -907,10 +921,11 @@ void rend_client_refetch_v2_renddesc(rend_data_t *rend_query) { rend_cache_entry_t *e = NULL; + const char *onion_address = rend_data_get_address(rend_query); tor_assert(rend_query); /* Before fetching, check if we already have a usable descriptor here. */ - if (rend_cache_lookup_entry(rend_query->onion_address, -1, &e) == 0 && + if (rend_cache_lookup_entry(onion_address, -1, &e) == 0 && rend_client_any_intro_points_usable(e)) { log_info(LD_REND, "We would fetch a v2 rendezvous descriptor, but we " "already have a usable descriptor here. Not fetching."); @@ -923,7 +938,7 @@ rend_client_refetch_v2_renddesc(rend_data_t *rend_query) return; } log_debug(LD_REND, "Fetching v2 rendezvous descriptor for service %s", - safe_str_client(rend_query->onion_address)); + safe_str_client(onion_address)); rend_client_fetch_v2_desc(rend_query, NULL); /* We don't need to look the error code because either on failure or @@ -959,7 +974,7 @@ rend_client_cancel_descriptor_fetches(void) } else { log_debug(LD_REND, "Marking for close dir conn fetching " "rendezvous descriptor for service %s", - safe_str(rd->onion_address)); + safe_str(rend_data_get_address(rd))); } connection_mark_for_close(conn); } @@ -989,25 +1004,26 @@ rend_client_cancel_descriptor_fetches(void) */ int rend_client_report_intro_point_failure(extend_info_t *failed_intro, - rend_data_t *rend_query, + rend_data_t *rend_data, unsigned int failure_type) { int i, r; rend_cache_entry_t *ent; connection_t *conn; + const char *onion_address = rend_data_get_address(rend_data); - r = rend_cache_lookup_entry(rend_query->onion_address, -1, &ent); + r = rend_cache_lookup_entry(onion_address, -1, &ent); if (r < 0) { /* Either invalid onion address or cache entry not found. */ switch (-r) { case EINVAL: log_warn(LD_BUG, "Malformed service ID %s.", - escaped_safe_str_client(rend_query->onion_address)); + escaped_safe_str_client(onion_address)); return -1; case ENOENT: log_info(LD_REND, "Unknown service %s. Re-fetching descriptor.", - escaped_safe_str_client(rend_query->onion_address)); - rend_client_refetch_v2_renddesc(rend_query); + escaped_safe_str_client(onion_address)); + rend_client_refetch_v2_renddesc(rend_data); return 0; default: log_warn(LD_BUG, "Unknown cache lookup returned code: %d", r); @@ -1031,7 +1047,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, case INTRO_POINT_FAILURE_GENERIC: rend_cache_intro_failure_note(failure_type, (uint8_t *)failed_intro->identity_digest, - rend_query->onion_address); + onion_address); rend_intro_point_free(intro); smartlist_del(ent->parsed->intro_nodes, i); break; @@ -1049,8 +1065,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, if (zap_intro_point) { rend_cache_intro_failure_note( failure_type, - (uint8_t *) failed_intro->identity_digest, - rend_query->onion_address); + (uint8_t *) failed_intro->identity_digest, onion_address); rend_intro_point_free(intro); smartlist_del(ent->parsed->intro_nodes, i); } @@ -1064,14 +1079,14 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, if (! rend_client_any_intro_points_usable(ent)) { log_info(LD_REND, "No more intro points remain for %s. Re-fetching descriptor.", - escaped_safe_str_client(rend_query->onion_address)); - rend_client_refetch_v2_renddesc(rend_query); + escaped_safe_str_client(onion_address)); + rend_client_refetch_v2_renddesc(rend_data); /* move all pending streams back to renddesc_wait */ /* NOTE: We can now do this faster, if we use pending_entry_connections */ while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT, - rend_query->onion_address))) { + onion_address))) { connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); conn->state = AP_CONN_STATE_RENDDESC_WAIT; } @@ -1080,7 +1095,7 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, } log_info(LD_REND,"%d options left for %s.", smartlist_len(ent->parsed->intro_nodes), - escaped_safe_str_client(rend_query->onion_address)); + escaped_safe_str_client(onion_address)); return 1; } @@ -1221,10 +1236,11 @@ rend_client_desc_trynow(const char *query) rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data; if (!rend_data) continue; - if (rend_cmp_service_ids(query, rend_data->onion_address)) + const char *onion_address = rend_data_get_address(rend_data); + if (rend_cmp_service_ids(query, onion_address)) continue; assert_connection_ok(base_conn, now); - if (rend_cache_lookup_entry(rend_data->onion_address, -1, + if (rend_cache_lookup_entry(onion_address, -1, &entry) == 0 && rend_client_any_intro_points_usable(entry)) { /* either this fetch worked, or it failed but there was a @@ -1259,11 +1275,12 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data) { unsigned int have_onion = 0; rend_cache_entry_t *cache_entry = NULL; + const char *onion_address = rend_data_get_address(rend_data); + rend_data_v2_t *rend_data_v2 = TO_REND_DATA_V2(rend_data); - if (*rend_data->onion_address != '\0') { + if (onion_address[0] != '\0') { /* Ignore return value; we find an entry, or we don't. */ - (void) rend_cache_lookup_entry(rend_data->onion_address, -1, - &cache_entry); + (void) rend_cache_lookup_entry(onion_address, -1, &cache_entry); have_onion = 1; } @@ -1277,17 +1294,17 @@ rend_client_note_connection_attempt_ended(const rend_data_t *rend_data) /* Remove the HS's entries in last_hid_serv_requests. */ if (have_onion) { unsigned int replica; - for (replica = 0; replica < ARRAY_LENGTH(rend_data->descriptor_id); + for (replica = 0; replica < ARRAY_LENGTH(rend_data_v2->descriptor_id); replica++) { - const char *desc_id = rend_data->descriptor_id[replica]; + const char *desc_id = rend_data_v2->descriptor_id[replica]; purge_hid_serv_from_last_hid_serv_requests(desc_id); } log_info(LD_REND, "Connection attempt for %s has ended; " "cleaning up temporary state.", - safe_str_client(rend_data->onion_address)); + safe_str_client(onion_address)); } else { /* We only have an ID for a fetch. Probably used by HSFETCH. */ - purge_hid_serv_from_last_hid_serv_requests(rend_data->desc_id_fetch); + purge_hid_serv_from_last_hid_serv_requests(rend_data_v2->desc_id_fetch); } } @@ -1301,12 +1318,13 @@ rend_client_get_random_intro(const rend_data_t *rend_query) int ret; extend_info_t *result; rend_cache_entry_t *entry; + const char *onion_address = rend_data_get_address(rend_query); - ret = rend_cache_lookup_entry(rend_query->onion_address, -1, &entry); + ret = rend_cache_lookup_entry(onion_address, -1, &entry); if (ret < 0 || !rend_client_any_intro_points_usable(entry)) { log_warn(LD_REND, "Query '%s' didn't have valid rend desc in cache. Failing.", - safe_str_client(rend_query->onion_address)); + safe_str_client(onion_address)); /* XXX: Should we refetch the descriptor here if the IPs are not usable * anymore ?. */ return NULL; diff --git a/src/or/rendclient.h b/src/or/rendclient.h index b8f8c2f871..164305a773 100644 --- a/src/or/rendclient.h +++ b/src/or/rendclient.h @@ -27,7 +27,7 @@ void rend_client_cancel_descriptor_fetches(void); void rend_client_purge_last_hid_serv_requests(void); int rend_client_report_intro_point_failure(extend_info_t *failed_intro, - rend_data_t *rend_query, + rend_data_t *rend_data, unsigned int failure_type); int rend_client_rendezvous_acked(origin_circuit_t *circ, diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index d9d39b1f19..f2060e528c 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -12,6 +12,7 @@ #include "circuitbuild.h" #include "config.h" #include "control.h" +#include "hs_common.h" #include "rendclient.h" #include "rendcommon.h" #include "rendmid.h" @@ -804,124 +805,6 @@ rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, command); } -/** Allocate and return a new rend_data_t with the same - * contents as <b>query</b>. */ -rend_data_t * -rend_data_dup(const rend_data_t *data) -{ - rend_data_t *data_dup; - tor_assert(data); - data_dup = tor_memdup(data, sizeof(rend_data_t)); - data_dup->hsdirs_fp = smartlist_new(); - SMARTLIST_FOREACH(data->hsdirs_fp, char *, fp, - smartlist_add(data_dup->hsdirs_fp, - tor_memdup(fp, DIGEST_LEN))); - return data_dup; -} - -/** Compute descriptor ID for each replicas and save them. A valid onion - * address must be present in the <b>rend_data</b>. - * - * Return 0 on success else -1. */ -static int -compute_desc_id(rend_data_t *rend_data) -{ - int ret = 0; - unsigned replica; - time_t now = time(NULL); - - tor_assert(rend_data); - - /* Compute descriptor ID for each replicas. */ - for (replica = 0; replica < ARRAY_LENGTH(rend_data->descriptor_id); - replica++) { - ret = rend_compute_v2_desc_id(rend_data->descriptor_id[replica], - rend_data->onion_address, - rend_data->descriptor_cookie, - now, replica); - if (ret < 0) { - goto end; - } - } - - end: - return ret; -} - -/** Allocate and initialize a rend_data_t object for a service using the - * given arguments. Only the <b>onion_address</b> is not optional. - * - * Return a valid rend_data_t pointer. */ -rend_data_t * -rend_data_service_create(const char *onion_address, const char *pk_digest, - const uint8_t *cookie, rend_auth_type_t auth_type) -{ - rend_data_t *rend_data = tor_malloc_zero(sizeof(*rend_data)); - - /* We need at least one else the call is wrong. */ - tor_assert(onion_address != NULL); - - if (pk_digest) { - memcpy(rend_data->rend_pk_digest, pk_digest, - sizeof(rend_data->rend_pk_digest)); - } - if (cookie) { - memcpy(rend_data->rend_cookie, cookie, - sizeof(rend_data->rend_cookie)); - } - - strlcpy(rend_data->onion_address, onion_address, - sizeof(rend_data->onion_address)); - rend_data->auth_type = auth_type; - /* Won't be used but still need to initialize it for rend_data dup and - * free. */ - rend_data->hsdirs_fp = smartlist_new(); - - return rend_data; -} - -/** Allocate and initialize a rend_data_t object for a client request using - * the given arguments. Either an onion address or a descriptor ID is - * needed. Both can be given but only the onion address will be used to make - * the descriptor fetch. - * - * Return a valid rend_data_t pointer or NULL on error meaning the - * descriptor IDs couldn't be computed from the given data. */ -rend_data_t * -rend_data_client_create(const char *onion_address, const char *desc_id, - const char *cookie, rend_auth_type_t auth_type) -{ - rend_data_t *rend_data = tor_malloc_zero(sizeof(*rend_data)); - - /* We need at least one else the call is wrong. */ - tor_assert(onion_address != NULL || desc_id != NULL); - - if (cookie) { - memcpy(rend_data->descriptor_cookie, cookie, - sizeof(rend_data->descriptor_cookie)); - } - if (desc_id) { - memcpy(rend_data->desc_id_fetch, desc_id, - sizeof(rend_data->desc_id_fetch)); - } - if (onion_address) { - strlcpy(rend_data->onion_address, onion_address, - sizeof(rend_data->onion_address)); - if (compute_desc_id(rend_data) < 0) { - goto error; - } - } - - rend_data->auth_type = auth_type; - rend_data->hsdirs_fp = smartlist_new(); - - return rend_data; - - error: - rend_data_free(rend_data); - return NULL; -} - /** Determine the routers that are responsible for <b>id</b> (binary) and * add pointers to those routers' routerstatus_t to <b>responsible_dirs</b>. * Return -1 if we're returning an empty smartlist, else return 0. @@ -1116,3 +999,32 @@ assert_circ_anonymity_ok(origin_circuit_t *circ, } } +/* Return 1 iff the given <b>digest</b> of a permenanent hidden service key is + * equal to the digest in the origin circuit <b>ocirc</b> of its rend data . + * If the rend data doesn't exist, 0 is returned. This function is agnostic to + * the rend data version. */ +int +rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc, + const uint8_t *digest) +{ + size_t rend_pk_digest_len; + const uint8_t *rend_pk_digest; + + tor_assert(ocirc); + tor_assert(digest); + + if (ocirc->rend_data == NULL) { + goto no_match; + } + + rend_pk_digest = rend_data_get_pk_digest(ocirc->rend_data, + &rend_pk_digest_len); + if (tor_memeq(rend_pk_digest, digest, rend_pk_digest_len)) { + goto match; + } + no_match: + return 0; + match: + return 1; +} + diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 090e6f25e0..942ace5761 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -18,19 +18,6 @@ typedef enum rend_intro_point_failure_t { INTRO_POINT_FAILURE_UNREACHABLE = 2, } rend_intro_point_failure_t; -/** Free all storage associated with <b>data</b> */ -static inline void -rend_data_free(rend_data_t *data) -{ - if (!data) { - return; - } - /* Cleanup the HSDir identity digest. */ - SMARTLIST_FOREACH(data->hsdirs_fp, char *, d, tor_free(d)); - smartlist_free(data->hsdirs_fp); - tor_free(data); -} - int rend_cmp_service_ids(const char *one, const char *two); void rend_process_relay_cell(circuit_t *circ, const crypt_path_t *layer_hint, @@ -60,15 +47,8 @@ void rend_get_descriptor_id_bytes(char *descriptor_id_out, int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, const char *id); -rend_data_t *rend_data_dup(const rend_data_t *data); -rend_data_t *rend_data_client_create(const char *onion_address, - const char *desc_id, - const char *cookie, - rend_auth_type_t auth_type); -rend_data_t *rend_data_service_create(const char *onion_address, - const char *pk_digest, - const uint8_t *cookie, - rend_auth_type_t auth_type); +int rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc, + const uint8_t *digest); char *rend_auth_encode_cookie(const uint8_t *cookie_in, rend_auth_type_t auth_type); diff --git a/src/or/rendmid.c b/src/or/rendmid.c index ca0ad7b0d4..f39c92afae 100644 --- a/src/or/rendmid.c +++ b/src/or/rendmid.c @@ -106,7 +106,7 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, RELAY_COMMAND_INTRO_ESTABLISHED, "", 0, NULL)<0) { log_info(LD_GENERAL, "Couldn't send INTRO_ESTABLISHED cell."); - goto err; + goto err_no_close; } /* Now, set up this circuit. */ @@ -122,8 +122,9 @@ rend_mid_establish_intro(or_circuit_t *circ, const uint8_t *request, log_warn(LD_PROTOCOL, "Rejecting truncated ESTABLISH_INTRO cell."); reason = END_CIRC_REASON_TORPROTOCOL; err: - if (pk) crypto_pk_free(pk); circuit_mark_for_close(TO_CIRCUIT(circ), reason); + err_no_close: + if (pk) crypto_pk_free(pk); return -1; } @@ -201,14 +202,15 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request, (char*)request, request_len, NULL)) { log_warn(LD_GENERAL, "Unable to send INTRODUCE2 cell to Tor client."); - goto err; + /* Stop right now, the circuit has been closed. */ + return -1; } /* And send an ack down the client's circuit. Empty body means succeeded. */ if (relay_send_command_from_edge(0,TO_CIRCUIT(circ), RELAY_COMMAND_INTRODUCE_ACK, NULL,0,NULL)) { log_warn(LD_GENERAL, "Unable to send INTRODUCE_ACK cell to Tor client."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); + /* Stop right now, the circuit has been closed. */ return -1; } @@ -220,8 +222,6 @@ rend_mid_introduce(or_circuit_t *circ, const uint8_t *request, RELAY_COMMAND_INTRODUCE_ACK, nak_body, 1, NULL)) { log_warn(LD_GENERAL, "Unable to send NAK to Tor client."); - /* Is this right? */ - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); } return -1; } @@ -269,8 +269,8 @@ rend_mid_establish_rendezvous(or_circuit_t *circ, const uint8_t *request, RELAY_COMMAND_RENDEZVOUS_ESTABLISHED, "", 0, NULL)<0) { log_warn(LD_PROTOCOL, "Couldn't send RENDEZVOUS_ESTABLISHED cell."); - reason = END_CIRC_REASON_INTERNAL; - goto err; + /* Stop right now, the circuit has been closed. */ + return -1; } circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_REND_POINT_WAITING); @@ -346,7 +346,8 @@ rend_mid_rendezvous(or_circuit_t *circ, const uint8_t *request, log_warn(LD_GENERAL, "Unable to send RENDEZVOUS2 cell to client on circuit %u.", (unsigned)rend_circ->p_circ_id); - goto err; + /* Stop right now, the circuit has been closed. */ + return -1; } /* Join the circuits. */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 4d04da02aa..457c2a02a9 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -17,6 +17,7 @@ #include "config.h" #include "control.h" #include "directory.h" +#include "hs_common.h" #include "main.h" #include "networkstatus.h" #include "nodelist.h" @@ -78,6 +79,10 @@ static int rend_service_check_private_dir(const or_options_t *options, static int rend_service_check_private_dir_impl(const or_options_t *options, const rend_service_t *s, int create); +static const smartlist_t* rend_get_service_list( + const smartlist_t* substitute_service_list); +static smartlist_t* rend_get_service_list_mutable( + smartlist_t* substitute_service_list); /** Represents the mapping from a virtual port of a rendezvous service to * a real port on some IP. @@ -123,18 +128,58 @@ static const char *hostname_fname = "hostname"; static const char *client_keys_fname = "client_keys"; static const char *sos_poison_fname = "onion_service_non_anonymous"; +/** A list of rend_service_t's for services run on this OP. + */ +static smartlist_t *rend_service_list = NULL; + +/* Like rend_get_service_list_mutable, but returns a read-only list. */ +static const smartlist_t* +rend_get_service_list(const smartlist_t* substitute_service_list) +{ + /* It is safe to cast away the const here, because + * rend_get_service_list_mutable does not actually modify the list */ + return rend_get_service_list_mutable((smartlist_t*)substitute_service_list); +} + +/* Return a mutable list of hidden services. + * If substitute_service_list is not NULL, return it. + * Otherwise, check if the global rend_service_list is non-NULL, and if so, + * return it. + * Otherwise, return NULL. + * */ +static smartlist_t* +rend_get_service_list_mutable(smartlist_t* substitute_service_list) +{ + if (substitute_service_list) { + return substitute_service_list; + } + + /* If no special service list is provided, then just use the global one. */ + + if (BUG(!rend_service_list)) { + /* No global HS list, which is a programmer error. */ + return NULL; + } + + return rend_service_list; +} + +/** Tells if onion service <b>s</b> is ephemeral. + */ +static unsigned int +rend_service_is_ephemeral(const struct rend_service_t *s) +{ + return (s->directory == NULL); +} + /** Returns a escaped string representation of the service, <b>s</b>. */ static const char * rend_service_escaped_dir(const struct rend_service_t *s) { - return (s->directory) ? escaped(s->directory) : "[EPHEMERAL]"; + return rend_service_is_ephemeral(s) ? "[EPHEMERAL]" : escaped(s->directory); } -/** A list of rend_service_t's for services run on this OP. - */ -static smartlist_t *rend_service_list = NULL; - /** Return the number of rendezvous services we have configured. */ int num_rend_services(void) @@ -230,17 +275,10 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) int i; rend_service_port_config_t *p; - smartlist_t *s_list; - /* If no special service list is provided, then just use the global one. */ - if (!service_list) { - if (BUG(!rend_service_list)) { - /* No global HS list, which is a failure. */ - return -1; - } - - s_list = rend_service_list; - } else { - s_list = service_list; + /* Use service_list for unit tests */ + smartlist_t *s_list = rend_get_service_list_mutable(service_list); + if (BUG(!s_list)) { + return -1; } service->intro_nodes = smartlist_new(); @@ -248,7 +286,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) if (service->max_streams_per_circuit < 0) { log_warn(LD_CONFIG, "Hidden service (%s) configured with negative max " - "streams per circuit; ignoring.", + "streams per circuit.", rend_service_escaped_dir(service)); rend_service_free(service); return -1; @@ -257,7 +295,7 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) if (service->max_streams_close_circuit < 0 || service->max_streams_close_circuit > 1) { log_warn(LD_CONFIG, "Hidden service (%s) configured with invalid " - "max streams handling; ignoring.", + "max streams handling.", rend_service_escaped_dir(service)); rend_service_free(service); return -1; @@ -267,15 +305,14 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) (!service->clients || smartlist_len(service->clients) == 0)) { log_warn(LD_CONFIG, "Hidden service (%s) with client authorization but no " - "clients; ignoring.", + "clients.", rend_service_escaped_dir(service)); rend_service_free(service); return -1; } if (!service->ports || !smartlist_len(service->ports)) { - log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured; " - "ignoring.", + log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.", rend_service_escaped_dir(service)); rend_service_free(service); return -1; @@ -296,22 +333,21 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) * lock file. But this is enough to detect a simple mistake that * at least one person has actually made. */ - if (service->directory != NULL) { + if (!rend_service_is_ephemeral(service)) { /* Skip dupe for ephemeral services. */ SMARTLIST_FOREACH(s_list, rend_service_t*, ptr, dupe = dupe || !strcmp(ptr->directory, service->directory)); if (dupe) { log_warn(LD_REND, "Another hidden service is already configured for " - "directory %s, ignoring.", + "directory %s.", rend_service_escaped_dir(service)); rend_service_free(service); return -1; } } - smartlist_add(s_list, service); - log_debug(LD_REND,"Configuring service with directory \"%s\"", - service->directory); + log_debug(LD_REND,"Configuring service with directory %s", + rend_service_escaped_dir(service)); for (i = 0; i < smartlist_len(service->ports); ++i) { p = smartlist_get(service->ports, i); if (!(p->is_unix_addr)) { @@ -325,14 +361,16 @@ rend_add_service(smartlist_t *service_list, rend_service_t *service) "Service maps port %d to socket at \"%s\"", p->virtual_port, p->unix_addr); #else - log_debug(LD_REND, - "Service maps port %d to an AF_UNIX socket, but we " - "have no AF_UNIX support on this platform. This is " - "probably a bug.", - p->virtual_port); + log_warn(LD_BUG, + "Service maps port %d to an AF_UNIX socket, but we " + "have no AF_UNIX support on this platform. This is " + "probably a bug.", + p->virtual_port); + return -1; #endif /* defined(HAVE_SYS_UN_H) */ } } + smartlist_add(s_list, service); return 0; } /* NOTREACHED */ @@ -354,9 +392,9 @@ rend_service_port_config_new(const char *socket_path) return conf; } -/** Parses a real-port to virtual-port mapping separated by the provided - * separator and returns a new rend_service_port_config_t, or NULL and an - * optional error string on failure. +/** Parses a virtual-port to real-port/socket mapping separated by + * the provided separator and returns a new rend_service_port_config_t, + * or NULL and an optional error string on failure. * * The format is: VirtualPort SEP (IP|RealPort|IP:RealPort|'socket':path)? * @@ -381,14 +419,12 @@ rend_service_parse_port_config(const char *string, const char *sep, smartlist_split_string(sl, string, sep, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 2); if (smartlist_len(sl) < 1 || BUG(smartlist_len(sl) > 2)) { - if (err_msg_out) - err_msg = tor_strdup("Bad syntax in hidden service port configuration."); + err_msg = tor_strdup("Bad syntax in hidden service port configuration."); goto err; } virtport = (int)tor_parse_long(smartlist_get(sl,0), 10, 1, 65535, NULL,NULL); if (!virtport) { - if (err_msg_out) - tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service " + tor_asprintf(&err_msg, "Missing or invalid port %s in hidden service " "port configuration", escaped(smartlist_get(sl,0))); goto err; @@ -416,10 +452,8 @@ rend_service_parse_port_config(const char *string, const char *sep, } else if (strchr(addrport, ':') || strchr(addrport, '.')) { /* else try it as an IP:port pair if it has a : or . in it */ if (tor_addr_port_lookup(addrport, &addr, &p)<0) { - if (err_msg_out) - err_msg = tor_strdup("Unparseable address in hidden service port " - "configuration."); - + err_msg = tor_strdup("Unparseable address in hidden service port " + "configuration."); goto err; } realport = p?p:virtport; @@ -427,11 +461,9 @@ rend_service_parse_port_config(const char *string, const char *sep, /* No addr:port, no addr -- must be port. */ realport = (int)tor_parse_long(addrport, 10, 1, 65535, NULL, NULL); if (!realport) { - if (err_msg_out) - tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in " - "hidden service port configuration.", - escaped(addrport)); - + tor_asprintf(&err_msg, "Unparseable or out-of-range port %s in " + "hidden service port configuration.", + escaped(addrport)); goto err; } tor_addr_from_ipv4h(&addr, 0x7F000001u); /* Default to 127.0.0.1 */ @@ -450,7 +482,11 @@ rend_service_parse_port_config(const char *string, const char *sep, err: tor_free(addrport); - if (err_msg_out) *err_msg_out = err_msg; + if (err_msg_out != NULL) { + *err_msg_out = err_msg; + } else { + tor_free(err_msg); + } SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); @@ -495,27 +531,14 @@ rend_service_check_dir_and_add(smartlist_t *service_list, return 0; } else { /* Use service_list for unit tests */ - smartlist_t *s_list = NULL; - /* If no special service list is provided, then just use the global one. */ - if (!service_list) { - if (BUG(!rend_service_list)) { - /* No global HS list, which is a failure, because we plan on adding to - * it */ - return -1; - } - s_list = rend_service_list; - } else { - s_list = service_list; - } + smartlist_t *s_list = rend_get_service_list_mutable(service_list); /* s_list can not be NULL here - if both service_list and rend_service_list * are NULL, and validate_only is false, we exit earlier in the function */ if (BUG(!s_list)) { return -1; } - /* Ignore service failures until 030 */ - rend_add_service(s_list, service); - return 0; + return rend_add_service(s_list, service); } } @@ -584,7 +607,8 @@ rend_config_services(const or_options_t *options, int validate_only) } log_info(LD_CONFIG, "HiddenServiceAllowUnknownPorts=%d for %s", - (int)service->allow_unknown_ports, service->directory); + (int)service->allow_unknown_ports, + rend_service_escaped_dir(service)); } else if (!strcasecmp(line->key, "HiddenServiceDirGroupReadable")) { service->dir_group_readable = (int)tor_parse_long(line->value, @@ -598,7 +622,8 @@ rend_config_services(const or_options_t *options, int validate_only) } log_info(LD_CONFIG, "HiddenServiceDirGroupReadable=%d for %s", - service->dir_group_readable, service->directory); + service->dir_group_readable, + rend_service_escaped_dir(service)); } else if (!strcasecmp(line->key, "HiddenServiceMaxStreams")) { service->max_streams_per_circuit = (int)tor_parse_long(line->value, 10, 0, 65535, &ok, NULL); @@ -611,7 +636,8 @@ rend_config_services(const or_options_t *options, int validate_only) } log_info(LD_CONFIG, "HiddenServiceMaxStreams=%d for %s", - service->max_streams_per_circuit, service->directory); + service->max_streams_per_circuit, + rend_service_escaped_dir(service)); } else if (!strcasecmp(line->key, "HiddenServiceMaxStreamsCloseCircuit")) { service->max_streams_close_circuit = (int)tor_parse_long(line->value, 10, 0, 1, &ok, NULL); @@ -625,7 +651,8 @@ rend_config_services(const or_options_t *options, int validate_only) } log_info(LD_CONFIG, "HiddenServiceMaxStreamsCloseCircuit=%d for %s", - (int)service->max_streams_close_circuit, service->directory); + (int)service->max_streams_close_circuit, + rend_service_escaped_dir(service)); } else if (!strcasecmp(line->key, "HiddenServiceNumIntroductionPoints")) { service->n_intro_points_wanted = (unsigned int) tor_parse_long(line->value, 10, @@ -641,7 +668,8 @@ rend_config_services(const or_options_t *options, int validate_only) return -1; } log_info(LD_CONFIG, "HiddenServiceNumIntroductionPoints=%d for %s", - service->n_intro_points_wanted, service->directory); + service->n_intro_points_wanted, + rend_service_escaped_dir(service)); } else if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) { /* Parse auth type and comma-separated list of client names and add a * rend_authorized_client_t for each client to the service's list @@ -817,8 +845,7 @@ rend_config_services(const or_options_t *options, int validate_only) int keep_it = 0; tor_assert(oc->rend_data); SMARTLIST_FOREACH(surviving_services, rend_service_t *, ptr, { - if (tor_memeq(ptr->pk_digest, oc->rend_data->rend_pk_digest, - DIGEST_LEN)) { + if (rend_circuit_pk_digest_eq(oc, (uint8_t *) ptr->pk_digest)) { keep_it = 1; break; } @@ -828,7 +855,7 @@ rend_config_services(const or_options_t *options, int validate_only) log_info(LD_REND, "Closing intro point %s for service %s.", safe_str_client(extend_info_describe( oc->build_state->chosen_exit)), - oc->rend_data->onion_address); + rend_data_get_address(oc->rend_data)); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); /* XXXX Is there another reason we should use here? */ } @@ -938,7 +965,7 @@ rend_service_del_ephemeral(const char *service_id) "removal."); return -1; } - if (s->directory) { + if (!rend_service_is_ephemeral(s)) { log_warn(LD_CONFIG, "Requested non-ephemeral Onion Service for removal."); return -1; } @@ -955,12 +982,13 @@ rend_service_del_ephemeral(const char *service_id) circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); tor_assert(oc->rend_data); - if (!tor_memeq(s->pk_digest, oc->rend_data->rend_pk_digest, DIGEST_LEN)) + if (!rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { continue; + } log_debug(LD_REND, "Closing intro point %s for service %s.", safe_str_client(extend_info_describe( oc->build_state->chosen_exit)), - oc->rend_data->onion_address); + rend_data_get_address(oc->rend_data)); circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED); } } SMARTLIST_FOREACH_END(circ); @@ -1054,7 +1082,7 @@ rend_service_sos_poison_path(const rend_service_t *service) return rend_service_path(service, sos_poison_fname); } -/** Return True if hidden services <b>service> has been poisoned by single +/** Return True if hidden services <b>service</b> has been poisoned by single * onion mode. */ static int service_is_single_onion_poisoned(const rend_service_t *service) @@ -1067,7 +1095,7 @@ service_is_single_onion_poisoned(const rend_service_t *service) return 0; } - if (!service->directory) { + if (rend_service_is_ephemeral(service)) { return 0; } @@ -1163,7 +1191,7 @@ poison_new_single_onion_hidden_service_dir_impl(const rend_service_t *service, int retval = -1; char *poison_fname = NULL; - if (!service->directory) { + if (rend_service_is_ephemeral(service)) { log_info(LD_REND, "Ephemeral HS started in non-anonymous mode."); return 0; } @@ -1249,22 +1277,17 @@ rend_service_poison_new_single_onion_dir(const rend_service_t *s, int rend_service_load_all_keys(const smartlist_t *service_list) { - const smartlist_t *s_list = NULL; - /* If no special service list is provided, then just use the global one. */ - if (!service_list) { - if (BUG(!rend_service_list)) { - return -1; - } - s_list = rend_service_list; - } else { - s_list = service_list; + /* Use service_list for unit tests */ + const smartlist_t *s_list = rend_get_service_list(service_list); + if (BUG(!s_list)) { + return -1; } SMARTLIST_FOREACH_BEGIN(s_list, rend_service_t *, s) { if (s->private_key) continue; - log_info(LD_REND, "Loading hidden-service keys from \"%s\"", - s->directory); + log_info(LD_REND, "Loading hidden-service keys from %s", + rend_service_escaped_dir(s)); if (rend_service_load_keys(s) < 0) return -1; @@ -1296,9 +1319,9 @@ rend_services_add_filenames_to_lists(smartlist_t *open_lst, if (!rend_service_list) return; SMARTLIST_FOREACH_BEGIN(rend_service_list, rend_service_t *, s) { - if (s->directory) { + if (!rend_service_is_ephemeral(s)) { rend_service_add_filenames_to_list(open_lst, s); - smartlist_add(stat_lst, tor_strdup(s->directory)); + smartlist_add_strdup(stat_lst, s->directory); } } SMARTLIST_FOREACH_END(s); } @@ -1793,7 +1816,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, const or_options_t *options = get_options(); char *err_msg = NULL; int err_msg_severity = LOG_WARN; - const char *stage_descr = NULL; + const char *stage_descr = NULL, *rend_pk_digest; int reason = END_CIRC_REASON_TORPROTOCOL; /* Service/circuit/key stuff we can learn before parsing */ char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; @@ -1827,14 +1850,15 @@ rend_service_receive_introduction(origin_circuit_t *circuit, assert_circ_anonymity_ok(circuit, options); tor_assert(circuit->rend_data); + /* XXX: This is version 2 specific (only one supported). */ + rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL); /* 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); + rend_pk_digest, REND_SERVICE_ID_LEN); /* 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(rend_pk_digest); if (!service) { log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro " @@ -2057,8 +2081,7 @@ rend_service_receive_introduction(origin_circuit_t *circuit, /* Fill in the circuit's state. */ launched->rend_data = - rend_data_service_create(service->service_id, - circuit->rend_data->rend_pk_digest, + rend_data_service_create(service->service_id, rend_pk_digest, parsed_req->rc, service->auth_type); launched->build_state->service_pending_final_cpath_ref = @@ -3092,9 +3115,9 @@ count_intro_point_circuits(const rend_service_t *service) circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); if (oc->rend_data && - !rend_cmp_service_ids(service->service_id, - oc->rend_data->onion_address)) + rend_circuit_pk_digest_eq(oc, (uint8_t *) service->pk_digest)) { num_ipos++; + } } } SMARTLIST_FOREACH_END(circ); @@ -3114,17 +3137,19 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) char auth[DIGEST_LEN + 9]; char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; int reason = END_CIRC_REASON_TORPROTOCOL; + const char *rend_pk_digest; tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO); assert_circ_anonymity_ok(circuit, get_options()); tor_assert(circuit->cpath); tor_assert(circuit->rend_data); + /* XXX: This is version 2 specific (only on supported). */ + rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, - circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN); + rend_pk_digest, REND_SERVICE_ID_LEN); - service = rend_service_get_by_pk_digest( - circuit->rend_data->rend_pk_digest); + service = rend_service_get_by_pk_digest(rend_pk_digest); if (!service) { log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %u.", safe_str_client(serviceid), (unsigned)circuit->base_.n_circ_id); @@ -3165,9 +3190,8 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_C_GENERAL); { - rend_data_t *rend_data = circuit->rend_data; + rend_data_free(circuit->rend_data); circuit->rend_data = NULL; - rend_data_free(rend_data); } { crypto_pk_t *intro_key = circuit->intro_key; @@ -3219,8 +3243,7 @@ rend_service_intro_has_opened(origin_circuit_t *circuit) log_info(LD_GENERAL, "Couldn't send introduction request for service %s on circuit %u", serviceid, (unsigned)circuit->base_.n_circ_id); - reason = END_CIRC_REASON_INTERNAL; - goto err; + goto done; } /* We've attempted to use this circuit */ @@ -3251,15 +3274,17 @@ rend_service_intro_established(origin_circuit_t *circuit, char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; (void) request; (void) request_len; + tor_assert(circuit->rend_data); + /* XXX: This is version 2 specific (only supported one for now). */ + const char *rend_pk_digest = + (char *) rend_data_get_pk_digest(circuit->rend_data, NULL); if (circuit->base_.purpose != CIRCUIT_PURPOSE_S_ESTABLISH_INTRO) { log_warn(LD_PROTOCOL, "received INTRO_ESTABLISHED cell on non-intro circuit."); goto err; } - tor_assert(circuit->rend_data); - service = rend_service_get_by_pk_digest( - circuit->rend_data->rend_pk_digest); + service = rend_service_get_by_pk_digest(rend_pk_digest); if (!service) { log_warn(LD_REND, "Unknown service on introduction circuit %u.", (unsigned)circuit->base_.n_circ_id); @@ -3282,7 +3307,7 @@ rend_service_intro_established(origin_circuit_t *circuit, circuit_change_purpose(TO_CIRCUIT(circuit), CIRCUIT_PURPOSE_S_INTRO); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32 + 1, - circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN); + rend_pk_digest, REND_SERVICE_ID_LEN); log_info(LD_REND, "Received INTRO_ESTABLISHED cell on circuit %u for service %s", (unsigned)circuit->base_.n_circ_id, serviceid); @@ -3309,6 +3334,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) char serviceid[REND_SERVICE_ID_LEN_BASE32+1]; char hexcookie[9]; int reason; + const char *rend_cookie, *rend_pk_digest; tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); tor_assert(circuit->cpath); @@ -3316,6 +3342,11 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) assert_circ_anonymity_ok(circuit, get_options()); tor_assert(circuit->rend_data); + /* XXX: This is version 2 specific (only one supported). */ + rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, + NULL); + rend_cookie = circuit->rend_data->rend_cookie; + /* Declare the circuit dirty to avoid reuse, and for path-bias */ if (!circuit->base_.timestamp_dirty) circuit->base_.timestamp_dirty = time(NULL); @@ -3325,9 +3356,9 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) hop = circuit->build_state->service_pending_final_cpath_ref->cpath; - base16_encode(hexcookie,9,circuit->rend_data->rend_cookie,4); + base16_encode(hexcookie,9, rend_cookie,4); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, - circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN); + rend_pk_digest, REND_SERVICE_ID_LEN); log_info(LD_REND, "Done building circuit %u to rendezvous with " @@ -3356,8 +3387,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) circuit->build_state->pending_final_cpath = hop; circuit->build_state->service_pending_final_cpath_ref->cpath = NULL; - service = rend_service_get_by_pk_digest( - circuit->rend_data->rend_pk_digest); + service = rend_service_get_by_pk_digest(rend_pk_digest); if (!service) { log_warn(LD_GENERAL, "Internal error: unrecognized service ID on " "rendezvous circuit."); @@ -3366,7 +3396,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) } /* All we need to do is send a RELAY_RENDEZVOUS1 cell... */ - memcpy(buf, circuit->rend_data->rend_cookie, REND_COOKIE_LEN); + memcpy(buf, rend_cookie, REND_COOKIE_LEN); if (crypto_dh_get_public(hop->rend_dh_handshake_state, buf+REND_COOKIE_LEN, DH_KEY_LEN)<0) { log_warn(LD_GENERAL,"Couldn't get DH public key."); @@ -3382,8 +3412,7 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) buf, REND_COOKIE_LEN+DH_KEY_LEN+DIGEST_LEN, circuit->cpath->prev)<0) { log_warn(LD_GENERAL, "Couldn't send RENDEZVOUS1 cell."); - reason = END_CIRC_REASON_INTERNAL; - goto err; + goto done; } crypto_dh_free(hop->rend_dh_handshake_state); @@ -3430,8 +3459,8 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest) origin_circuit_t *circ = NULL; tor_assert(intro); - while ((circ = circuit_get_next_by_pk_and_purpose(circ,pk_digest, - CIRCUIT_PURPOSE_S_INTRO))) { + while ((circ = circuit_get_next_by_pk_and_purpose(circ, + (uint8_t *) pk_digest, CIRCUIT_PURPOSE_S_INTRO))) { if (tor_memeq(circ->build_state->chosen_exit->identity_digest, intro->extend_info->identity_digest, DIGEST_LEN) && circ->rend_data) { @@ -3440,8 +3469,9 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest) } circ = NULL; - while ((circ = circuit_get_next_by_pk_and_purpose(circ,pk_digest, - CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) { + while ((circ = circuit_get_next_by_pk_and_purpose(circ, + (uint8_t *) pk_digest, + CIRCUIT_PURPOSE_S_ESTABLISH_INTRO))) { if (tor_memeq(circ->build_state->chosen_exit->identity_digest, intro->extend_info->identity_digest, DIGEST_LEN) && circ->rend_data) { @@ -3480,7 +3510,7 @@ find_intro_point(origin_circuit_t *circ) tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO); tor_assert(circ->rend_data); - serviceid = circ->rend_data->onion_address; + serviceid = rend_data_get_address(circ->rend_data); SMARTLIST_FOREACH(rend_service_list, rend_service_t *, s, if (tor_memeq(s->service_id, serviceid, REND_SERVICE_ID_LEN_BASE32)) { @@ -3865,10 +3895,13 @@ void rend_service_desc_has_uploaded(const rend_data_t *rend_data) { rend_service_t *service; + const char *onion_address; tor_assert(rend_data); - service = rend_service_get_by_service_id(rend_data->onion_address); + onion_address = rend_data_get_address(rend_data); + + service = rend_service_get_by_service_id(onion_address); if (service == NULL) { return; } @@ -4168,8 +4201,8 @@ rend_service_dump_stats(int severity) for (i=0; i < smartlist_len(rend_service_list); ++i) { service = smartlist_get(rend_service_list, i); - tor_log(severity, LD_GENERAL, "Service configured in \"%s\":", - service->directory); + tor_log(severity, LD_GENERAL, "Service configured in %s:", + rend_service_escaped_dir(service)); for (j=0; j < smartlist_len(service->intro_nodes); ++j) { intro = smartlist_get(service->intro_nodes, j); safe_name = safe_str_client(intro->extend_info->nickname); @@ -4255,14 +4288,16 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, smartlist_t *matching_ports; rend_service_port_config_t *chosen_port; unsigned int warn_once = 0; + const char *rend_pk_digest; tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); tor_assert(circ->rend_data); log_debug(LD_REND,"beginning to hunt for addr/port"); + /* XXX: This is version 2 specific (only one supported). */ + rend_pk_digest = (char *) rend_data_get_pk_digest(circ->rend_data, NULL); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, - circ->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN); - service = rend_service_get_by_pk_digest( - circ->rend_data->rend_pk_digest); + rend_pk_digest, REND_SERVICE_ID_LEN); + service = rend_service_get_by_pk_digest(rend_pk_digest); if (!service) { log_warn(LD_REND, "Couldn't find any service associated with pk %s on " "rendezvous circuit %u; closing.", diff --git a/src/or/router.c b/src/or/router.c index 6d3a32a60c..fd2942ec67 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -2206,7 +2206,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) log_warn(LD_CONFIG, "There is a router named \"%s\" in my " "declared family, but that isn't a legal nickname. " "Skipping it.", escaped(name)); - smartlist_add(warned_nonexistent_family, tor_strdup(name)); + smartlist_add_strdup(warned_nonexistent_family, name); } if (is_legal) { smartlist_add(ri->declared_family, name); @@ -2881,7 +2881,7 @@ router_dump_router_to_string(routerinfo_t *router, /* Write the exit policy to the end of 's'. */ if (!router->exit_policy || !smartlist_len(router->exit_policy)) { - smartlist_add(chunks, tor_strdup("reject *:*\n")); + smartlist_add_strdup(chunks, "reject *:*\n"); } else if (router->exit_policy) { char *exit_policy = router_dump_exit_policy_to_string(router,1,0); @@ -2903,12 +2903,12 @@ router_dump_router_to_string(routerinfo_t *router, if (decide_to_advertise_begindir(options, router->supports_tunnelled_dir_requests)) { - smartlist_add(chunks, tor_strdup("tunnelled-dir-server\n")); + smartlist_add_strdup(chunks, "tunnelled-dir-server\n"); } /* Sign the descriptor with Ed25519 */ if (emit_ed_sigs) { - smartlist_add(chunks, tor_strdup("router-sig-ed25519 ")); + smartlist_add_strdup(chunks, "router-sig-ed25519 "); crypto_digest_smartlist_prefix(digest, DIGEST256_LEN, ED_DESC_SIGNATURE_PREFIX, chunks, "", DIGEST_SHA256); @@ -2924,7 +2924,7 @@ router_dump_router_to_string(routerinfo_t *router, } /* Sign the descriptor with RSA */ - smartlist_add(chunks, tor_strdup("router-signature\n")); + smartlist_add_strdup(chunks, "router-signature\n"); crypto_digest_smartlist(digest, DIGEST_LEN, chunks, "", DIGEST_SHA1); @@ -2939,7 +2939,7 @@ router_dump_router_to_string(routerinfo_t *router, } /* include a last '\n' */ - smartlist_add(chunks, tor_strdup("\n")); + smartlist_add_strdup(chunks, "\n"); output = smartlist_join_strings(chunks, "", 0, NULL); @@ -3197,13 +3197,13 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, if (should_record_bridge_info(options) && write_stats_to_extrainfo) { const char *bridge_stats = geoip_get_bridge_stats_extrainfo(now); if (bridge_stats) { - smartlist_add(chunks, tor_strdup(bridge_stats)); + smartlist_add_strdup(chunks, bridge_stats); } } if (emit_ed_sigs) { char sha256_digest[DIGEST256_LEN]; - smartlist_add(chunks, tor_strdup("router-sig-ed25519 ")); + smartlist_add_strdup(chunks, "router-sig-ed25519 "); crypto_digest_smartlist_prefix(sha256_digest, DIGEST256_LEN, ED_DESC_SIGNATURE_PREFIX, chunks, "", DIGEST_SHA256); @@ -3218,7 +3218,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, smartlist_add_asprintf(chunks, "%s\n", buf); } - smartlist_add(chunks, tor_strdup("router-signature\n")); + smartlist_add_strdup(chunks, "router-signature\n"); s = smartlist_join_strings(chunks, "", 0, NULL); while (strlen(s) > MAX_EXTRAINFO_UPLOAD_SIZE - DIROBJ_MAX_SIG_LEN) { @@ -3253,7 +3253,7 @@ extrainfo_dump_to_string(char **s_out, extrainfo_t *extrainfo, "descriptor."); goto err; } - smartlist_add(chunks, tor_strdup(sig)); + smartlist_add_strdup(chunks, sig); tor_free(s); s = smartlist_join_strings(chunks, "", 0, NULL); diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index ca32228fc7..6c53c50305 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -5,8 +5,13 @@ * \file routerkeys.c * * \brief Functions and structures to handle generating and maintaining the - * set of keypairs necessary to be an OR. (Some of the code in router.c - * belongs here.) + * set of keypairs necessary to be an OR. + * + * The keys handled here now are the Ed25519 keys that Tor relays use to sign + * descriptors, authenticate themselves on links, and identify one another + * uniquely. Other keys are maintained in router.c and rendservice.c. + * + * (TODO: The keys in router.c should go here too.) */ #include "or.h" @@ -19,6 +24,7 @@ #define ENC_KEY_HEADER "Boxed Ed25519 key" #define ENC_KEY_TAG "master" +/* DOCDOC */ static ssize_t do_getpass(const char *prompt, char *buf, size_t buflen, int twice, const or_options_t *options) @@ -85,6 +91,7 @@ do_getpass(const char *prompt, char *buf, size_t buflen, return length; } +/* DOCDOC */ int read_encrypted_secret_key(ed25519_secret_key_t *out, const char *fname) @@ -157,6 +164,7 @@ read_encrypted_secret_key(ed25519_secret_key_t *out, return r; } +/* DOCDOC */ int write_encrypted_secret_key(const ed25519_secret_key_t *key, const char *fname) @@ -200,6 +208,7 @@ write_encrypted_secret_key(const ed25519_secret_key_t *key, return r; } +/* DOCDOC */ static int write_secret_key(const ed25519_secret_key_t *key, int encrypted, const char *fname, @@ -733,8 +742,12 @@ load_ed_keys(const or_options_t *options, time_t now) if (need_new_signing_key) { log_notice(LD_OR, "It looks like I need to generate and sign a new " - "medium-term signing key, because %s. To do that, I need to " - "load%s the permanent master identity key.", + "medium-term signing key, because %s. To do that, I " + "need to load%s the permanent master identity key. " + "If the master identity key was not moved or encrypted " + "with a passphrase, this will be done automatically and " + "no further action is required. Otherwise, provide the " + "necessary data using 'tor --keygen' to do it manually.", (NULL == use_signing) ? "I don't have one" : EXPIRES_SOON(check_signing_cert, 0) ? "the one I have is expired" : "you asked me to make one with --keygen", @@ -742,15 +755,19 @@ load_ed_keys(const or_options_t *options, time_t now) } else if (want_new_signing_key && !offline_master) { log_notice(LD_OR, "It looks like I should try to generate and sign a " "new medium-term signing key, because the one I have is " - "going to expire soon. To do that, I'm going to have to try to " - "load the permanent master identity key."); + "going to expire soon. To do that, I'm going to have to " + "try to load the permanent master identity key. " + "If the master identity key was not moved or encrypted " + "with a passphrase, this will be done automatically and " + "no further action is required. Otherwise, provide the " + "necessary data using 'tor --keygen' to do it manually."); } else if (want_new_signing_key) { log_notice(LD_OR, "It looks like I should try to generate and sign a " "new medium-term signing key, because the one I have is " "going to expire soon. But OfflineMasterKey is set, so I " - "won't try to load a permanent master identity key is set. " - "You will need to use 'tor --keygen' make a new signing key " - "and certificate."); + "won't try to load a permanent master identity key. You " + "will need to use 'tor --keygen' to make a new signing " + "key and certificate."); } { @@ -927,7 +944,18 @@ load_ed_keys(const or_options_t *options, time_t now) return -1; } -/* DOCDOC */ +/** + * Retrieve our currently-in-use Ed25519 link certificate and id certificate, + * and, if they would expire soon (based on the time <b>now</b>, generate new + * certificates (without embedding the public part of the signing key inside). + * + * The signed_key from the expiring certificate will be used to sign the new + * key within newly generated X509 certificate. + * + * Returns -1 upon error. Otherwise, returns 0 upon success (either when the + * current certificate is still valid, or when a new certificate was + * successfully generated). + */ int generate_ed_link_cert(const or_options_t *options, time_t now) { @@ -967,6 +995,17 @@ generate_ed_link_cert(const or_options_t *options, time_t now) #undef SET_KEY #undef SET_CERT +/** + * Return 1 if any of the following are true: + * + * - if one of our Ed25519 signing, auth, or link certificates would expire + * soon w.r.t. the time <b>now</b>, + * - if we do not currently have a link certificate, or + * - if our cached Ed25519 link certificate is not same as the one we're + * currently using. + * + * Otherwise, returns 0. + */ int should_make_new_ed_keys(const or_options_t *options, const time_t now) { @@ -997,6 +1036,61 @@ should_make_new_ed_keys(const or_options_t *options, const time_t now) #undef EXPIRES_SOON +#ifdef TOR_UNIT_TESTS +/* Helper for unit tests: populate the ed25519 keys without saving or + * loading */ +void +init_mock_ed_keys(const crypto_pk_t *rsa_identity_key) +{ + routerkeys_free_all(); + +#define MAKEKEY(k) \ + k = tor_malloc_zero(sizeof(*k)); \ + if (ed25519_keypair_generate(k, 0) < 0) { \ + log_warn(LD_BUG, "Couldn't make a keypair"); \ + goto err; \ + } + MAKEKEY(master_identity_key); + MAKEKEY(master_signing_key); + MAKEKEY(current_auth_key); +#define MAKECERT(cert, signing, signed_, type, flags) \ + cert = tor_cert_create(signing, \ + type, \ + &signed_->pubkey, \ + time(NULL), 86400, \ + flags); \ + if (!cert) { \ + log_warn(LD_BUG, "Couldn't make a %s certificate!", #cert); \ + goto err; \ + } + + MAKECERT(signing_key_cert, + master_identity_key, master_signing_key, CERT_TYPE_ID_SIGNING, + CERT_FLAG_INCLUDE_SIGNING_KEY); + MAKECERT(auth_key_cert, + master_signing_key, current_auth_key, CERT_TYPE_SIGNING_AUTH, 0); + + if (generate_ed_link_cert(get_options(), time(NULL)) < 0) { + log_warn(LD_BUG, "Couldn't make link certificate"); + goto err; + } + + rsa_ed_crosscert_len = tor_make_rsa_ed25519_crosscert( + &master_identity_key->pubkey, + rsa_identity_key, + time(NULL)+86400, + &rsa_ed_crosscert); + + return; + + err: + routerkeys_free_all(); + tor_assert_nonfatal_unreached(); +} +#undef MAKEKEY +#undef MAKECERT +#endif + const ed25519_public_key_t * get_master_identity_key(void) { @@ -1005,6 +1099,16 @@ get_master_identity_key(void) return &master_identity_key->pubkey; } +#ifdef TOR_UNIT_TESTS +/* only exists for the unit tests, since otherwise the identity key + * should be used to sign nothing but the signing key. */ +const ed25519_keypair_t * +get_master_identity_keypair(void) +{ + return master_identity_key; +} +#endif + const ed25519_keypair_t * get_master_signing_keypair(void) { @@ -1139,9 +1243,12 @@ routerkeys_free_all(void) tor_cert_free(signing_key_cert); tor_cert_free(link_cert_cert); tor_cert_free(auth_key_cert); + tor_free(rsa_ed_crosscert); master_identity_key = master_signing_key = NULL; current_auth_key = NULL; signing_key_cert = link_cert_cert = auth_key_cert = NULL; + rsa_ed_crosscert = NULL; // redundant + rsa_ed_crosscert_len = 0; } diff --git a/src/or/routerkeys.h b/src/or/routerkeys.h index be9b19aea8..307a1cd234 100644 --- a/src/or/routerkeys.h +++ b/src/or/routerkeys.h @@ -73,5 +73,10 @@ int write_encrypted_secret_key(const ed25519_secret_key_t *out, void routerkeys_free_all(void); +#ifdef TOR_UNIT_TESTS +const ed25519_keypair_t *get_master_identity_keypair(void); +void init_mock_ed_keys(const crypto_pk_t *rsa_identity_key); +#endif + #endif diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 56c0522cdc..9bcca76b63 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -586,7 +586,7 @@ trusted_dirs_load_certs_from_string(const char *contents, int source, "signing key %s", from_store ? "cached" : "downloaded", ds->nickname, hex_str(cert->signing_key_digest,DIGEST_LEN)); } else { - int adding = directory_caches_unknown_auth_certs(get_options()); + int adding = we_want_to_fetch_unknown_auth_certs(get_options()); log_info(LD_DIR, "%s %s certificate for unrecognized directory " "authority with signing key %s", adding ? "Adding" : "Not adding", @@ -929,7 +929,8 @@ authority_certs_fetch_resource_impl(const char *resource, const routerstatus_t *rs) { const or_options_t *options = get_options(); - int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0); + int get_via_tor = purpose_needs_anonymity(DIR_PURPOSE_FETCH_CERTIFICATE, 0, + resource); /* Make sure bridge clients never connect to anything but a bridge */ if (options->UseBridges) { @@ -1011,7 +1012,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now, char *resource = NULL; cert_list_t *cl; const or_options_t *options = get_options(); - const int cache = directory_caches_unknown_auth_certs(options); + const int keep_unknown = we_want_to_fetch_unknown_auth_certs(options); fp_pair_t *fp_tmp = NULL; char id_digest_str[2*DIGEST_LEN+1]; char sk_digest_str[2*DIGEST_LEN+1]; @@ -1083,9 +1084,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now, if (!smartlist_len(voter->sigs)) continue; /* This authority never signed this consensus, so don't * go looking for a cert with key digest 0000000000. */ - if (!cache && + if (!keep_unknown && !trusteddirserver_get_by_v3_auth_digest(voter->identity_digest)) - continue; /* We are not a cache, and we don't know this authority.*/ + continue; /* We don't want unknown certs, and we don't know this + * authority.*/ /* * If we don't know *any* cert for this authority, and a download by ID @@ -1202,7 +1204,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now, int need_plus = 0; smartlist_t *fps = smartlist_new(); - smartlist_add(fps, tor_strdup("fp/")); + smartlist_add_strdup(fps, "fp/"); SMARTLIST_FOREACH_BEGIN(missing_id_digests, const char *, d) { char *fp = NULL; @@ -1242,7 +1244,7 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now, int need_plus = 0; smartlist_t *fp_pairs = smartlist_new(); - smartlist_add(fp_pairs, tor_strdup("fp-sk/")); + smartlist_add_strdup(fp_pairs, "fp-sk/"); SMARTLIST_FOREACH_BEGIN(missing_cert_digests, const fp_pair_t *, d) { char *fp_pair = NULL; @@ -2037,9 +2039,9 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, !router_supports_extrainfo(node->identity, is_trusted_extrainfo)) continue; /* Don't make the same node a guard twice */ - if (for_guard && node->using_as_guard) { - continue; - } + if (for_guard && is_node_used_as_guard(node)) { + continue; + } /* Ensure that a directory guard is actually a guard node. */ if (for_guard && !node->is_possible_guard) { continue; @@ -3894,7 +3896,7 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg, router_describe(router)); *msg = "Router descriptor is not referenced by any network-status."; - /* Only journal this desc if we'll be serving it. */ + /* Only journal this desc if we want to keep old descriptors */ if (!from_cache && should_cache_old_descriptors()) signed_desc_append_to_journal(&router->cache_info, &routerlist->desc_store); @@ -4524,13 +4526,14 @@ router_load_extrainfo_from_string(const char *s, const char *eos, smartlist_free(extrainfo_list); } -/** Return true iff any networkstatus includes a descriptor whose digest - * is that of <b>desc</b>. */ +/** Return true iff the latest ns-flavored consensus includes a descriptor + * whose digest is that of <b>desc</b>. */ static int signed_desc_digest_is_recognized(signed_descriptor_t *desc) { const routerstatus_t *rs; - networkstatus_t *consensus = networkstatus_get_latest_consensus(); + networkstatus_t *consensus = networkstatus_get_latest_consensus_by_flavor( + FLAV_NS); if (consensus) { rs = networkstatus_vote_find_entry(consensus, desc->identity_digest); @@ -5153,7 +5156,7 @@ update_consensus_router_descriptor_downloads(time_t now, int is_vote, ++n_would_reject; continue; /* We would throw it out immediately. */ } - if (!directory_caches_dir_info(options) && + if (!we_want_to_fetch_flavor(options, consensus->flavor) && !client_would_use_router(rs, now, options)) { ++n_wouldnt_use; continue; /* We would never use it ourself. */ diff --git a/src/or/routerparse.c b/src/or/routerparse.c index a78d1ee53e..2cfd3fc58a 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -60,6 +60,7 @@ #include "circuitstats.h" #include "dirserv.h" #include "dirvote.h" +#include "parsecommon.h" #include "policies.h" #include "protover.h" #include "rendcommon.h" @@ -81,267 +82,6 @@ /****************************************************************************/ -/** Enumeration of possible token types. The ones starting with K_ correspond - * to directory 'keywords'. A_ is for an annotation, R or C is related to - * hidden services, ERR_ is an error in the tokenizing process, EOF_ is an - * end-of-file marker, and NIL_ is used to encode not-a-token. - */ -typedef enum { - K_ACCEPT = 0, - K_ACCEPT6, - K_DIRECTORY_SIGNATURE, - K_RECOMMENDED_SOFTWARE, - K_REJECT, - K_REJECT6, - K_ROUTER, - K_SIGNED_DIRECTORY, - K_SIGNING_KEY, - K_ONION_KEY, - K_ONION_KEY_NTOR, - K_ROUTER_SIGNATURE, - K_PUBLISHED, - K_RUNNING_ROUTERS, - K_ROUTER_STATUS, - K_PLATFORM, - K_PROTO, - K_OPT, - K_BANDWIDTH, - K_CONTACT, - K_NETWORK_STATUS, - K_UPTIME, - K_DIR_SIGNING_KEY, - K_FAMILY, - K_FINGERPRINT, - K_HIBERNATING, - K_READ_HISTORY, - K_WRITE_HISTORY, - K_NETWORK_STATUS_VERSION, - K_DIR_SOURCE, - K_DIR_OPTIONS, - K_CLIENT_VERSIONS, - K_SERVER_VERSIONS, - K_RECOMMENDED_CLIENT_PROTOCOLS, - K_RECOMMENDED_RELAY_PROTOCOLS, - K_REQUIRED_CLIENT_PROTOCOLS, - K_REQUIRED_RELAY_PROTOCOLS, - K_OR_ADDRESS, - K_ID, - K_P, - K_P6, - K_R, - K_A, - K_S, - K_V, - K_W, - K_M, - K_EXTRA_INFO, - K_EXTRA_INFO_DIGEST, - K_CACHES_EXTRA_INFO, - K_HIDDEN_SERVICE_DIR, - K_ALLOW_SINGLE_HOP_EXITS, - K_IPV6_POLICY, - K_ROUTER_SIG_ED25519, - K_IDENTITY_ED25519, - K_MASTER_KEY_ED25519, - K_ONION_KEY_CROSSCERT, - K_NTOR_ONION_KEY_CROSSCERT, - - K_DIRREQ_END, - K_DIRREQ_V2_IPS, - K_DIRREQ_V3_IPS, - K_DIRREQ_V2_REQS, - K_DIRREQ_V3_REQS, - K_DIRREQ_V2_SHARE, - K_DIRREQ_V3_SHARE, - K_DIRREQ_V2_RESP, - K_DIRREQ_V3_RESP, - K_DIRREQ_V2_DIR, - K_DIRREQ_V3_DIR, - K_DIRREQ_V2_TUN, - K_DIRREQ_V3_TUN, - K_ENTRY_END, - K_ENTRY_IPS, - K_CELL_END, - K_CELL_PROCESSED, - K_CELL_QUEUED, - K_CELL_TIME, - K_CELL_CIRCS, - K_EXIT_END, - K_EXIT_WRITTEN, - K_EXIT_READ, - K_EXIT_OPENED, - - K_DIR_KEY_CERTIFICATE_VERSION, - K_DIR_IDENTITY_KEY, - K_DIR_KEY_PUBLISHED, - K_DIR_KEY_EXPIRES, - K_DIR_KEY_CERTIFICATION, - K_DIR_KEY_CROSSCERT, - K_DIR_ADDRESS, - K_DIR_TUNNELLED, - - K_VOTE_STATUS, - K_VALID_AFTER, - K_FRESH_UNTIL, - K_VALID_UNTIL, - K_VOTING_DELAY, - - K_KNOWN_FLAGS, - K_PARAMS, - K_BW_WEIGHTS, - K_VOTE_DIGEST, - K_CONSENSUS_DIGEST, - K_ADDITIONAL_DIGEST, - K_ADDITIONAL_SIGNATURE, - K_CONSENSUS_METHODS, - K_CONSENSUS_METHOD, - K_LEGACY_DIR_KEY, - K_DIRECTORY_FOOTER, - K_SIGNING_CERT_ED, - K_SR_FLAG, - K_COMMIT, - K_PREVIOUS_SRV, - K_CURRENT_SRV, - K_PACKAGE, - - A_PURPOSE, - A_LAST_LISTED, - A_UNKNOWN_, - - R_RENDEZVOUS_SERVICE_DESCRIPTOR, - R_VERSION, - R_PERMANENT_KEY, - R_SECRET_ID_PART, - R_PUBLICATION_TIME, - R_PROTOCOL_VERSIONS, - R_INTRODUCTION_POINTS, - R_SIGNATURE, - - R_IPO_IDENTIFIER, - R_IPO_IP_ADDRESS, - R_IPO_ONION_PORT, - R_IPO_ONION_KEY, - R_IPO_SERVICE_KEY, - - C_CLIENT_NAME, - C_DESCRIPTOR_COOKIE, - C_CLIENT_KEY, - - ERR_, - EOF_, - NIL_ -} directory_keyword; - -#define MIN_ANNOTATION A_PURPOSE -#define MAX_ANNOTATION A_UNKNOWN_ - -/** Structure to hold a single directory token. - * - * We parse a directory by breaking it into "tokens", each consisting - * of a keyword, a line full of arguments, and a binary object. The - * arguments and object are both optional, depending on the keyword - * type. - * - * This structure is only allocated in memareas; do not allocate it on - * the heap, or token_clear() won't work. - */ -typedef struct directory_token_t { - directory_keyword tp; /**< Type of the token. */ - int n_args:30; /**< Number of elements in args */ - char **args; /**< Array of arguments from keyword line. */ - - char *object_type; /**< -----BEGIN [object_type]-----*/ - size_t object_size; /**< Bytes in object_body */ - char *object_body; /**< Contents of object, base64-decoded. */ - - crypto_pk_t *key; /**< For public keys only. Heap-allocated. */ - - char *error; /**< For ERR_ tokens only. */ -} directory_token_t; - -/* ********************************************************************** */ - -/** We use a table of rules to decide how to parse each token type. */ - -/** Rules for whether the keyword needs an object. */ -typedef enum { - NO_OBJ, /**< No object, ever. */ - NEED_OBJ, /**< Object is required. */ - NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */ - NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */ - NEED_KEY, /**< Object is required, and must be a public key. */ - OBJ_OK, /**< Object is optional. */ -} obj_syntax; - -#define AT_START 1 -#define AT_END 2 - -/** Determines the parsing rules for a single token type. */ -typedef struct token_rule_t { - /** The string value of the keyword identifying the type of item. */ - const char *t; - /** The corresponding directory_keyword enum. */ - directory_keyword v; - /** Minimum number of arguments for this item */ - int min_args; - /** Maximum number of arguments for this item */ - int max_args; - /** If true, we concatenate all arguments for this item into a single - * string. */ - int concat_args; - /** Requirements on object syntax for this item. */ - obj_syntax os; - /** Lowest number of times this item may appear in a document. */ - int min_cnt; - /** Highest number of times this item may appear in a document. */ - int max_cnt; - /** One or more of AT_START/AT_END to limit where the item may appear in a - * document. */ - int pos; - /** True iff this token is an annotation. */ - int is_annotation; -} token_rule_t; - -/** - * @name macros for defining token rules - * - * Helper macros to define token tables. 's' is a string, 't' is a - * directory_keyword, 'a' is a trio of argument multiplicities, and 'o' is an - * object syntax. - */ -/**@{*/ - -/** Appears to indicate the end of a table. */ -#define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 } -/** An item with no restrictions: used for obsolete document types */ -#define T(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 } -/** An item with no restrictions on multiplicity or location. */ -#define T0N(s,t,a,o) { s, t, a, o, 0, INT_MAX, 0, 0 } -/** An item that must appear exactly once */ -#define T1(s,t,a,o) { s, t, a, o, 1, 1, 0, 0 } -/** An item that must appear exactly once, at the start of the document */ -#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START, 0 } -/** An item that must appear exactly once, at the end of the document */ -#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END, 0 } -/** An item that must appear one or more times */ -#define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0, 0 } -/** An item that must appear no more than once */ -#define T01(s,t,a,o) { s, t, a, o, 0, 1, 0, 0 } -/** An annotation that must appear no more than once */ -#define A01(s,t,a,o) { s, t, a, o, 0, 1, 0, 1 } - -/** Argument multiplicity: any number of arguments. */ -#define ARGS 0,INT_MAX,0 -/** Argument multiplicity: no arguments. */ -#define NO_ARGS 0,0,0 -/** Argument multiplicity: concatenate all arguments. */ -#define CONCAT_ARGS 1,1,1 -/** Argument multiplicity: at least <b>n</b> arguments. */ -#define GE(n) n,INT_MAX,0 -/** Argument multiplicity: exactly <b>n</b> arguments. */ -#define EQ(n) n,n,0 -/**@}*/ - /** List of tokens recognized in router descriptors */ static token_rule_t routerdesc_token_table[] = { T0N("reject", K_REJECT, ARGS, NO_OBJ ), @@ -628,28 +368,8 @@ static int router_get_hashes_impl(const char *s, size_t s_len, common_digests_t *digests, const char *start_str, const char *end_str, char end_char); -static void token_clear(directory_token_t *tok); -static smartlist_t *find_all_by_keyword(smartlist_t *s, directory_keyword k); static smartlist_t *find_all_exitpolicy(smartlist_t *s); -static directory_token_t *find_by_keyword_(smartlist_t *s, - directory_keyword keyword, - const char *keyword_str); -#define find_by_keyword(s, keyword) find_by_keyword_((s), (keyword), #keyword) -static directory_token_t *find_opt_by_keyword(smartlist_t *s, - directory_keyword keyword); - -#define TS_ANNOTATIONS_OK 1 -#define TS_NOCHECK 2 -#define TS_NO_NEW_ANNOTATIONS 4 -static int tokenize_string(memarea_t *area, - const char *start, const char *end, - smartlist_t *out, - token_rule_t *table, - int flags); -static directory_token_t *get_next_token(memarea_t *area, - const char **s, - const char *eos, - token_rule_t *table); + #define CST_CHECK_AUTHORITY (1<<0) #define CST_NO_CHECK_OBJTYPE (1<<1) static int check_signature_token(const char *digest, @@ -2100,12 +1820,13 @@ router_parse_entry_from_string(const char *s, const char *end, ed25519_checkable_t check[3]; int check_ok[3]; - if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) { + time_t expires = TIME_MAX; + if (tor_cert_get_checkable_sig(&check[0], cert, NULL, &expires) < 0) { log_err(LD_BUG, "Couldn't create 'checkable' for cert."); goto err; } if (tor_cert_get_checkable_sig(&check[1], - ntor_cc_cert, &ntor_cc_pk) < 0) { + ntor_cc_cert, &ntor_cc_pk, &expires) < 0) { log_err(LD_BUG, "Couldn't create 'checkable' for ntor_cc_cert."); goto err; } @@ -2135,10 +1856,7 @@ router_parse_entry_from_string(const char *s, const char *end, } /* We check this before adding it to the routerlist. */ - if (cert->valid_until < ntor_cc_cert->valid_until) - router->cert_expiration_time = cert->valid_until; - else - router->cert_expiration_time = ntor_cc_cert->valid_until; + router->cert_expiration_time = expires; } } @@ -2220,7 +1938,7 @@ router_parse_entry_from_string(const char *s, const char *end, escaped(tok->args[i])); goto err; } - smartlist_add(router->declared_family, tor_strdup(tok->args[i])); + smartlist_add_strdup(router->declared_family, tok->args[i]); } } @@ -2452,7 +2170,7 @@ extrainfo_parse_entry_from_string(const char *s, const char *end, ed25519_checkable_t check[2]; int check_ok[2]; - if (tor_cert_get_checkable_sig(&check[0], cert, NULL) < 0) { + if (tor_cert_get_checkable_sig(&check[0], cert, NULL, NULL) < 0) { log_err(LD_BUG, "Couldn't create 'checkable' for cert."); goto err; } @@ -2964,6 +2682,8 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->protocols_known = 1; rs->supports_extend2_cells = protocol_list_supports_protocol(tok->args[0], PRT_RELAY, 2); + rs->supports_ed25519_link_handshake = + protocol_list_supports_protocol(tok->args[0], PRT_LINKAUTH, 3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -3723,9 +3443,9 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHODS); if (tok) { for (i=0; i < tok->n_args; ++i) - smartlist_add(ns->supported_methods, tor_strdup(tok->args[i])); + smartlist_add_strdup(ns->supported_methods, tok->args[i]); } else { - smartlist_add(ns->supported_methods, tor_strdup("1")); + smartlist_add_strdup(ns->supported_methods, "1"); } } else { tok = find_opt_by_keyword(tokens, K_CONSENSUS_METHOD); @@ -3807,7 +3527,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, ns->package_lines = smartlist_new(); if (package_lst) { SMARTLIST_FOREACH(package_lst, directory_token_t *, t, - smartlist_add(ns->package_lines, tor_strdup(t->args[0]))); + smartlist_add_strdup(ns->package_lines, t->args[0])); } smartlist_free(package_lst); } @@ -3816,7 +3536,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, ns->known_flags = smartlist_new(); inorder = 1; for (i = 0; i < tok->n_args; ++i) { - smartlist_add(ns->known_flags, tor_strdup(tok->args[i])); + smartlist_add_strdup(ns->known_flags, tok->args[i]); if (i>0 && strcmp(tok->args[i-1], tok->args[i])>= 0) { log_warn(LD_DIR, "%s >= %s", tok->args[i-1], tok->args[i]); inorder = 0; @@ -3868,7 +3588,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, } tor_free(last_kwd); last_kwd = tor_strndup(tok->args[i], eq_pos); - smartlist_add(ns->net_params, tor_strdup(tok->args[i])); + smartlist_add_strdup(ns->net_params, tok->args[i]); } if (!inorder) { log_warn(LD_DIR, "params not in order"); @@ -4111,7 +3831,7 @@ networkstatus_parse_vote_from_string(const char *s, const char **eos_out, log_warn(LD_DIR, "Bad element '%s' in params", escaped(tok->args[i])); goto err; } - smartlist_add(ns->weight_params, tor_strdup(tok->args[i])); + smartlist_add_strdup(ns->weight_params, tok->args[i]); } } @@ -4740,445 +4460,6 @@ assert_addr_policy_ok(smartlist_t *lst) }); } -/* - * Low-level tokenizer for router descriptors and directories. - */ - -/** Free all resources allocated for <b>tok</b> */ -static void -token_clear(directory_token_t *tok) -{ - if (tok->key) - crypto_pk_free(tok->key); -} - -#define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz) -#define ALLOC(sz) memarea_alloc(area,sz) -#define STRDUP(str) memarea_strdup(area,str) -#define STRNDUP(str,n) memarea_strndup(area,(str),(n)) - -#define RET_ERR(msg) \ - STMT_BEGIN \ - if (tok) token_clear(tok); \ - tok = ALLOC_ZERO(sizeof(directory_token_t)); \ - tok->tp = ERR_; \ - tok->error = STRDUP(msg); \ - goto done_tokenizing; \ - STMT_END - -/** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys - * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>. - * Return <b>tok</b> on success, or a new ERR_ token if the token didn't - * conform to the syntax we wanted. - **/ -static inline directory_token_t * -token_check_object(memarea_t *area, const char *kwd, - directory_token_t *tok, obj_syntax o_syn) -{ - char ebuf[128]; - switch (o_syn) { - case NO_OBJ: - /* No object is allowed for this token. */ - if (tok->object_body) { - tor_snprintf(ebuf, sizeof(ebuf), "Unexpected object for %s", kwd); - RET_ERR(ebuf); - } - if (tok->key) { - tor_snprintf(ebuf, sizeof(ebuf), "Unexpected public key for %s", kwd); - RET_ERR(ebuf); - } - break; - case NEED_OBJ: - /* There must be a (non-key) object. */ - if (!tok->object_body) { - tor_snprintf(ebuf, sizeof(ebuf), "Missing object for %s", kwd); - RET_ERR(ebuf); - } - break; - case NEED_KEY_1024: /* There must be a 1024-bit public key. */ - case NEED_SKEY_1024: /* There must be a 1024-bit private key. */ - if (tok->key && crypto_pk_num_bits(tok->key) != PK_BYTES*8) { - tor_snprintf(ebuf, sizeof(ebuf), "Wrong size on key for %s: %d bits", - kwd, crypto_pk_num_bits(tok->key)); - RET_ERR(ebuf); - } - /* fall through */ - case NEED_KEY: /* There must be some kind of key. */ - if (!tok->key) { - tor_snprintf(ebuf, sizeof(ebuf), "Missing public key for %s", kwd); - RET_ERR(ebuf); - } - if (o_syn != NEED_SKEY_1024) { - if (crypto_pk_key_is_private(tok->key)) { - tor_snprintf(ebuf, sizeof(ebuf), - "Private key given for %s, which wants a public key", kwd); - RET_ERR(ebuf); - } - } else { /* o_syn == NEED_SKEY_1024 */ - if (!crypto_pk_key_is_private(tok->key)) { - tor_snprintf(ebuf, sizeof(ebuf), - "Public key given for %s, which wants a private key", kwd); - RET_ERR(ebuf); - } - } - break; - case OBJ_OK: - /* Anything goes with this token. */ - break; - } - - done_tokenizing: - return tok; -} - -/** Helper: parse space-separated arguments from the string <b>s</b> ending at - * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the - * number of parsed elements into the n_args field of <b>tok</b>. Allocate - * all storage in <b>area</b>. Return the number of arguments parsed, or - * return -1 if there was an insanely high number of arguments. */ -static inline int -get_token_arguments(memarea_t *area, directory_token_t *tok, - const char *s, const char *eol) -{ -/** Largest number of arguments we'll accept to any token, ever. */ -#define MAX_ARGS 512 - char *mem = memarea_strndup(area, s, eol-s); - char *cp = mem; - int j = 0; - char *args[MAX_ARGS]; - while (*cp) { - if (j == MAX_ARGS) - return -1; - args[j++] = cp; - cp = (char*)find_whitespace(cp); - if (!cp || !*cp) - break; /* End of the line. */ - *cp++ = '\0'; - cp = (char*)eat_whitespace(cp); - } - tok->n_args = j; - tok->args = memarea_memdup(area, args, j*sizeof(char*)); - return j; -#undef MAX_ARGS -} - -/** Helper function: read the next token from *s, advance *s to the end of the - * token, and return the parsed token. Parse *<b>s</b> according to the list - * of tokens in <b>table</b>. - */ -static directory_token_t * -get_next_token(memarea_t *area, - const char **s, const char *eos, token_rule_t *table) -{ - /** Reject any object at least this big; it is probably an overflow, an - * attack, a bug, or some other nonsense. */ -#define MAX_UNPARSED_OBJECT_SIZE (128*1024) - /** Reject any line at least this big; it is probably an overflow, an - * attack, a bug, or some other nonsense. */ -#define MAX_LINE_LENGTH (128*1024) - - const char *next, *eol, *obstart; - size_t obname_len; - int i; - directory_token_t *tok; - obj_syntax o_syn = NO_OBJ; - char ebuf[128]; - const char *kwd = ""; - - tor_assert(area); - tok = ALLOC_ZERO(sizeof(directory_token_t)); - tok->tp = ERR_; - - /* Set *s to first token, eol to end-of-line, next to after first token */ - *s = eat_whitespace_eos(*s, eos); /* eat multi-line whitespace */ - tor_assert(eos >= *s); - eol = memchr(*s, '\n', eos-*s); - if (!eol) - eol = eos; - if (eol - *s > MAX_LINE_LENGTH) { - RET_ERR("Line far too long"); - } - - next = find_whitespace_eos(*s, eol); - - if (!strcmp_len(*s, "opt", next-*s)) { - /* Skip past an "opt" at the start of the line. */ - *s = eat_whitespace_eos_no_nl(next, eol); - next = find_whitespace_eos(*s, eol); - } else if (*s == eos) { /* If no "opt", and end-of-line, line is invalid */ - RET_ERR("Unexpected EOF"); - } - - /* Search the table for the appropriate entry. (I tried a binary search - * instead, but it wasn't any faster.) */ - for (i = 0; table[i].t ; ++i) { - if (!strcmp_len(*s, table[i].t, next-*s)) { - /* We've found the keyword. */ - kwd = table[i].t; - tok->tp = table[i].v; - o_syn = table[i].os; - *s = eat_whitespace_eos_no_nl(next, eol); - /* We go ahead whether there are arguments or not, so that tok->args is - * always set if we want arguments. */ - if (table[i].concat_args) { - /* The keyword takes the line as a single argument */ - tok->args = ALLOC(sizeof(char*)); - tok->args[0] = STRNDUP(*s,eol-*s); /* Grab everything on line */ - tok->n_args = 1; - } else { - /* This keyword takes multiple arguments. */ - if (get_token_arguments(area, tok, *s, eol)<0) { - tor_snprintf(ebuf, sizeof(ebuf),"Far too many arguments to %s", kwd); - RET_ERR(ebuf); - } - *s = eol; - } - if (tok->n_args < table[i].min_args) { - tor_snprintf(ebuf, sizeof(ebuf), "Too few arguments to %s", kwd); - RET_ERR(ebuf); - } else if (tok->n_args > table[i].max_args) { - tor_snprintf(ebuf, sizeof(ebuf), "Too many arguments to %s", kwd); - RET_ERR(ebuf); - } - break; - } - } - - if (tok->tp == ERR_) { - /* No keyword matched; call it an "K_opt" or "A_unrecognized" */ - if (**s == '@') - tok->tp = A_UNKNOWN_; - else - tok->tp = K_OPT; - tok->args = ALLOC(sizeof(char*)); - tok->args[0] = STRNDUP(*s, eol-*s); - tok->n_args = 1; - o_syn = OBJ_OK; - } - - /* Check whether there's an object present */ - *s = eat_whitespace_eos(eol, eos); /* Scan from end of first line */ - tor_assert(eos >= *s); - eol = memchr(*s, '\n', eos-*s); - if (!eol || eol-*s<11 || strcmpstart(*s, "-----BEGIN ")) /* No object. */ - goto check_object; - - obstart = *s; /* Set obstart to start of object spec */ - if (*s+16 >= eol || memchr(*s+11,'\0',eol-*s-16) || /* no short lines, */ - strcmp_len(eol-5, "-----", 5) || /* nuls or invalid endings */ - (eol-*s) > MAX_UNPARSED_OBJECT_SIZE) { /* name too long */ - RET_ERR("Malformed object: bad begin line"); - } - tok->object_type = STRNDUP(*s+11, eol-*s-16); - obname_len = eol-*s-16; /* store objname length here to avoid a strlen() */ - *s = eol+1; /* Set *s to possible start of object data (could be eos) */ - - /* Go to the end of the object */ - next = tor_memstr(*s, eos-*s, "-----END "); - if (!next) { - RET_ERR("Malformed object: missing object end line"); - } - tor_assert(eos >= next); - eol = memchr(next, '\n', eos-next); - if (!eol) /* end-of-line marker, or eos if there's no '\n' */ - eol = eos; - /* Validate the ending tag, which should be 9 + NAME + 5 + eol */ - if ((size_t)(eol-next) != 9+obname_len+5 || - strcmp_len(next+9, tok->object_type, obname_len) || - strcmp_len(eol-5, "-----", 5)) { - tor_snprintf(ebuf, sizeof(ebuf), "Malformed object: mismatched end tag %s", - tok->object_type); - ebuf[sizeof(ebuf)-1] = '\0'; - RET_ERR(ebuf); - } - if (next - *s > MAX_UNPARSED_OBJECT_SIZE) - RET_ERR("Couldn't parse object: missing footer or object much too big."); - - if (!strcmp(tok->object_type, "RSA PUBLIC KEY")) { /* If it's a public key */ - tok->key = crypto_pk_new(); - if (crypto_pk_read_public_key_from_string(tok->key, obstart, eol-obstart)) - RET_ERR("Couldn't parse public key."); - } else if (!strcmp(tok->object_type, "RSA PRIVATE KEY")) { /* private key */ - tok->key = crypto_pk_new(); - if (crypto_pk_read_private_key_from_string(tok->key, obstart, eol-obstart)) - RET_ERR("Couldn't parse private key."); - } else { /* If it's something else, try to base64-decode it */ - int r; - tok->object_body = ALLOC(next-*s); /* really, this is too much RAM. */ - r = base64_decode(tok->object_body, next-*s, *s, next-*s); - if (r<0) - RET_ERR("Malformed object: bad base64-encoded data"); - tok->object_size = r; - } - *s = eol; - - check_object: - tok = token_check_object(area, kwd, tok, o_syn); - - done_tokenizing: - return tok; - -#undef RET_ERR -#undef ALLOC -#undef ALLOC_ZERO -#undef STRDUP -#undef STRNDUP -} - -/** Read all tokens from a string between <b>start</b> and <b>end</b>, and add - * them to <b>out</b>. Parse according to the token rules in <b>table</b>. - * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the - * entire string. - */ -static int -tokenize_string(memarea_t *area, - const char *start, const char *end, smartlist_t *out, - token_rule_t *table, int flags) -{ - const char **s; - directory_token_t *tok = NULL; - int counts[NIL_]; - int i; - int first_nonannotation; - int prev_len = smartlist_len(out); - tor_assert(area); - - s = &start; - if (!end) { - end = start+strlen(start); - } else { - /* it's only meaningful to check for nuls if we got an end-of-string ptr */ - if (memchr(start, '\0', end-start)) { - log_warn(LD_DIR, "parse error: internal NUL character."); - return -1; - } - } - for (i = 0; i < NIL_; ++i) - counts[i] = 0; - - SMARTLIST_FOREACH(out, const directory_token_t *, t, ++counts[t->tp]); - - while (*s < end && (!tok || tok->tp != EOF_)) { - tok = get_next_token(area, s, end, table); - if (tok->tp == ERR_) { - log_warn(LD_DIR, "parse error: %s", tok->error); - token_clear(tok); - return -1; - } - ++counts[tok->tp]; - smartlist_add(out, tok); - *s = eat_whitespace_eos(*s, end); - } - - if (flags & TS_NOCHECK) - return 0; - - if ((flags & TS_ANNOTATIONS_OK)) { - first_nonannotation = -1; - for (i = 0; i < smartlist_len(out); ++i) { - tok = smartlist_get(out, i); - if (tok->tp < MIN_ANNOTATION || tok->tp > MAX_ANNOTATION) { - first_nonannotation = i; - break; - } - } - if (first_nonannotation < 0) { - log_warn(LD_DIR, "parse error: item contains only annotations"); - return -1; - } - for (i=first_nonannotation; i < smartlist_len(out); ++i) { - tok = smartlist_get(out, i); - if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) { - log_warn(LD_DIR, "parse error: Annotations mixed with keywords"); - return -1; - } - } - if ((flags & TS_NO_NEW_ANNOTATIONS)) { - if (first_nonannotation != prev_len) { - log_warn(LD_DIR, "parse error: Unexpected annotations."); - return -1; - } - } - } else { - for (i=0; i < smartlist_len(out); ++i) { - tok = smartlist_get(out, i); - if (tok->tp >= MIN_ANNOTATION && tok->tp <= MAX_ANNOTATION) { - log_warn(LD_DIR, "parse error: no annotations allowed."); - return -1; - } - } - first_nonannotation = 0; - } - for (i = 0; table[i].t; ++i) { - if (counts[table[i].v] < table[i].min_cnt) { - log_warn(LD_DIR, "Parse error: missing %s element.", table[i].t); - return -1; - } - if (counts[table[i].v] > table[i].max_cnt) { - log_warn(LD_DIR, "Parse error: too many %s elements.", table[i].t); - return -1; - } - if (table[i].pos & AT_START) { - if (smartlist_len(out) < 1 || - (tok = smartlist_get(out, first_nonannotation))->tp != table[i].v) { - log_warn(LD_DIR, "Parse error: first item is not %s.", table[i].t); - return -1; - } - } - if (table[i].pos & AT_END) { - if (smartlist_len(out) < 1 || - (tok = smartlist_get(out, smartlist_len(out)-1))->tp != table[i].v) { - log_warn(LD_DIR, "Parse error: last item is not %s.", table[i].t); - return -1; - } - } - } - return 0; -} - -/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return - * NULL if no such keyword is found. - */ -static directory_token_t * -find_opt_by_keyword(smartlist_t *s, directory_keyword keyword) -{ - SMARTLIST_FOREACH(s, directory_token_t *, t, if (t->tp == keyword) return t); - return NULL; -} - -/** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail - * with an assert if no such keyword is found. - */ -static directory_token_t * -find_by_keyword_(smartlist_t *s, directory_keyword keyword, - const char *keyword_as_string) -{ - directory_token_t *tok = find_opt_by_keyword(s, keyword); - if (PREDICT_UNLIKELY(!tok)) { - log_err(LD_BUG, "Missing %s [%d] in directory object that should have " - "been validated. Internal error.", keyword_as_string, (int)keyword); - tor_assert(tok); - } - return tok; -} - -/** If there are any directory_token_t entries in <b>s</b> whose keyword is - * <b>k</b>, return a newly allocated smartlist_t containing all such entries, - * in the same order in which they occur in <b>s</b>. Otherwise return - * NULL. */ -static smartlist_t * -find_all_by_keyword(smartlist_t *s, directory_keyword k) -{ - smartlist_t *out = NULL; - SMARTLIST_FOREACH(s, directory_token_t *, t, - if (t->tp == k) { - if (!out) - out = smartlist_new(); - smartlist_add(out, t); - }); - return out; -} - /** Return a newly allocated smartlist of all accept or reject tokens in * <b>s</b>. */ @@ -5476,7 +4757,7 @@ microdescs_parse_from_string(const char *s, const char *eos, escaped(tok->args[i])); goto next; } - smartlist_add(md->family, tor_strdup(tok->args[i])); + smartlist_add_strdup(md->family, tok->args[i]); } } diff --git a/src/or/routerset.c b/src/or/routerset.c index 58b66ea777..4182dbc5c4 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -262,12 +262,12 @@ routerset_add_unknown_ccs(routerset_t **setp, int only_if_some_cc_set) geoip_get_country("A1") >= 0; if (add_unknown) { - smartlist_add(set->country_names, tor_strdup("??")); - smartlist_add(set->list, tor_strdup("{??}")); + smartlist_add_strdup(set->country_names, "??"); + smartlist_add_strdup(set->list, "{??}"); } if (add_a1) { - smartlist_add(set->country_names, tor_strdup("a1")); - smartlist_add(set->list, tor_strdup("{a1}")); + smartlist_add_strdup(set->country_names, "a1"); + smartlist_add_strdup(set->list, "{a1}"); } if (add_unknown || add_a1) { diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 49ac1b939a..033e6d119c 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -1,11 +1,6 @@ /* * Copyright (c) 2013-2016, The Tor Project, Inc. */ /* See LICENSE for licensing information */ -/** - * \file scheduler.c - * \brief Relay scheduling system - **/ - #include "or.h" #define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */ @@ -32,66 +27,102 @@ static uint32_t sched_q_high_water = 32768; static uint32_t sched_max_flush_cells = 16; -/* - * Write scheduling works by keeping track of which channels can +/** + * \file scheduler.c + * \brief Channel scheduling system: decides which channels should send and + * receive when. + * + * This module implements a scheduler algorithm, to decide + * which channels should send/receive when. + * + * The earliest versions of Tor approximated a kind of round-robin system + * among active connections, but only approximated it. + * + * Now, write scheduling works by keeping track of which channels can * accept cells, and have cells to write. From the scheduler's perspective, * a channel can be in four possible states: * - * 1.) Not open for writes, no cells to send - * - Not much to do here, and the channel will have scheduler_state == - * SCHED_CHAN_IDLE - * - Transitions from: - * - Open for writes/has cells by simultaneously draining all circuit + * <ol> + * <li> + * Not open for writes, no cells to send. + * <ul><li> Not much to do here, and the channel will have scheduler_state + * == SCHED_CHAN_IDLE + * <li> Transitions from: + * <ul> + * <li>Open for writes/has cells by simultaneously draining all circuit * queues and filling the output buffer. - * - Transitions to: - * - Not open for writes/has cells by arrival of cells on an attached + * </ul> + * <li> Transitions to: + * <ul> + * <li> Not open for writes/has cells by arrival of cells on an attached * circuit (this would be driven from append_cell_to_circuit_queue()) - * - Open for writes/no cells by a channel type specific path; + * <li> Open for writes/no cells by a channel type specific path; * driven from connection_or_flushed_some() for channel_tls_t. + * </ul> + * </ul> * - * 2.) Open for writes, no cells to send - * - Not much here either; this will be the state an idle but open channel - * can be expected to settle in. It will have scheduler_state == - * SCHED_CHAN_WAITING_FOR_CELLS - * - Transitions from: - * - Not open for writes/no cells by flushing some of the output + * <li> Open for writes, no cells to send + * <ul> + * <li>Not much here either; this will be the state an idle but open + * channel can be expected to settle in. It will have scheduler_state + * == SCHED_CHAN_WAITING_FOR_CELLS + * <li> Transitions from: + * <ul> + * <li>Not open for writes/no cells by flushing some of the output * buffer. - * - Open for writes/has cells by the scheduler moving cells from + * <li>Open for writes/has cells by the scheduler moving cells from * circuit queues to channel output queue, but not having enough * to fill the output queue. - * - Transitions to: - * - Open for writes/has cells by arrival of new cells on an attached + * </ul> + * <li> Transitions to: + * <ul> + * <li>Open for writes/has cells by arrival of new cells on an attached * circuit, in append_cell_to_circuit_queue() + * </ul> + * </ul> * - * 3.) Not open for writes, cells to send - * - This is the state of a busy circuit limited by output bandwidth; + * <li>Not open for writes, cells to send + * <ul> + * <li>This is the state of a busy circuit limited by output bandwidth; * cells have piled up in the circuit queues waiting to be relayed. * The channel will have scheduler_state == SCHED_CHAN_WAITING_TO_WRITE. - * - Transitions from: - * - Not open for writes/no cells by arrival of cells on an attached + * <li> Transitions from: + * <ul> + * <li>Not open for writes/no cells by arrival of cells on an attached * circuit - * - Open for writes/has cells by filling an output buffer without + * <li> Open for writes/has cells by filling an output buffer without * draining all cells from attached circuits - * - Transitions to: - * - Opens for writes/has cells by draining some of the output buffer + * </ul> + * <li> Transitions to: + * <ul> + * <li>Opens for writes/has cells by draining some of the output buffer * via the connection_or_flushed_some() path (for channel_tls_t). + * </ul> + * </ul> * - * 4.) Open for writes, cells to send - * - This connection is ready to relay some cells and waiting for + * <li>Open for writes, cells to send + * <ul> + * <li>This connection is ready to relay some cells and waiting for * the scheduler to choose it. The channel will have scheduler_state == * SCHED_CHAN_PENDING. - * - Transitions from: - * - Not open for writes/has cells by the connection_or_flushed_some() + * <li>Transitions from: + * <ul> + * <li> Not open for writes/has cells by the connection_or_flushed_some() * path - * - Open for writes/no cells by the append_cell_to_circuit_queue() + * <li> Open for writes/no cells by the append_cell_to_circuit_queue() * path - * - Transitions to: - * - Not open for writes/no cells by draining all circuit queues and - * simultaneously filling the output buffer. - * - Not open for writes/has cells by writing enough cells to fill the + * </ul> + * <li> Transitions to: + * <ul> + * <li>Not open for writes/no cells by draining all circuit queues and + * simultaneously filling the output buffer. + * <li>Not open for writes/has cells by writing enough cells to fill the * output buffer - * - Open for writes/no cells by draining all attached circuit queues + * <li>Open for writes/no cells by draining all attached circuit queues * without also filling the output buffer + * </ul> + * </ul> + * </ol> * * Other event-driven parts of the code move channels between these scheduling * states by calling scheduler functions; the scheduler only runs on open-for- diff --git a/src/or/torcert.c b/src/or/torcert.c index a6a33c675a..c58f3da2d3 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -6,8 +6,27 @@ * * \brief Implementation for ed25519-signed certificates as used in the Tor * protocol. + * + * This certificate format is designed to be simple and compact; it's + * documented in tor-spec.txt in the torspec.git repository. All of the + * certificates in this format are signed with an Ed25519 key; the + * contents themselves may be another Ed25519 key, a digest of a + * RSA key, or some other material. + * + * In this module there is also support for a crooss-certification of + * Ed25519 identities using (older) RSA1024 identities. + * + * Tor uses other types of certificate too, beyond those described in this + * module. Notably, our use of TLS requires us to touch X.509 certificates, + * even though sensible people would stay away from those. Our X.509 + * certificates are represented with tor_x509_cert_t, and implemented in + * tortls.c. We also have a separate certificate type that authorities + * use to authenticate their RSA signing keys with their RSA identity keys: + * that one is authority_cert_t, and it's mostly handled in routerlist.c. */ +#include "or.h" +#include "config.h" #include "crypto.h" #include "torcert.h" #include "ed25519_cert.h" @@ -137,7 +156,12 @@ tor_cert_parse(const uint8_t *encoded, const size_t len) cert->encoded_len = len; memcpy(cert->signed_key.pubkey, parsed->certified_key, 32); - cert->valid_until = parsed->exp_field * 3600; + int64_t valid_until_64 = ((int64_t)parsed->exp_field) * 3600; +#if SIZEOF_TIME_T < SIZEOF_INT64_T + if (valid_until_64 > TIME_MAX) + valid_until_64 = TIME_MAX - 1; +#endif + cert->valid_until = (time_t) valid_until_64; cert->cert_type = parsed->cert_type; for (unsigned i = 0; i < ed25519_cert_getlen_ext(parsed); ++i) { @@ -164,11 +188,17 @@ tor_cert_parse(const uint8_t *encoded, const size_t len) } /** Fill in <b>checkable_out</b> with the information needed to check - * the signature on <b>cert</b> with <b>pubkey</b>. */ + * the signature on <b>cert</b> with <b>pubkey</b>. + * + * On success, if <b>expiration_out</b> is provided, and it is some time + * _after_ the expiration time of this certificate, set it to the + * expiration time of this certificate. + */ int tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out, const tor_cert_t *cert, - const ed25519_public_key_t *pubkey) + const ed25519_public_key_t *pubkey, + time_t *expiration_out) { if (! pubkey) { if (cert->signing_key_included) @@ -185,6 +215,10 @@ tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out, memcpy(checkable_out->signature.sig, cert->encoded + signed_len, ED25519_SIG_LEN); + if (expiration_out) { + *expiration_out = MIN(*expiration_out, cert->valid_until); + } + return 0; } @@ -199,14 +233,15 @@ tor_cert_checksig(tor_cert_t *cert, { ed25519_checkable_t checkable; int okay; + time_t expires = TIME_MAX; - if (now && now > cert->valid_until) { - cert->cert_expired = 1; + if (tor_cert_get_checkable_sig(&checkable, cert, pubkey, &expires) < 0) return -1; - } - if (tor_cert_get_checkable_sig(&checkable, cert, pubkey) < 0) + if (now && now > expires) { + cert->cert_expired = 1; return -1; + } if (ed25519_checksig_batch(&okay, &checkable, 1) < 0) { cert->sig_bad = 1; @@ -255,6 +290,8 @@ tor_cert_opt_eq(const tor_cert_t *cert1, const tor_cert_t *cert2) return tor_cert_eq(cert1, cert2); } +#define RSA_ED_CROSSCERT_PREFIX "Tor TLS RSA/Ed25519 cross-certificate" + /** Create new cross-certification object to certify <b>ed_key</b> as the * master ed25519 identity key for the RSA identity key <b>rsa_key</b>. * Allocates and stores the encoded certificate in *<b>cert</b>, and returns @@ -279,11 +316,21 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, ssize_t sz = rsa_ed_crosscert_encode(res, alloc_sz, cc); tor_assert(sz > 0 && sz <= alloc_sz); + crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256); + crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX, + strlen(RSA_ED_CROSSCERT_PREFIX)); + const int signed_part_len = 32 + 4; + crypto_digest_add_bytes(d, (char*)res, signed_part_len); + + uint8_t digest[DIGEST256_LEN]; + crypto_digest_get_digest(d, (char*)digest, sizeof(digest)); + crypto_digest_free(d); + int siglen = crypto_pk_private_sign(rsa_key, (char*)rsa_ed_crosscert_getarray_sig(cc), rsa_ed_crosscert_getlen_sig(cc), - (char*)res, signed_part_len); + (char*)digest, sizeof(digest)); tor_assert(siglen > 0 && siglen <= (int)crypto_pk_keysize(rsa_key)); tor_assert(siglen <= UINT8_MAX); cc->sig_len = siglen; @@ -295,3 +342,350 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, return sz; } +/** + * Check whether the <b>crosscert_len</b> byte certificate in <b>crosscert</b> + * is in fact a correct cross-certification of <b>master_key</b> using + * the RSA key <b>rsa_id_key</b>. + * + * Also reject the certificate if it expired before + * <b>reject_if_expired_before</b>. + * + * Return 0 on success, negative on failure. + */ +int +rsa_ed25519_crosscert_check(const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before) +{ + rsa_ed_crosscert_t *cc = NULL; + int rv; + +#define ERR(code, s) \ + do { \ + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, \ + "Received a bad RSA->Ed25519 crosscert: %s", \ + (s)); \ + rv = (code); \ + goto err; \ + } while (0) + + if (BUG(crypto_pk_keysize(rsa_id_key) > PK_BYTES)) + return -1; + + if (BUG(!crosscert)) + return -1; + + ssize_t parsed_len = rsa_ed_crosscert_parse(&cc, crosscert, crosscert_len); + if (parsed_len < 0 || crosscert_len != (size_t)parsed_len) { + ERR(-2, "Unparseable or overlong crosscert"); + } + + if (tor_memneq(rsa_ed_crosscert_getarray_ed_key(cc), + master_key->pubkey, + ED25519_PUBKEY_LEN)) { + ERR(-3, "Crosscert did not match Ed25519 key"); + } + + const uint32_t expiration_date = rsa_ed_crosscert_get_expiration(cc); + const uint64_t expiration_time = expiration_date * 3600; + + if (reject_if_expired_before < 0 || + expiration_time < (uint64_t)reject_if_expired_before) { + ERR(-4, "Crosscert is expired"); + } + + const uint8_t *eos = rsa_ed_crosscert_get_end_of_signed(cc); + const uint8_t *sig = rsa_ed_crosscert_getarray_sig(cc); + const uint8_t siglen = rsa_ed_crosscert_get_sig_len(cc); + tor_assert(eos >= crosscert); + tor_assert((size_t)(eos - crosscert) <= crosscert_len); + tor_assert(siglen == rsa_ed_crosscert_getlen_sig(cc)); + + /* Compute the digest */ + uint8_t digest[DIGEST256_LEN]; + crypto_digest_t *d = crypto_digest256_new(DIGEST_SHA256); + crypto_digest_add_bytes(d, RSA_ED_CROSSCERT_PREFIX, + strlen(RSA_ED_CROSSCERT_PREFIX)); + crypto_digest_add_bytes(d, (char*)crosscert, eos-crosscert); + crypto_digest_get_digest(d, (char*)digest, sizeof(digest)); + crypto_digest_free(d); + + /* Now check the signature */ + uint8_t signed_[PK_BYTES]; + int signed_len = crypto_pk_public_checksig(rsa_id_key, + (char*)signed_, sizeof(signed_), + (char*)sig, siglen); + if (signed_len < DIGEST256_LEN) { + ERR(-5, "Bad signature, or length of signed data not as expected"); + } + + if (tor_memneq(digest, signed_, DIGEST256_LEN)) { + ERR(-6, "The signature was good, but it didn't match the data"); + } + + rv = 0; + err: + rsa_ed_crosscert_free(cc); + return rv; +} + +/** Construct and return a new empty or_handshake_certs object */ +or_handshake_certs_t * +or_handshake_certs_new(void) +{ + return tor_malloc_zero(sizeof(or_handshake_certs_t)); +} + +/** Release all storage held in <b>certs</b> */ +void +or_handshake_certs_free(or_handshake_certs_t *certs) +{ + if (!certs) + return; + + tor_x509_cert_free(certs->auth_cert); + tor_x509_cert_free(certs->link_cert); + tor_x509_cert_free(certs->id_cert); + + tor_cert_free(certs->ed_id_sign); + tor_cert_free(certs->ed_sign_link); + tor_cert_free(certs->ed_sign_auth); + tor_free(certs->ed_rsa_crosscert); + + memwipe(certs, 0xBD, sizeof(*certs)); + tor_free(certs); +} + +#undef ERR +#define ERR(s) \ + do { \ + log_fn(severity, LD_PROTOCOL, \ + "Received a bad CERTS cell: %s", \ + (s)); \ + return 0; \ + } while (0) + +int +or_handshake_certs_rsa_ok(int severity, + or_handshake_certs_t *certs, + tor_tls_t *tls, + time_t now) +{ + tor_x509_cert_t *link_cert = certs->link_cert; + tor_x509_cert_t *auth_cert = certs->auth_cert; + tor_x509_cert_t *id_cert = certs->id_cert; + + if (certs->started_here) { + if (! (id_cert && link_cert)) + ERR("The certs we wanted (ID, Link) were missing"); + if (! tor_tls_cert_matches_key(tls, link_cert)) + ERR("The link certificate didn't match the TLS public key"); + if (! tor_tls_cert_is_valid(severity, link_cert, id_cert, now, 0)) + ERR("The link certificate was not valid"); + if (! tor_tls_cert_is_valid(severity, id_cert, id_cert, now, 1)) + ERR("The ID certificate was not valid"); + } else { + if (! (id_cert && auth_cert)) + ERR("The certs we wanted (ID, Auth) were missing"); + if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, auth_cert, id_cert, now, 1)) + ERR("The authentication certificate was not valid"); + if (! tor_tls_cert_is_valid(LOG_PROTOCOL_WARN, id_cert, id_cert, now, 1)) + ERR("The ID certificate was not valid"); + } + + return 1; +} + +/** Check all the ed25519 certificates in <b>certs</b> against each other, and + * against the peer certificate in <b>tls</b> if appropriate. On success, + * return 0; on failure, return a negative value and warn at level + * <b>severity</b> */ +int +or_handshake_certs_ed25519_ok(int severity, + or_handshake_certs_t *certs, + tor_tls_t *tls, + time_t now) +{ + ed25519_checkable_t check[10]; + unsigned n_checkable = 0; + time_t expiration = TIME_MAX; + +#define ADDCERT(cert, pk) \ + do { \ + tor_assert(n_checkable < ARRAY_LENGTH(check)); \ + if (tor_cert_get_checkable_sig(&check[n_checkable++], cert, pk, \ + &expiration) < 0) \ + ERR("Could not get checkable cert."); \ + } while (0) + + if (! certs->ed_id_sign || !certs->ed_id_sign->signing_key_included) { + ERR("No Ed25519 signing key"); + } + ADDCERT(certs->ed_id_sign, NULL); + + if (certs->started_here) { + if (! certs->ed_sign_link) + ERR("No Ed25519 link key"); + { + /* check for a match with the TLS cert. */ + tor_x509_cert_t *peer_cert = tor_tls_get_peer_cert(tls); + if (BUG(!peer_cert)) { + /* This is a bug, because if we got to this point, we are a connection + * that was initiated here, and we completed a TLS handshake. The + * other side *must* have given us a certificate! */ + ERR("No x509 peer cert"); // LCOV_EXCL_LINE + } + const common_digests_t *peer_cert_digests = + tor_x509_cert_get_cert_digests(peer_cert); + int okay = tor_memeq(peer_cert_digests->d[DIGEST_SHA256], + certs->ed_sign_link->signed_key.pubkey, + DIGEST256_LEN); + tor_x509_cert_free(peer_cert); + if (!okay) + ERR("Link certificate does not match TLS certificate"); + } + + ADDCERT(certs->ed_sign_link, &certs->ed_id_sign->signed_key); + + } else { + if (! certs->ed_sign_auth) + ERR("No Ed25519 link authentication key"); + ADDCERT(certs->ed_sign_auth, &certs->ed_id_sign->signed_key); + } + + if (expiration < now) { + ERR("At least one certificate expired."); + } + + /* Okay, we've gotten ready to check all the Ed25519 certificates. + * Now, we are going to check the RSA certificate's cross-certification + * with the ED certificates. + * + * FFFF In the future, we might want to make this optional. + */ + + tor_x509_cert_t *rsa_id_cert = certs->id_cert; + if (!rsa_id_cert) { + ERR("Missing legacy RSA ID certificate"); + } + if (! tor_tls_cert_is_valid(severity, rsa_id_cert, rsa_id_cert, now, 1)) { + ERR("The legacy RSA ID certificate was not valid"); + } + if (! certs->ed_rsa_crosscert) { + ERR("Missing RSA->Ed25519 crosscert"); + } + crypto_pk_t *rsa_id_key = tor_tls_cert_get_key(rsa_id_cert); + if (!rsa_id_key) { + ERR("RSA ID cert had no RSA key"); + } + + if (rsa_ed25519_crosscert_check(certs->ed_rsa_crosscert, + certs->ed_rsa_crosscert_len, + rsa_id_key, + &certs->ed_id_sign->signing_key, + now) < 0) { + crypto_pk_free(rsa_id_key); + ERR("Invalid RSA->Ed25519 crosscert"); + } + crypto_pk_free(rsa_id_key); + rsa_id_key = NULL; + + /* FFFF We could save a little time in the client case by queueing + * this batch to check it later, along with the signature from the + * AUTHENTICATE cell. That will change our data flow a bit, though, + * so I say "postpone". */ + + if (ed25519_checksig_batch(NULL, check, n_checkable) < 0) { + ERR("At least one Ed25519 certificate was badly signed"); + } + + return 1; +} + +/** + * Check the Ed certificates and/or the RSA certificates, as appropriate. If + * we obtained an Ed25519 identity, set *ed_id_out. If we obtained an RSA + * identity, set *rs_id_out. Otherwise, set them both to NULL. + */ +void +or_handshake_certs_check_both(int severity, + or_handshake_certs_t *certs, + tor_tls_t *tls, + time_t now, + const ed25519_public_key_t **ed_id_out, + const common_digests_t **rsa_id_out) +{ + tor_assert(ed_id_out); + tor_assert(rsa_id_out); + + *ed_id_out = NULL; + *rsa_id_out = NULL; + + if (certs->ed_id_sign) { + if (or_handshake_certs_ed25519_ok(severity, certs, tls, now)) { + tor_assert(certs->ed_id_sign); + tor_assert(certs->id_cert); + + *ed_id_out = &certs->ed_id_sign->signing_key; + *rsa_id_out = tor_x509_cert_get_id_digests(certs->id_cert); + + /* If we reached this point, we did not look at any of the + * subsidiary RSA certificates, so we'd better just remove them. + */ + tor_x509_cert_free(certs->link_cert); + tor_x509_cert_free(certs->auth_cert); + certs->link_cert = certs->auth_cert = NULL; + } + /* We do _not_ fall through here. If you provided us Ed25519 + * certificates, we expect to verify them! */ + } else { + /* No ed25519 keys given in the CERTS cell */ + if (or_handshake_certs_rsa_ok(severity, certs, tls, now)) { + *rsa_id_out = tor_x509_cert_get_id_digests(certs->id_cert); + } + } +} + +/* === ENCODING === */ + +/* Encode the ed25519 certificate <b>cert</b> and put the newly allocated + * string in <b>cert_str_out</b>. Return 0 on success else a negative value. */ +int +tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out) +{ + int ret = -1; + char *ed_cert_b64 = NULL; + size_t ed_cert_b64_len; + + tor_assert(cert); + tor_assert(cert_str_out); + + /* Get the encoded size and add the NUL byte. */ + ed_cert_b64_len = base64_encode_size(cert->encoded_len, + BASE64_ENCODE_MULTILINE) + 1; + ed_cert_b64 = tor_malloc_zero(ed_cert_b64_len); + + /* Base64 encode the encoded certificate. */ + if (base64_encode(ed_cert_b64, ed_cert_b64_len, + (const char *) cert->encoded, cert->encoded_len, + BASE64_ENCODE_MULTILINE) < 0) { + log_err(LD_BUG, "Couldn't base64-encode ed22519 cert!"); + goto err; + } + + /* Put everything together in a NUL terminated string. */ + tor_asprintf(cert_str_out, + "-----BEGIN ED25519 CERT-----\n" + "%s" + "-----END ED25519 CERT-----", + ed_cert_b64); + /* Success! */ + ret = 0; + + err: + tor_free(ed_cert_b64); + return ret; +} + diff --git a/src/or/torcert.h b/src/or/torcert.h index 9c819c0abb..090f6b5811 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -6,12 +6,15 @@ #include "crypto_ed25519.h" -#define SIGNED_KEY_TYPE_ED25519 0x01 +#define SIGNED_KEY_TYPE_ED25519 0x01 -#define CERT_TYPE_ID_SIGNING 0x04 -#define CERT_TYPE_SIGNING_LINK 0x05 -#define CERT_TYPE_SIGNING_AUTH 0x06 -#define CERT_TYPE_ONION_ID 0x0A +#define CERT_TYPE_ID_SIGNING 0x04 +#define CERT_TYPE_SIGNING_LINK 0x05 +#define CERT_TYPE_SIGNING_AUTH 0x06 +#define CERT_TYPE_SIGNING_HS_DESC 0x08 +#define CERT_TYPE_AUTH_HS_IP_KEY 0x09 +#define CERT_TYPE_ONION_ID 0x0A +#define CERT_TYPE_CROSS_HS_IP_KEYS 0x0B #define CERT_FLAG_INCLUDE_SIGNING_KEY 0x1 @@ -57,8 +60,9 @@ tor_cert_t *tor_cert_parse(const uint8_t *cert, size_t certlen); void tor_cert_free(tor_cert_t *cert); int tor_cert_get_checkable_sig(ed25519_checkable_t *checkable_out, - const tor_cert_t *out, - const ed25519_public_key_t *pubkey); + const tor_cert_t *out, + const ed25519_public_key_t *pubkey, + time_t *expiration_out); int tor_cert_checksig(tor_cert_t *cert, const ed25519_public_key_t *pubkey, time_t now); @@ -71,6 +75,30 @@ ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, const crypto_pk_t *rsa_key, time_t expires, uint8_t **cert); +int rsa_ed25519_crosscert_check(const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before); + +or_handshake_certs_t *or_handshake_certs_new(void); +void or_handshake_certs_free(or_handshake_certs_t *certs); +int or_handshake_certs_rsa_ok(int severity, + or_handshake_certs_t *certs, + tor_tls_t *tls, + time_t now); +int or_handshake_certs_ed25519_ok(int severity, + or_handshake_certs_t *certs, + tor_tls_t *tls, + time_t now); +void or_handshake_certs_check_both(int severity, + or_handshake_certs_t *certs, + tor_tls_t *tls, + time_t now, + const ed25519_public_key_t **ed_id_out, + const common_digests_t **rsa_id_out); + +int tor_cert_encode_ed22519(const tor_cert_t *cert, char **cert_str_out); #endif diff --git a/src/or/transports.c b/src/or/transports.c index 7a52b737e4..f755882c16 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -430,7 +430,7 @@ add_transport_to_proxy(const char *transport, managed_proxy_t *mp) { tor_assert(mp->transports_to_launch); if (!smartlist_contains_string(mp->transports_to_launch, transport)) - smartlist_add(mp->transports_to_launch, tor_strdup(transport)); + smartlist_add_strdup(mp->transports_to_launch, transport); } /** Called when a SIGHUP occurs. Returns true if managed proxy @@ -1322,7 +1322,7 @@ create_managed_proxy_environment(const managed_proxy_t *mp) tor_free(state_tmp); } - smartlist_add(envs, tor_strdup("TOR_PT_MANAGED_TRANSPORT_VER=1")); + smartlist_add_strdup(envs, "TOR_PT_MANAGED_TRANSPORT_VER=1"); { char *transports_to_launch = |