diff options
Diffstat (limited to 'src/feature')
71 files changed, 2286 insertions, 404 deletions
diff --git a/src/feature/client/addressmap.c b/src/feature/client/addressmap.c index 1a6958d38c..cc97166f36 100644 --- a/src/feature/client/addressmap.c +++ b/src/feature/client/addressmap.c @@ -23,7 +23,6 @@ #include "app/config/config.h" #include "core/or/connection_edge.h" #include "feature/control/control_events.h" -#include "feature/relay/dns.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerset.h" @@ -689,7 +688,7 @@ client_dns_set_addressmap_impl(entry_connection_t *for_conn, if (ttl<0) ttl = DEFAULT_DNS_TTL; else - ttl = dns_clip_ttl(ttl); + ttl = clip_dns_ttl(ttl); if (exitname) { /* XXXX fails to ever get attempts to get an exit address of diff --git a/src/feature/client/bridges.c b/src/feature/client/bridges.c index 2b52a1173d..66b04f3bc2 100644 --- a/src/feature/client/bridges.c +++ b/src/feature/client/bridges.c @@ -844,8 +844,7 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) } } - if (options->ClientPreferIPv6ORPort == -1 || - options->ClientAutoIPv6ORPort == 0) { + if (options->ClientPreferIPv6ORPort == -1) { /* Mark which address to use based on which bridge_t we got. */ node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && !tor_addr_is_null(&node->ri->ipv6_addr)); diff --git a/src/feature/client/entrynodes.c b/src/feature/client/entrynodes.c index 8962f65006..2843558e93 100644 --- a/src/feature/client/entrynodes.c +++ b/src/feature/client/entrynodes.c @@ -1974,10 +1974,12 @@ get_retry_schedule(time_t failing_since, time_t now, const struct { time_t maximum; int primary_delay; int nonprimary_delay; } delays[] = { + // clang-format off { SIX_HOURS, 10*60, 1*60*60 }, { FOUR_DAYS, 90*60, 4*60*60 }, { SEVEN_DAYS, 4*60*60, 18*60*60 }, { TIME_MAX, 9*60*60, 36*60*60 } + // clang-format on }; unsigned i; diff --git a/src/feature/client/transports.c b/src/feature/client/transports.c index a8ea9781a4..55069bb60a 100644 --- a/src/feature/client/transports.c +++ b/src/feature/client/transports.c @@ -1420,8 +1420,10 @@ create_managed_proxy_environment(const managed_proxy_t *mp) smartlist_add_asprintf(envs, "TOR_PT_EXTENDED_SERVER_PORT=%s", ext_or_addrport_tmp); } - smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s", - cookie_file_loc); + if (cookie_file_loc) { + smartlist_add_asprintf(envs, "TOR_PT_AUTH_COOKIE_FILE=%s", + cookie_file_loc); + } tor_free(ext_or_addrport_tmp); tor_free(cookie_file_loc); diff --git a/src/feature/control/btrack.c b/src/feature/control/btrack.c index 874150ee13..3595af0fcc 100644 --- a/src/feature/control/btrack.c +++ b/src/feature/control/btrack.c @@ -57,7 +57,7 @@ btrack_add_pubsub(pubsub_connector_t *connector) const subsys_fns_t sys_btrack = { .name = "btrack", .supported = true, - .level = -30, + .level = 55, .initialize = btrack_init, .shutdown = btrack_fini, .add_pubsub = btrack_add_pubsub, diff --git a/src/feature/control/btrack_orconn_maps.c b/src/feature/control/btrack_orconn_maps.c index 0ef54237a8..a60dffb8c4 100644 --- a/src/feature/control/btrack_orconn_maps.c +++ b/src/feature/control/btrack_orconn_maps.c @@ -47,17 +47,18 @@ bto_chan_eq_(bt_orconn_t *a, bt_orconn_t *b) } HT_HEAD(bto_gid_ht, bt_orconn_t); -HT_PROTOTYPE(bto_gid_ht, bt_orconn_t, node, bto_gid_hash_, bto_gid_eq_) +HT_PROTOTYPE(bto_gid_ht, bt_orconn_t, node, bto_gid_hash_, bto_gid_eq_); HT_GENERATE2(bto_gid_ht, bt_orconn_t, node, bto_gid_hash_, bto_gid_eq_, 0.6, - tor_reallocarray_, tor_free_) + tor_reallocarray_, tor_free_); static struct bto_gid_ht *bto_gid_map; HT_HEAD(bto_chan_ht, bt_orconn_t); -HT_PROTOTYPE(bto_chan_ht, bt_orconn_t, chan_node, bto_chan_hash_, bto_chan_eq_) +HT_PROTOTYPE(bto_chan_ht, bt_orconn_t, chan_node, bto_chan_hash_, + bto_chan_eq_); HT_GENERATE2(bto_chan_ht, bt_orconn_t, chan_node, bto_chan_hash_, bto_chan_eq_, 0.6, - tor_reallocarray_, tor_free_) + tor_reallocarray_, tor_free_); static struct bto_chan_ht *bto_chan_map; /** Clear the GID hash map, freeing any bt_orconn_t objects that become diff --git a/src/feature/control/control_bootstrap.c b/src/feature/control/control_bootstrap.c index 2e78fad690..fee7612ba2 100644 --- a/src/feature/control/control_bootstrap.c +++ b/src/feature/control/control_bootstrap.c @@ -171,6 +171,12 @@ control_event_bootstrap_core(int loglevel, bootstrap_status_t status, control_event_client_status(LOG_NOTICE, "%s", buf); } +int +control_get_bootstrap_percent(void) +{ + return bootstrap_percent; +} + /** Called when Tor has made progress at bootstrapping its directory * information and initial circuits. * diff --git a/src/feature/control/control_cmd.c b/src/feature/control/control_cmd.c index c2d23243e5..cdefef97e1 100644 --- a/src/feature/control/control_cmd.c +++ b/src/feature/control/control_cmd.c @@ -2272,7 +2272,7 @@ typedef struct control_cmd_def_t { **/ #define ONE_LINE(name, flags) \ { \ - #name, \ + (#name), \ handle_control_ ##name, \ flags, \ &name##_syntax, \ @@ -2283,7 +2283,7 @@ typedef struct control_cmd_def_t { * flags. **/ #define MULTLINE(name, flags) \ - { "+"#name, \ + { ("+"#name), \ handle_control_ ##name, \ flags, \ &name##_syntax \ diff --git a/src/feature/control/control_events.h b/src/feature/control/control_events.h index 74bbc0047d..4a5492b510 100644 --- a/src/feature/control/control_events.h +++ b/src/feature/control/control_events.h @@ -12,6 +12,7 @@ #ifndef TOR_CONTROL_EVENTS_H #define TOR_CONTROL_EVENTS_H +#include "lib/cc/ctassert.h" #include "core/or/ocirc_event.h" #include "core/or/orconn_event.h" @@ -164,6 +165,7 @@ int control_event_buildtimeout_set(buildtimeout_set_event_t type, int control_event_signal(uintptr_t signal); void control_event_bootstrap(bootstrap_status_t status, int progress); +int control_get_bootstrap_percent(void); MOCK_DECL(void, control_event_bootstrap_prob_or,(const char *warn, int reason, or_connection_t *or_conn)); @@ -287,10 +289,7 @@ typedef uint64_t event_mask_t; /* If EVENT_MAX_ ever hits 0x0040, we need to make the mask into a * different structure, as it can only handle a maximum left shift of 1<<63. */ - -#if EVENT_MAX_ >= EVENT_CAPACITY_ -#error control_connection_t.event_mask has an event greater than its capacity -#endif +CTASSERT(EVENT_MAX_ < EVENT_CAPACITY_); #define EVENT_MASK_(e) (((uint64_t)1)<<(e)) diff --git a/src/feature/dirauth/dirauth_config.c b/src/feature/dirauth/dirauth_config.c index ca16dc8424..38d2a8bc5a 100644 --- a/src/feature/dirauth/dirauth_config.c +++ b/src/feature/dirauth/dirauth_config.c @@ -21,7 +21,7 @@ #include "core/or/or.h" #include "app/config/config.h" -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/voting_schedule.h" #include "feature/stats/rephist.h" #include "feature/dirauth/authmode.h" @@ -305,7 +305,7 @@ options_act_dirauth(const or_options_t *old_options) /* We may need to reschedule some dirauth stuff if our status changed. */ if (old_options) { if (options_transition_affects_dirauth_timing(old_options, options)) { - voting_schedule_recalculate_timing(options, time(NULL)); + dirauth_sched_recalculate_timing(options, time(NULL)); reschedule_dirvote(options); } } diff --git a/src/feature/dirauth/dircollate.c b/src/feature/dirauth/dircollate.c index b35cb021ff..2657f53853 100644 --- a/src/feature/dirauth/dircollate.c +++ b/src/feature/dirauth/dircollate.c @@ -90,9 +90,9 @@ ddmap_entry_set_digests(ddmap_entry_t *ent, } HT_PROTOTYPE(double_digest_map, ddmap_entry_t, node, ddmap_entry_hash, - ddmap_entry_eq) + ddmap_entry_eq); HT_GENERATE2(double_digest_map, ddmap_entry_t, node, ddmap_entry_hash, - ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_) + ddmap_entry_eq, 0.6, tor_reallocarray, tor_free_); /** Helper: add a single vote_routerstatus_t <b>vrs</b> to the collator * <b>dc</b>, indexing it by its RSA key digest, and by the 2-tuple of its RSA @@ -324,4 +324,3 @@ dircollator_get_votes_for_router(dircollator_t *dc, int idx) return digestmap_get(dc->by_collated_rsa_sha1, smartlist_get(dc->all_rsa_sha1_lst, idx)); } - diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c index e230815ca3..7eb2b720a6 100644 --- a/src/feature/dirauth/dirvote.c +++ b/src/feature/dirauth/dirvote.c @@ -36,7 +36,7 @@ #include "feature/stats/rephist.h" #include "feature/client/entrynodes.h" /* needed for guardfraction methods */ #include "feature/nodelist/torcert.h" -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/voting_schedule.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" @@ -886,7 +886,7 @@ dirvote_get_intermediate_param_value(const smartlist_t *param_list, int ok; value = (int32_t) tor_parse_long(integer_str, 10, INT32_MIN, INT32_MAX, &ok, NULL); - if (BUG(! ok)) + if (BUG(!ok)) return default_val; ++n_found; } @@ -2853,7 +2853,7 @@ dirvote_act(const or_options_t *options, time_t now) "Mine is %s.", keys, hex_str(c->cache_info.identity_digest, DIGEST_LEN)); tor_free(keys); - voting_schedule_recalculate_timing(options, now); + dirauth_sched_recalculate_timing(options, now); } #define IF_TIME_FOR_NEXT_ACTION(when_field, done_field) \ @@ -2899,7 +2899,7 @@ dirvote_act(const or_options_t *options, time_t now) networkstatus_get_latest_consensus_by_flavor(FLAV_NS)); /* XXXX We will want to try again later if we haven't got enough * signatures yet. Implement this if it turns out to ever happen. */ - voting_schedule_recalculate_timing(options, now); + dirauth_sched_recalculate_timing(options, now); return voting_schedule.voting_starts; } ENDIF @@ -2966,7 +2966,7 @@ dirvote_perform_vote(void) if (!contents) return -1; - pending_vote = dirvote_add_vote(contents, &msg, &status); + pending_vote = dirvote_add_vote(contents, 0, &msg, &status); tor_free(contents); if (!pending_vote) { log_warn(LD_DIR, "Couldn't store my own vote! (I told myself, '%s'.)", @@ -3122,13 +3122,45 @@ list_v3_auth_ids(void) return keys; } +/* Check the voter information <b>vi</b>, and assert that at least one + * signature is good. Asserts on failure. */ +static void +assert_any_sig_good(const networkstatus_voter_info_t *vi) +{ + int any_sig_good = 0; + SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig, + if (sig->good_signature) + any_sig_good = 1); + tor_assert(any_sig_good); +} + +/* Add <b>cert</b> to our list of known authority certificates. */ +static void +add_new_cert_if_needed(const struct authority_cert_t *cert) +{ + tor_assert(cert); + if (!authority_cert_get_by_digests(cert->cache_info.identity_digest, + cert->signing_key_digest)) { + /* Hey, it's a new cert! */ + trusted_dirs_load_certs_from_string( + cert->cache_info.signed_descriptor_body, + TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/, + NULL); + if (!authority_cert_get_by_digests(cert->cache_info.identity_digest, + cert->signing_key_digest)) { + log_warn(LD_BUG, "We added a cert, but still couldn't find it."); + } + } +} + /** Called when we have received a networkstatus vote in <b>vote_body</b>. * Parse and validate it, and on success store it as a pending vote (which we * then return). Return NULL on failure. Sets *<b>msg_out</b> and * *<b>status_out</b> to an HTTP response and status code. (V3 authority * only) */ pending_vote_t * -dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) +dirvote_add_vote(const char *vote_body, time_t time_posted, + const char **msg_out, int *status_out) { networkstatus_t *vote; networkstatus_voter_info_t *vi; @@ -3159,13 +3191,7 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) } tor_assert(smartlist_len(vote->voters) == 1); vi = get_voter(vote); - { - int any_sig_good = 0; - SMARTLIST_FOREACH(vi->sigs, document_signature_t *, sig, - if (sig->good_signature) - any_sig_good = 1); - tor_assert(any_sig_good); - } + assert_any_sig_good(vi); ds = trusteddirserver_get_by_v3_auth_digest(vi->identity_digest); if (!ds) { char *keys = list_v3_auth_ids(); @@ -3178,19 +3204,7 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) *msg_out = "Vote not from a recognized v3 authority"; goto err; } - tor_assert(vote->cert); - if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest, - vote->cert->signing_key_digest)) { - /* Hey, it's a new cert! */ - trusted_dirs_load_certs_from_string( - vote->cert->cache_info.signed_descriptor_body, - TRUSTED_DIRS_CERTS_SRC_FROM_VOTE, 1 /*flush*/, - NULL); - if (!authority_cert_get_by_digests(vote->cert->cache_info.identity_digest, - vote->cert->signing_key_digest)) { - log_warn(LD_BUG, "We added a cert, but still couldn't find it."); - } - } + add_new_cert_if_needed(vote->cert); /* Is it for the right period? */ if (vote->valid_after != voting_schedule.interval_starts) { @@ -3203,6 +3217,23 @@ dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) goto err; } + /* Check if we received it, as a post, after the cutoff when we + * start asking other dir auths for it. If we do, the best plan + * is to discard it, because using it greatly increases the chances + * of a split vote for this round (some dir auths got it in time, + * some didn't). */ + if (time_posted && time_posted > voting_schedule.fetch_missing_votes) { + char tbuf1[ISO_TIME_LEN+1], tbuf2[ISO_TIME_LEN+1]; + format_iso_time(tbuf1, time_posted); + format_iso_time(tbuf2, voting_schedule.fetch_missing_votes); + log_warn(LD_DIR, "Rejecting posted vote from %s received at %s; " + "our cutoff for received votes is %s. Check your clock, " + "CPU load, and network load. Also check the authority that " + "posted the vote.", vi->address, tbuf1, tbuf2); + *msg_out = "Posted vote received too late, would be dangerous to count it"; + goto err; + } + /* Fetch any new router descriptors we just learned about */ update_consensus_router_descriptor_downloads(time(NULL), 1, vote); @@ -4613,7 +4644,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, else last_consensus_interval = options->TestingV3AuthInitialVotingInterval; v3_out->valid_after = - voting_schedule_get_start_of_next_interval(now, + voting_sched_get_start_of_interval_after(now, (int)last_consensus_interval, options->TestingV3AuthVotingStartOffset); format_iso_time(tbuf, v3_out->valid_after); @@ -4635,17 +4666,14 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key, /* These are hardwired, to avoid disaster. */ v3_out->recommended_relay_protocols = - tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " - "Link=4 Microdesc=1-2 Relay=2"); + tor_strdup(DIRVOTE_RECCOMEND_RELAY_PROTO); v3_out->recommended_client_protocols = - tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " - "Link=4 Microdesc=1-2 Relay=2"); - v3_out->required_client_protocols = - tor_strdup("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " - "Link=4 Microdesc=1-2 Relay=2"); + tor_strdup(DIRVOTE_RECCOMEND_CLIENT_PROTO); + v3_out->required_relay_protocols = - tor_strdup("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " - "Link=3-4 Microdesc=1 Relay=1-2"); + tor_strdup(DIRVOTE_REQUIRE_RELAY_PROTO); + v3_out->required_client_protocols = + tor_strdup(DIRVOTE_REQUIRE_CLIENT_PROTO); /* We are not allowed to vote to require anything we don't have. */ tor_assert(protover_all_supported(v3_out->required_relay_protocols, NULL)); diff --git a/src/feature/dirauth/dirvote.h b/src/feature/dirauth/dirvote.h index 675f4ee148..fa7b1da4ab 100644 --- a/src/feature/dirauth/dirvote.h +++ b/src/feature/dirauth/dirvote.h @@ -94,6 +94,7 @@ void dirvote_dirreq_get_status_vote(const char *url, smartlist_t *items, /* Storing signatures and votes functions */ struct pending_vote_t * dirvote_add_vote(const char *vote_body, + time_t time_posted, const char **msg_out, int *status_out); int dirvote_add_signatures(const char *detached_signatures_body, @@ -142,9 +143,13 @@ dirvote_dirreq_get_status_vote(const char *url, smartlist_t *items, } static inline struct pending_vote_t * -dirvote_add_vote(const char *vote_body, const char **msg_out, int *status_out) +dirvote_add_vote(const char *vote_body, + time_t time_posted, + const char **msg_out, + int *status_out) { (void) vote_body; + (void) time_posted; /* If the dirauth module is disabled, this should NEVER be called else we * failed to safeguard the dirauth module. */ tor_assert_nonfatal_unreached(); @@ -230,6 +235,36 @@ char *networkstatus_get_detached_signatures(smartlist_t *consensuses); STATIC microdesc_t *dirvote_create_microdescriptor(const routerinfo_t *ri, int consensus_method); +/** The recommended relay protocols for this authority's votes. + * Recommending a new protocol causes old tor versions to log a warning. + */ +#define DIRVOTE_RECCOMEND_RELAY_PROTO \ + "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \ + "Link=4 Microdesc=1-2 Relay=2" +/** The recommended client protocols for this authority's votes. + * Recommending a new protocol causes old tor versions to log a warning. + */ +#define DIRVOTE_RECCOMEND_CLIENT_PROTO \ + "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \ + "Link=4 Microdesc=1-2 Relay=2" + +/** The required relay protocols for this authority's votes. + * WARNING: Requiring a new protocol causes old tor versions to shut down. + * Requiring the wrong protocols can break the tor network. + * See Proposal 303: When and how to remove support for protocol versions. + */ +#define DIRVOTE_REQUIRE_RELAY_PROTO \ + "Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \ + "Link=3-4 Microdesc=1 Relay=1-2" +/** The required relay protocols for this authority's votes. + * WARNING: Requiring a new protocol causes old tor versions to shut down. + * Requiring the wrong protocols can break the tor network. + * See Proposal 303: When and how to remove support for protocol versions. + */ +#define DIRVOTE_REQUIRE_CLIENT_PROTO \ + "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 " \ + "Link=4 Microdesc=1-2 Relay=2" + #endif /* defined(DIRVOTE_PRIVATE) */ #endif /* !defined(TOR_DIRVOTE_H) */ diff --git a/src/feature/dirauth/include.am b/src/feature/dirauth/include.am index 2ef629ae35..e26f120d4e 100644 --- a/src/feature/dirauth/include.am +++ b/src/feature/dirauth/include.am @@ -19,7 +19,8 @@ MODULE_DIRAUTH_SOURCES = \ src/feature/dirauth/recommend_pkg.c \ src/feature/dirauth/shared_random.c \ src/feature/dirauth/shared_random_state.c \ - src/feature/dirauth/voteflags.c + src/feature/dirauth/voteflags.c \ + src/feature/dirauth/voting_schedule.c # ADD_C_FILE: INSERT HEADERS HERE. noinst_HEADERS += \ @@ -43,7 +44,8 @@ noinst_HEADERS += \ src/feature/dirauth/shared_random.h \ src/feature/dirauth/shared_random_state.h \ src/feature/dirauth/vote_microdesc_hash_st.h \ - src/feature/dirauth/voteflags.h + src/feature/dirauth/voteflags.h \ + src/feature/dirauth/voting_schedule.h if BUILD_MODULE_DIRAUTH LIBTOR_APP_A_SOURCES += $(MODULE_DIRAUTH_SOURCES) diff --git a/src/feature/dirauth/keypin.c b/src/feature/dirauth/keypin.c index 6f6cfc01f1..98584a7d42 100644 --- a/src/feature/dirauth/keypin.c +++ b/src/feature/dirauth/keypin.c @@ -118,14 +118,14 @@ return (unsigned) siphash24g(a->ed25519_key, sizeof(a->ed25519_key)); } HT_PROTOTYPE(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa, - keypin_ents_eq_rsa) + keypin_ents_eq_rsa); HT_GENERATE2(rsamap, keypin_ent_st, rsamap_node, keypin_ent_hash_rsa, - keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_) + keypin_ents_eq_rsa, 0.6, tor_reallocarray, tor_free_); HT_PROTOTYPE(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed, - keypin_ents_eq_ed) + keypin_ents_eq_ed); HT_GENERATE2(edmap, keypin_ent_st, edmap_node, keypin_ent_hash_ed, - keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_) + keypin_ents_eq_ed, 0.6, tor_reallocarray, tor_free_); /** * Check whether we already have an entry in the key pinning table for a diff --git a/src/feature/dirauth/shared_random.c b/src/feature/dirauth/shared_random.c index 48e2147ea6..fd55008242 100644 --- a/src/feature/dirauth/shared_random.c +++ b/src/feature/dirauth/shared_random.c @@ -99,7 +99,7 @@ #include "feature/nodelist/dirlist.h" #include "feature/hs_common/shared_random_client.h" #include "feature/dirauth/shared_random_state.h" -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/voting_schedule.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/authmode.h" @@ -1261,7 +1261,7 @@ sr_act_post_consensus(const networkstatus_t *consensus) } /* Prepare our state so that it's ready for the next voting period. */ - sr_state_update(voting_schedule_get_next_valid_after_time()); + sr_state_update(dirauth_sched_get_next_valid_after_time()); } /** Initialize shared random subsystem. This MUST be called early in the boot diff --git a/src/feature/dirauth/shared_random_state.c b/src/feature/dirauth/shared_random_state.c index 1792d540c6..598d781557 100644 --- a/src/feature/dirauth/shared_random_state.c +++ b/src/feature/dirauth/shared_random_state.c @@ -20,7 +20,7 @@ #include "feature/dirauth/shared_random.h" #include "feature/hs_common/shared_random_client.h" #include "feature/dirauth/shared_random_state.h" -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/voting_schedule.h" #include "lib/encoding/confline.h" #include "lib/version/torversion.h" @@ -60,6 +60,7 @@ DUMMY_TYPECHECK_INSTANCE(sr_disk_state_t); #define SR_DISK_STATE_MAGIC 0x98AB1254 /** Array of variables that are saved to disk as a persistent state. */ +// clang-format off static const config_var_t state_vars[] = { V(Version, POSINT, "0"), V(TorVersion, STRING, NULL), @@ -73,6 +74,7 @@ static const config_var_t state_vars[] = { VAR("SharedRandCurrentValue", LINELIST_S, SharedRandValues, NULL), END_OF_CONFIG_VARS }; +// clang-format on /** "Extra" variable in the state that receives lines we can't parse. This * lets us preserve options from versions of Tor newer than us. */ @@ -139,7 +141,7 @@ get_state_valid_until_time(time_t now) voting_interval = get_voting_interval(); /* Find the time the current round started. */ - beginning_of_current_round = get_start_time_of_current_round(); + beginning_of_current_round = dirauth_sched_get_cur_valid_after_time(); /* Find how many rounds are left till the end of the protocol run */ current_round = (now / voting_interval) % total_rounds; @@ -1330,7 +1332,7 @@ sr_state_init(int save_to_disk, int read_from_disk) /* We have a state in memory, let's make sure it's updated for the current * and next voting round. */ { - time_t valid_after = voting_schedule_get_next_valid_after_time(); + time_t valid_after = dirauth_sched_get_next_valid_after_time(); sr_state_update(valid_after); } return 0; diff --git a/src/feature/dircommon/voting_schedule.c b/src/feature/dirauth/voting_schedule.c index 389f7f6b5d..efc4a0b316 100644 --- a/src/feature/dircommon/voting_schedule.c +++ b/src/feature/dirauth/voting_schedule.c @@ -3,12 +3,11 @@ /** * \file voting_schedule.c - * \brief This file contains functions that are from the directory authority - * subsystem related to voting specifically but used by many part of - * tor. The full feature is built as part of the dirauth module. + * \brief Compute information about our voting schedule as a directory + * authority. **/ -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/voting_schedule.h" #include "core/or/or.h" #include "app/config/config.h" @@ -20,55 +19,11 @@ * Vote scheduling * ===== */ -/** Return the start of the next interval of size <b>interval</b> (in - * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always - * starts a fresh interval, and if the last interval of a day would be - * truncated to less than half its size, it is rolled into the - * previous interval. */ -time_t -voting_schedule_get_start_of_next_interval(time_t now, int interval, - int offset) -{ - struct tm tm; - time_t midnight_today=0; - time_t midnight_tomorrow; - time_t next; - - tor_gmtime_r(&now, &tm); - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - - if (tor_timegm(&tm, &midnight_today) < 0) { - // LCOV_EXCL_START - log_warn(LD_BUG, "Ran into an invalid time when trying to find midnight."); - // LCOV_EXCL_STOP - } - midnight_tomorrow = midnight_today + (24*60*60); - - next = midnight_today + ((now-midnight_today)/interval + 1)*interval; - - /* Intervals never cross midnight. */ - if (next > midnight_tomorrow) - next = midnight_tomorrow; - - /* If the interval would only last half as long as it's supposed to, then - * skip over to the next day. */ - if (next + interval/2 > midnight_tomorrow) - next = midnight_tomorrow; - - next += offset; - if (next - interval > now) - next -= interval; - - return next; -} - /* Populate and return a new voting_schedule_t that can be used to schedule * voting. The object is allocated on the heap and it's the responsibility of * the caller to free it. Can't fail. */ static voting_schedule_t * -get_voting_schedule(const or_options_t *options, time_t now, int severity) +create_voting_schedule(const or_options_t *options, time_t now, int severity) { int interval, vote_delay, dist_delay; time_t start; @@ -95,14 +50,15 @@ get_voting_schedule(const or_options_t *options, time_t now, int severity) } tor_assert(interval > 0); + new_voting_schedule->interval = interval; if (vote_delay + dist_delay > interval/2) vote_delay = dist_delay = interval / 4; start = new_voting_schedule->interval_starts = - voting_schedule_get_start_of_next_interval(now,interval, + voting_sched_get_start_of_interval_after(now,interval, options->TestingV3AuthVotingStartOffset); - end = voting_schedule_get_start_of_next_interval(start+1, interval, + end = voting_sched_get_start_of_interval_after(start+1, interval, options->TestingV3AuthVotingStartOffset); tor_assert(end > start); @@ -139,9 +95,13 @@ voting_schedule_free_(voting_schedule_t *voting_schedule_to_free) voting_schedule_t voting_schedule; -/* Using the time <b>now</b>, return the next voting valid-after time. */ -time_t -voting_schedule_get_next_valid_after_time(void) +/** + * Return the current voting schedule, recreating it if necessary. + * + * Dirauth only. + **/ +static const voting_schedule_t * +dirauth_get_voting_schedule(void) { time_t now = approx_time(); bool need_to_recalculate_voting_schedule = false; @@ -167,27 +127,62 @@ voting_schedule_get_next_valid_after_time(void) done: if (need_to_recalculate_voting_schedule) { - voting_schedule_recalculate_timing(get_options(), approx_time()); + dirauth_sched_recalculate_timing(get_options(), approx_time()); voting_schedule.created_on_demand = 1; } - return voting_schedule.interval_starts; + return &voting_schedule; +} + +/** Return the next voting valid-after time. + * + * Dirauth only. */ +time_t +dirauth_sched_get_next_valid_after_time(void) +{ + return dirauth_get_voting_schedule()->interval_starts; +} + +/** + * Return our best idea of what the valid-after time for the _current_ + * consensus, whether we have one or not. + * + * Dirauth only. + **/ +time_t +dirauth_sched_get_cur_valid_after_time(void) +{ + const voting_schedule_t *sched = dirauth_get_voting_schedule(); + time_t next_start = sched->interval_starts; + int interval = sched->interval; + int offset = get_options()->TestingV3AuthVotingStartOffset; + return voting_sched_get_start_of_interval_after(next_start - interval - 1, + interval, + offset); +} + +/** Return the voting interval that we are configured to use. + * + * Dirauth only. */ +int +dirauth_sched_get_configured_interval(void) +{ + return get_options()->V3AuthVotingInterval; } /** Set voting_schedule to hold the timing for the next vote we should be * doing. All type of tor do that because HS subsystem needs the timing as * well to function properly. */ void -voting_schedule_recalculate_timing(const or_options_t *options, time_t now) +dirauth_sched_recalculate_timing(const or_options_t *options, time_t now) { voting_schedule_t *new_voting_schedule; /* get the new voting schedule */ - new_voting_schedule = get_voting_schedule(options, now, LOG_INFO); + new_voting_schedule = create_voting_schedule(options, now, LOG_INFO); tor_assert(new_voting_schedule); /* Fill in the global static struct now */ memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule)); voting_schedule_free(new_voting_schedule); } - diff --git a/src/feature/dircommon/voting_schedule.h b/src/feature/dirauth/voting_schedule.h index e4c6210087..9e2ac29c75 100644 --- a/src/feature/dircommon/voting_schedule.h +++ b/src/feature/dirauth/voting_schedule.h @@ -11,6 +11,8 @@ #include "core/or/or.h" +#ifdef HAVE_MODULE_DIRAUTH + /** Scheduling information for a voting interval. */ typedef struct { /** When do we generate and distribute our vote for this interval? */ @@ -26,6 +28,9 @@ typedef struct { /** When do we publish the consensus? */ time_t interval_starts; + /** Our computed dirauth interval */ + int interval; + /** True iff we have generated and distributed our vote. */ int have_voted; /** True iff we've requested missing votes. */ @@ -53,12 +58,36 @@ typedef struct { extern voting_schedule_t voting_schedule; -void voting_schedule_recalculate_timing(const or_options_t *options, +void dirauth_sched_recalculate_timing(const or_options_t *options, time_t now); -time_t voting_schedule_get_start_of_next_interval(time_t now, - int interval, - int offset); -time_t voting_schedule_get_next_valid_after_time(void); +time_t dirauth_sched_get_next_valid_after_time(void); +time_t dirauth_sched_get_cur_valid_after_time(void); +int dirauth_sched_get_configured_interval(void); + +#else /* !defined(HAVE_MODULE_DIRAUTH) */ + +#define dirauth_sched_recalculate_timing(opt,now) \ + ((void)(opt), (void)(now)) + +static inline time_t +dirauth_sched_get_next_valid_after_time(void) +{ + tor_assert_unreached(); + return 0; +} +static inline time_t +dirauth_sched_get_cur_valid_after_time(void) +{ + tor_assert_unreached(); + return 0; +} +static inline int +dirauth_sched_get_configured_interval(void) +{ + tor_assert_unreached(); + return 1; +} +#endif /* defined(HAVE_MODULE_DIRAUTH) */ #endif /* !defined(TOR_VOTING_SCHEDULE_H) */ diff --git a/src/feature/dircache/conscache.c b/src/feature/dircache/conscache.c index ceba410a5f..d9aaccddc1 100644 --- a/src/feature/dircache/conscache.c +++ b/src/feature/dircache/conscache.c @@ -132,6 +132,15 @@ consensus_cache_may_overallocate(consensus_cache_t *cache) #endif } +// HACK: GCC on Appveyor hates that we may assert before returning. Work around +// the error. +#ifdef _WIN32 +#ifndef COCCI +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" +#endif +#endif + /** * Tell the sandbox (if any) configured by <b>cfg</b> to allow the * operations that <b>cache</b> will need. @@ -156,6 +165,12 @@ consensus_cache_register_with_sandbox(consensus_cache_t *cache, return storage_dir_register_with_sandbox(cache->dir, cfg); } +#ifdef _WIN32 +#ifndef COCCI +#pragma GCC diagnostic pop +#endif +#endif + /** * Helper: clear all entries from <b>cache</b> (but do not delete * any that aren't marked for removal diff --git a/src/feature/dircache/consdiffmgr.c b/src/feature/dircache/consdiffmgr.c index 8445b8f986..10590cd6d2 100644 --- a/src/feature/dircache/consdiffmgr.c +++ b/src/feature/dircache/consdiffmgr.c @@ -218,9 +218,9 @@ cdm_diff_eq(const cdm_diff_t *diff1, const cdm_diff_t *diff2) diff1->compress_method == diff2->compress_method; } -HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq) +HT_PROTOTYPE(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq); HT_GENERATE2(cdm_diff_ht, cdm_diff_t, node, cdm_diff_hash, cdm_diff_eq, - 0.6, tor_reallocarray, tor_free_) + 0.6, tor_reallocarray, tor_free_); #define cdm_diff_free(diff) \ FREE_AND_NULL(cdm_diff_t, cdm_diff_free_, (diff)) diff --git a/src/feature/dircache/dircache.c b/src/feature/dircache/dircache.c index 3b8775968a..4f7f209207 100644 --- a/src/feature/dircache/dircache.c +++ b/src/feature/dircache/dircache.c @@ -1695,7 +1695,7 @@ directory_handle_command_post,(dir_connection_t *conn, const char *headers, !strcmp(url,"/tor/post/vote")) { /* v3 networkstatus vote */ const char *msg = "OK"; int status; - if (dirvote_add_vote(body, &msg, &status)) { + if (dirvote_add_vote(body, approx_time(), &msg, &status)) { write_short_http_response(conn, status, "Vote stored"); } else { tor_assert(msg); diff --git a/src/feature/dirclient/dirclient.c b/src/feature/dirclient/dirclient.c index 1b6eed12f0..e7bec89cad 100644 --- a/src/feature/dirclient/dirclient.c +++ b/src/feature/dirclient/dirclient.c @@ -21,6 +21,7 @@ #include "feature/client/entrynodes.h" #include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" +#include "feature/dirclient/dirclient.h" #include "feature/dirauth/dirvote.h" #include "feature/dirauth/shared_random.h" #include "feature/dircache/dirserv.h" @@ -51,6 +52,7 @@ #include "feature/rend/rendservice.h" #include "feature/stats/predict_ports.h" +#include "lib/cc/ctassert.h" #include "lib/compress/compress.h" #include "lib/crypt_ops/crypto_format.h" #include "lib/crypt_ops/crypto_util.h" @@ -1443,9 +1445,7 @@ compare_strs_(const void **a, const void **b) } #define CONDITIONAL_CONSENSUS_FPR_LEN 3 -#if (CONDITIONAL_CONSENSUS_FPR_LEN > DIGEST_LEN) -#error "conditional consensus fingerprint length is larger than digest length" -#endif +CTASSERT(CONDITIONAL_CONSENSUS_FPR_LEN <= DIGEST_LEN); /** Return the URL we should use for a consensus download. * @@ -1964,6 +1964,44 @@ dir_client_decompress_response_body(char **bodyp, size_t *bodylenp, return rv; } +/** + * Total number of bytes downloaded of each directory purpose, when + * bootstrapped, and when not bootstrapped. + * + * (For example, the number of bytes downloaded of purpose p while + * not fully bootstrapped is total_dl[p][false].) + **/ +static uint64_t total_dl[DIR_PURPOSE_MAX_][2]; + +/** + * Heartbeat: dump a summary of how many bytes of which purpose we've + * downloaded, when bootstrapping and when not bootstrapping. + **/ +void +dirclient_dump_total_dls(void) +{ + const or_options_t *options = get_options(); + for (int bootstrapped = 0; bootstrapped < 2; ++bootstrapped) { + bool first_time = true; + for (int i=0; i < DIR_PURPOSE_MAX_; ++i) { + uint64_t n = total_dl[i][0]; + if (n == 0) + continue; + if (options->SafeLogging_ != SAFELOG_SCRUB_NONE && + purpose_needs_anonymity(i, ROUTER_PURPOSE_GENERAL, NULL)) + continue; + if (first_time) { + log_notice(LD_NET, + "While %sbootstrapping, fetched this many bytes: ", + bootstrapped?"not ":""); + first_time = false; + } + log_notice(LD_NET, " %"PRIu64" (%s)", + n, dir_conn_purpose_to_string(i)); + } + } +} + /** We are a client, and we've finished reading the server's * response. Parse it and act appropriately. * @@ -1997,6 +2035,16 @@ connection_dir_client_reached_eof(dir_connection_t *conn) received_bytes = connection_get_inbuf_len(TO_CONN(conn)); + log_debug(LD_DIR, "Downloaded %"TOR_PRIuSZ" bytes on connection of purpose " + "%s; bootstrap %d%%", + received_bytes, + dir_conn_purpose_to_string(conn->base_.purpose), + control_get_bootstrap_percent()); + { + bool bootstrapped = control_get_bootstrap_percent() == 100; + total_dl[conn->base_.purpose][bootstrapped] += received_bytes; + } + switch (connection_fetch_from_buf_http(TO_CONN(conn), &headers, MAX_HEADERS_SIZE, &body, &body_len, MAX_DIR_DL_SIZE, @@ -2364,7 +2412,7 @@ handle_response_fetch_status_vote(dir_connection_t *conn, conn->base_.port, conn->requested_resource); return -1; } - dirvote_add_vote(body, &msg, &st); + dirvote_add_vote(body, 0, &msg, &st); if (st > 299) { log_warn(LD_DIR, "Error adding retrieved vote: %s", msg); } else { diff --git a/src/feature/dirclient/dirclient.h b/src/feature/dirclient/dirclient.h index 08209721bb..096b197526 100644 --- a/src/feature/dirclient/dirclient.h +++ b/src/feature/dirclient/dirclient.h @@ -14,6 +14,8 @@ #include "feature/hs/hs_ident.h" +void dirclient_dump_total_dls(void); + int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, diff --git a/src/feature/dircommon/fp_pair.c b/src/feature/dircommon/fp_pair.c index 8b55896ba8..87e1c253bd 100644 --- a/src/feature/dircommon/fp_pair.c +++ b/src/feature/dircommon/fp_pair.c @@ -57,10 +57,10 @@ fp_pair_map_entry_hash(const fp_pair_map_entry_t *a) */ HT_PROTOTYPE(fp_pair_map_impl, fp_pair_map_entry_t, node, - fp_pair_map_entry_hash, fp_pair_map_entries_eq) + fp_pair_map_entry_hash, fp_pair_map_entries_eq); HT_GENERATE2(fp_pair_map_impl, fp_pair_map_entry_t, node, fp_pair_map_entry_hash, fp_pair_map_entries_eq, - 0.6, tor_reallocarray_, tor_free_) + 0.6, tor_reallocarray_, tor_free_); /** Constructor to create a new empty map from fp_pair_t to void * */ @@ -312,4 +312,3 @@ fp_pair_map_assert_ok(const fp_pair_map_t *map) { tor_assert(!fp_pair_map_impl_HT_REP_IS_BAD_(&(map->head))); } - diff --git a/src/feature/dircommon/include.am b/src/feature/dircommon/include.am index f0f0323d12..87850ce183 100644 --- a/src/feature/dircommon/include.am +++ b/src/feature/dircommon/include.am @@ -3,8 +3,7 @@ LIBTOR_APP_A_SOURCES += \ src/feature/dircommon/consdiff.c \ src/feature/dircommon/directory.c \ - src/feature/dircommon/fp_pair.c \ - src/feature/dircommon/voting_schedule.c + src/feature/dircommon/fp_pair.c # ADD_C_FILE: INSERT HEADERS HERE. noinst_HEADERS += \ @@ -12,5 +11,4 @@ noinst_HEADERS += \ src/feature/dircommon/dir_connection_st.h \ src/feature/dircommon/directory.h \ src/feature/dircommon/fp_pair.h \ - src/feature/dircommon/vote_timing_st.h \ - src/feature/dircommon/voting_schedule.h + src/feature/dircommon/vote_timing_st.h diff --git a/src/feature/dirparse/authcert_members.h b/src/feature/dirparse/authcert_members.h index c6755bb629..53eab175d6 100644 --- a/src/feature/dirparse/authcert_members.h +++ b/src/feature/dirparse/authcert_members.h @@ -14,6 +14,7 @@ #ifndef TOR_AUTHCERT_MEMBERS_H #define TOR_AUTHCERT_MEMBERS_H +// clang-format off #define AUTHCERT_MEMBERS \ T1("dir-key-certificate-version", K_DIR_KEY_CERTIFICATE_VERSION, \ GE(1), NO_OBJ ), \ @@ -25,5 +26,6 @@ T1("dir-key-certification", K_DIR_KEY_CERTIFICATION,\ NO_ARGS, NEED_OBJ),\ T01("dir-address", K_DIR_ADDRESS, GE(1), NO_OBJ) +// clang-format on #endif /* !defined(TOR_AUTHCERT_MEMBERS_H) */ diff --git a/src/feature/dirparse/authcert_parse.c b/src/feature/dirparse/authcert_parse.c index 3d42119b94..deb45c12de 100644 --- a/src/feature/dirparse/authcert_parse.c +++ b/src/feature/dirparse/authcert_parse.c @@ -21,11 +21,13 @@ #include "feature/dirparse/authcert_members.h" /** List of tokens recognized in V3 authority certificates. */ +// clang-format off static token_rule_t dir_key_certificate_table[] = { AUTHCERT_MEMBERS, T1("fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), END_OF_TABLE }; +// clang-format on /** Parse a key certificate from <b>s</b>; point <b>end-of-string</b> to * the first character after the certificate. */ diff --git a/src/feature/dirparse/microdesc_parse.c b/src/feature/dirparse/microdesc_parse.c index c2eabeb404..9231080aaa 100644 --- a/src/feature/dirparse/microdesc_parse.c +++ b/src/feature/dirparse/microdesc_parse.c @@ -28,6 +28,7 @@ #include "feature/nodelist/microdesc_st.h" /** List of tokens recognized in microdescriptors */ +// clang-format off static token_rule_t microdesc_token_table[] = { T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024), T01("ntor-onion-key", K_ONION_KEY_NTOR, GE(1), NO_OBJ ), @@ -39,6 +40,7 @@ static token_rule_t microdesc_token_table[] = { A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), END_OF_TABLE }; +// clang-format on /** Assuming that s starts with a microdesc, return the start of the * *NEXT* one. Return NULL on "not found." */ diff --git a/src/feature/dirparse/ns_parse.c b/src/feature/dirparse/ns_parse.c index 4d9b6e6e73..ac9325a608 100644 --- a/src/feature/dirparse/ns_parse.c +++ b/src/feature/dirparse/ns_parse.c @@ -43,6 +43,7 @@ /** List of tokens recognized in the body part of v3 networkstatus * documents. */ +// clang-format off static token_rule_t rtrstatus_token_table[] = { T01("p", K_P, CONCAT_ARGS, NO_OBJ ), T1( "r", K_R, GE(7), NO_OBJ ), @@ -56,8 +57,10 @@ static token_rule_t rtrstatus_token_table[] = { T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), END_OF_TABLE }; +// clang-format on /** List of tokens recognized in V3 networkstatus votes. */ +// clang-format off static token_rule_t networkstatus_token_table[] = { T1_START("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), @@ -98,8 +101,10 @@ static token_rule_t networkstatus_token_table[] = { END_OF_TABLE }; +// clang-format on /** List of tokens recognized in V3 networkstatus consensuses. */ +// clang-format off static token_rule_t networkstatus_consensus_token_table[] = { T1_START("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), @@ -136,14 +141,17 @@ static token_rule_t networkstatus_consensus_token_table[] = { END_OF_TABLE }; +// clang-format on /** List of tokens recognized in the footer of v1 directory footers. */ +// clang-format off static token_rule_t networkstatus_vote_footer_token_table[] = { T01("directory-footer", K_DIRECTORY_FOOTER, NO_ARGS, NO_OBJ ), T01("bandwidth-weights", K_BW_WEIGHTS, ARGS, NO_OBJ ), T( "directory-signature", K_DIRECTORY_SIGNATURE, GE(2), NEED_OBJ ), END_OF_TABLE }; +// clang-format on /** Try to find the start and end of the signed portion of a networkstatus * document in <b>s</b>. On success, set <b>start_out</b> to the first diff --git a/src/feature/dirparse/routerparse.c b/src/feature/dirparse/routerparse.c index f476beec66..8828a0f97a 100644 --- a/src/feature/dirparse/routerparse.c +++ b/src/feature/dirparse/routerparse.c @@ -81,6 +81,7 @@ /****************************************************************************/ /** List of tokens recognized in router descriptors */ +// clang-format off const token_rule_t routerdesc_token_table[] = { T0N("reject", K_REJECT, ARGS, NO_OBJ ), T0N("accept", K_ACCEPT, ARGS, NO_OBJ ), @@ -123,8 +124,10 @@ const token_rule_t routerdesc_token_table[] = { END_OF_TABLE }; +// clang-format on /** List of tokens recognized in extra-info documents. */ +// clang-format off static token_rule_t extrainfo_token_table[] = { T1_END( "router-signature", K_ROUTER_SIGNATURE, NO_ARGS, NEED_OBJ ), T1( "published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), @@ -162,6 +165,7 @@ static token_rule_t extrainfo_token_table[] = { END_OF_TABLE }; +// clang-format on #undef T diff --git a/src/feature/hs/hs_cache.c b/src/feature/hs/hs_cache.c index 9cf408ca3e..44cd2505fd 100644 --- a/src/feature/hs/hs_cache.c +++ b/src/feature/hs/hs_cache.c @@ -27,6 +27,21 @@ static int cached_client_descriptor_has_expired(time_t now, const hs_cache_client_descriptor_t *cached_desc); +/** Helper function: Return true iff the cache entry has a decrypted + * descriptor. + * + * A NULL desc object in the entry means that we were not able to decrypt the + * descriptor because we are likely lacking client authorization. It is still + * a valid entry but some operations can't be done without the decrypted + * descriptor thus this function MUST be used to safe guard access to the + * decrypted desc object. */ +static inline bool +entry_has_decrypted_descriptor(const hs_cache_client_descriptor_t *entry) +{ + tor_assert(entry); + return (entry->desc != NULL); +} + /********************** Directory HS cache ******************/ /** Directory descriptor cache. Map indexed by blinded key. */ @@ -341,8 +356,23 @@ static digest256map_t *hs_cache_client_intro_state; static size_t cache_get_client_entry_size(const hs_cache_client_descriptor_t *entry) { - return sizeof(*entry) + - strlen(entry->encoded_desc) + hs_desc_obj_size(entry->desc); + size_t size = 0; + + if (entry == NULL) { + goto end; + } + size += sizeof(*entry); + + if (entry->encoded_desc) { + size += strlen(entry->encoded_desc); + } + + if (entry_has_decrypted_descriptor(entry)) { + size += hs_desc_obj_size(entry->desc); + } + + end: + return size; } /** Remove a given descriptor from our cache. */ @@ -659,14 +689,20 @@ cache_store_as_client(hs_cache_client_descriptor_t *client_desc) * client authorization. */ cache_entry = lookup_v3_desc_as_client(client_desc->key.pubkey); if (cache_entry != NULL) { - /* Signalling an undecrypted descriptor. We'll always replace the one we - * have with the new one just fetched. */ - if (cache_entry->desc == NULL) { + /* If the current or the new cache entry don't have a decrypted descriptor + * (missing client authorization), we always replace the current one with + * the new one. Reason is that we can't inspect the revision counter + * within the plaintext data so we blindly replace. */ + if (!entry_has_decrypted_descriptor(cache_entry) || + !entry_has_decrypted_descriptor(client_desc)) { remove_v3_desc_as_client(cache_entry); cache_client_desc_free(cache_entry); goto store; } + /* From this point on, we know that the decrypted descriptor is in the + * current entry and new object thus safe to access. */ + /* If we have an entry in our cache that has a revision counter greater * than the one we just fetched, discard the one we fetched. */ if (cache_entry->desc->plaintext_data.revision_counter > @@ -740,11 +776,15 @@ cache_clean_v3_as_client(time_t now) MAP_DEL_CURRENT(key); entry_size = cache_get_client_entry_size(entry); bytes_removed += entry_size; + /* We just removed an old descriptor. We need to close all intro circuits - * so we don't have leftovers that can be selected while lacking a - * descriptor. We leave the rendezvous circuits opened because they could - * be in use. */ - hs_client_close_intro_circuits_from_desc(entry->desc); + * if the descriptor is decrypted so we don't have leftovers that can be + * selected while lacking a descriptor. Circuits are selected by intro + * authentication key thus we need the descriptor. We leave the rendezvous + * circuits opened because they could be in use. */ + if (entry_has_decrypted_descriptor(entry)) { + hs_client_close_intro_circuits_from_desc(entry->desc); + } /* Entry is not in the cache anymore, destroy it. */ cache_client_desc_free(entry); /* Update our OOM. We didn't use the remove() function because we are in @@ -793,7 +833,7 @@ hs_cache_lookup_as_client(const ed25519_public_key_t *key) tor_assert(key); cached_desc = lookup_v3_desc_as_client(key->pubkey); - if (cached_desc && cached_desc->desc) { + if (cached_desc && entry_has_decrypted_descriptor(cached_desc)) { return cached_desc->desc; } @@ -866,7 +906,7 @@ hs_cache_remove_as_client(const ed25519_public_key_t *key) /* If we have a decrypted/decoded descriptor, attempt to close its * introduction circuit(s). We shouldn't have circuit(s) without a * descriptor else it will lead to a failure. */ - if (cached_desc->desc) { + if (entry_has_decrypted_descriptor(cached_desc)) { hs_client_close_intro_circuits_from_desc(cached_desc->desc); } /* Remove and free. */ @@ -995,7 +1035,7 @@ hs_cache_client_new_auth_parse(const ed25519_public_key_t *service_pk) } cached_desc = lookup_v3_desc_as_client(service_pk->pubkey); - if (cached_desc == NULL || cached_desc->desc != NULL) { + if (cached_desc == NULL || entry_has_decrypted_descriptor(cached_desc)) { /* No entry for that service or the descriptor is already decoded. */ goto end; } diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c index 52bd663200..fc9f4a2654 100644 --- a/src/feature/hs/hs_cell.c +++ b/src/feature/hs/hs_cell.c @@ -13,6 +13,7 @@ #include "feature/hs_common/replaycache.h" #include "feature/hs/hs_cell.h" +#include "feature/hs/hs_ob.h" #include "core/crypto/hs_ntor.h" #include "core/or/origin_circuit_st.h" @@ -67,14 +68,17 @@ compute_introduce_mac(const uint8_t *encoded_cell, size_t encoded_cell_len, memwipe(mac_msg, 0, sizeof(mac_msg)); } -/** From a set of keys, subcredential and the ENCRYPTED section of an - * INTRODUCE2 cell, return a newly allocated intro cell keys structure. - * Finally, the client public key is copied in client_pk. On error, return - * NULL. */ +/** + * From a set of keys, a list of subcredentials, and the ENCRYPTED section of + * an INTRODUCE2 cell, return an array of newly allocated intro cell keys + * structures. Finally, the client public key is copied in client_pk. On + * error, return NULL. + **/ static hs_ntor_intro_cell_keys_t * get_introduce2_key_material(const ed25519_public_key_t *auth_key, const curve25519_keypair_t *enc_key, - const uint8_t *subcredential, + size_t n_subcredentials, + const hs_subcredential_t *subcredentials, const uint8_t *encrypted_section, curve25519_public_key_t *client_pk) { @@ -82,17 +86,19 @@ get_introduce2_key_material(const ed25519_public_key_t *auth_key, tor_assert(auth_key); tor_assert(enc_key); - tor_assert(subcredential); + tor_assert(n_subcredentials > 0); + tor_assert(subcredentials); tor_assert(encrypted_section); tor_assert(client_pk); - keys = tor_malloc_zero(sizeof(*keys)); + keys = tor_calloc(n_subcredentials, sizeof(hs_ntor_intro_cell_keys_t)); /* First bytes of the ENCRYPTED section are the client public key. */ memcpy(client_pk->public_key, encrypted_section, CURVE25519_PUBKEY_LEN); - if (hs_ntor_service_get_introduce1_keys(auth_key, enc_key, client_pk, - subcredential, keys) < 0) { + if (hs_ntor_service_get_introduce1_keys_multi(auth_key, enc_key, client_pk, + n_subcredentials, + subcredentials, keys) < 0) { /* Don't rely on the caller to wipe this on error. */ memwipe(client_pk, 0, sizeof(curve25519_public_key_t)); tor_free(keys); @@ -747,6 +753,74 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) return ret; } +/** For the encrypted INTRO2 cell in <b>encrypted_section</b>, use the crypto + * material in <b>data</b> to compute the right ntor keys. Also validate the + * INTRO2 MAC to ensure that the keys are the right ones. + * + * Return NULL on failure to either produce the key material or on MAC + * validation. Else return a newly allocated intro keys object. */ +static hs_ntor_intro_cell_keys_t * +get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data, + const uint8_t *encrypted_section, + size_t encrypted_section_len) +{ + hs_ntor_intro_cell_keys_t *intro_keys = NULL; + hs_ntor_intro_cell_keys_t *intro_keys_result = NULL; + + /* Build the key material out of the key material found in the cell. */ + intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, + data->n_subcredentials, + data->subcredentials, + encrypted_section, + &data->client_pk); + if (intro_keys == NULL) { + log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to " + "compute key material"); + return NULL; + } + + /* Make sure we are not about to underflow. */ + if (BUG(encrypted_section_len < DIGEST256_LEN)) { + return NULL; + } + + /* Validate MAC from the cell and our computed key material. The MAC field + * in the cell is at the end of the encrypted section. */ + intro_keys_result = tor_malloc_zero(sizeof(*intro_keys_result)); + for (unsigned i = 0; i < data->n_subcredentials; ++i) { + uint8_t mac[DIGEST256_LEN]; + + /* The MAC field is at the very end of the ENCRYPTED section. */ + size_t mac_offset = encrypted_section_len - sizeof(mac); + /* Compute the MAC. Use the entire encoded payload with a length up to the + * ENCRYPTED section. */ + compute_introduce_mac(data->payload, + data->payload_len - encrypted_section_len, + encrypted_section, encrypted_section_len, + intro_keys[i].mac_key, + sizeof(intro_keys[i].mac_key), + mac, sizeof(mac)); + /* Time-invariant conditional copy: if the MAC is what we expected, then + * set intro_keys_result to intro_keys[i]. Otherwise, don't: but don't + * leak which one it was! */ + bool equal = tor_memeq(mac, encrypted_section + mac_offset, sizeof(mac)); + memcpy_if_true_timei(equal, intro_keys_result, &intro_keys[i], + sizeof(*intro_keys_result)); + } + + /* We no longer need intro_keys. */ + memwipe(intro_keys, 0, + sizeof(hs_ntor_intro_cell_keys_t) * data->n_subcredentials); + tor_free(intro_keys); + + if (safe_mem_is_zero(intro_keys_result, sizeof(*intro_keys_result))) { + log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell"); + tor_free(intro_keys_result); /* sets intro_keys_result to NULL */ + } + + return intro_keys_result; +} + /** Parse the INTRODUCE2 cell using data which contains everything we need to * do so and contains the destination buffers of information we extract and * compute from the cell. Return 0 on success else a negative value. The @@ -795,47 +869,29 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, /* Check our replay cache for this introduction point. */ if (replaycache_add_test_and_elapsed(data->replay_cache, encrypted_section, encrypted_section_len, &elapsed)) { - log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the" + log_warn(LD_REND, "Possible replay detected! An INTRODUCE2 cell with the " "same ENCRYPTED section was seen %ld seconds ago. " "Dropping cell.", (long int) elapsed); goto done; } - /* Build the key material out of the key material found in the cell. */ - intro_keys = get_introduce2_key_material(data->auth_pk, data->enc_kp, - data->subcredential, - encrypted_section, - &data->client_pk); - if (intro_keys == NULL) { - log_info(LD_REND, "Invalid INTRODUCE2 encrypted data. Unable to " - "compute key material on circuit %u for service %s", - TO_CIRCUIT(circ)->n_circ_id, + /* First bytes of the ENCRYPTED section are the client public key (they are + * guaranteed to exist because of the length check above). We are gonna use + * the client public key to compute the ntor keys and decrypt the payload: + */ + memcpy(&data->client_pk.public_key, encrypted_section, + CURVE25519_PUBKEY_LEN); + + /* Get the right INTRODUCE2 ntor keys and verify the cell MAC */ + intro_keys = get_introduce2_keys_and_verify_mac(data, encrypted_section, + encrypted_section_len); + if (!intro_keys) { + log_warn(LD_REND, "Could not get valid INTRO2 keys on circuit %u " + "for service %s", TO_CIRCUIT(circ)->n_circ_id, safe_str_client(service->onion_address)); goto done; } - /* Validate MAC from the cell and our computed key material. The MAC field - * in the cell is at the end of the encrypted section. */ - { - uint8_t mac[DIGEST256_LEN]; - /* The MAC field is at the very end of the ENCRYPTED section. */ - size_t mac_offset = encrypted_section_len - sizeof(mac); - /* Compute the MAC. Use the entire encoded payload with a length up to the - * ENCRYPTED section. */ - compute_introduce_mac(data->payload, - data->payload_len - encrypted_section_len, - encrypted_section, encrypted_section_len, - intro_keys->mac_key, sizeof(intro_keys->mac_key), - mac, sizeof(mac)); - if (tor_memcmp(mac, encrypted_section + mac_offset, sizeof(mac))) { - log_info(LD_REND, "Invalid MAC validation for INTRODUCE2 cell on " - "circuit %u for service %s", - TO_CIRCUIT(circ)->n_circ_id, - safe_str_client(service->onion_address)); - goto done; - } - } - { /* The ENCRYPTED_DATA section starts just after the CLIENT_PK. */ const uint8_t *encrypted_data = diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h index 80f37057d2..2b28c44c50 100644 --- a/src/feature/hs/hs_cell.h +++ b/src/feature/hs/hs_cell.h @@ -16,6 +16,8 @@ * 3.2.2 of the specification). Below this value, the cell must be padded. */ #define HS_CELL_INTRODUCE1_MIN_SIZE 246 +struct hs_subcredential_t; + /** This data structure contains data that we need to build an INTRODUCE1 cell * used by the INTRODUCE1 build function. */ typedef struct hs_cell_introduce1_data_t { @@ -29,7 +31,7 @@ typedef struct hs_cell_introduce1_data_t { /** Introduction point encryption public key. */ const curve25519_public_key_t *enc_pk; /** Subcredentials of the service. */ - const uint8_t *subcredential; + const struct hs_subcredential_t *subcredential; /** Onion public key for the ntor handshake. */ const curve25519_public_key_t *onion_pk; /** Rendezvous cookie. */ @@ -55,9 +57,14 @@ typedef struct hs_cell_introduce2_data_t { owned by the introduction point object through which we received the INTRO2 cell*/ const curve25519_keypair_t *enc_kp; - /** Subcredentials of the service. Pointer owned by the descriptor that owns - the introduction point through which we received the INTRO2 cell. */ - const uint8_t *subcredential; + /** + * Length of the subcredentials array below. + **/ + size_t n_subcredentials; + /** Array of <b>n_subcredentials</b> subcredentials for the service. Pointer + * owned by the descriptor that owns the introduction point through which we + * received the INTRO2 cell. */ + const struct hs_subcredential_t *subcredentials; /** Payload of the received encoded cell. */ const uint8_t *payload; /** Size of the payload of the received encoded cell. */ diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index 90805a98b7..fdd226ba79 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -19,6 +19,7 @@ #include "feature/client/circpathbias.h" #include "feature/hs/hs_cell.h" #include "feature/hs/hs_circuit.h" +#include "feature/hs/hs_ob.h" #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_client.h" #include "feature/hs/hs_ident.h" @@ -367,10 +368,10 @@ get_service_anonymity_string(const hs_service_t *service) * success, a circuit identifier is attached to the circuit with the needed * data. This function will try to open a circuit for a maximum value of * MAX_REND_FAILURES then it will give up. */ -static void -launch_rendezvous_point_circuit(const hs_service_t *service, - const hs_service_intro_point_t *ip, - const hs_cell_introduce2_data_t *data) +MOCK_IMPL(STATIC void, +launch_rendezvous_point_circuit,(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const hs_cell_introduce2_data_t *data)) { int circ_needs_uptime; time_t now = time(NULL); @@ -578,7 +579,7 @@ retry_service_rendezvous_point(const origin_circuit_t *circ) static int setup_introduce1_data(const hs_desc_intro_point_t *ip, const node_t *rp_node, - const uint8_t *subcredential, + const hs_subcredential_t *subcredential, hs_cell_introduce1_data_t *intro1_data) { int ret = -1; @@ -958,6 +959,42 @@ hs_circ_handle_intro_established(const hs_service_t *service, return ret; } +/** + * Go into <b>data</b> and add the right subcredential to be able to handle + * this incoming cell. + * + * <b>desc_subcred</b> is the subcredential of the descriptor that corresponds + * to the intro point that received this intro request. This subcredential + * should be used if we are not an onionbalance instance. + * + * Return 0 if everything went well, or -1 in case of internal error. + */ +static int +get_subcredential_for_handling_intro2_cell(const hs_service_t *service, + hs_cell_introduce2_data_t *data, + const hs_subcredential_t *desc_subcred) +{ + /* Handle the simple case first: We are not an onionbalance instance and we + * should just use the regular descriptor subcredential */ + if (!hs_ob_service_is_instance(service)) { + data->n_subcredentials = 1; + data->subcredentials = desc_subcred; + return 0; + } + + /* This should not happen since we should have made onionbalance + * subcredentials when we created our descriptors. */ + if (BUG(!service->ob_subcreds)) { + return -1; + } + + /* We are an onionbalance instance: */ + data->n_subcredentials = service->n_ob_subcreds; + data->subcredentials = service->ob_subcreds; + + return 0; +} + /** We just received an INTRODUCE2 cell on the established introduction circuit * circ. Handle the INTRODUCE2 payload of size payload_len for the given * circuit and service. This cell is associated with the intro point object ip @@ -966,7 +1003,7 @@ int hs_circ_handle_introduce2(const hs_service_t *service, const origin_circuit_t *circ, hs_service_intro_point_t *ip, - const uint8_t *subcredential, + const hs_subcredential_t *subcredential, const uint8_t *payload, size_t payload_len) { int ret = -1; @@ -983,12 +1020,16 @@ hs_circ_handle_introduce2(const hs_service_t *service, * parsed, decrypted and key material computed correctly. */ data.auth_pk = &ip->auth_key_kp.pubkey; data.enc_kp = &ip->enc_key_kp; - data.subcredential = subcredential; data.payload = payload; data.payload_len = payload_len; data.link_specifiers = smartlist_new(); data.replay_cache = ip->replay_cache; + if (get_subcredential_for_handling_intro2_cell(service, + &data, subcredential)) { + goto done; + } + if (hs_cell_parse_introduce2(&data, circ, service) < 0) { goto done; } @@ -1092,7 +1133,7 @@ int hs_circ_send_introduce1(origin_circuit_t *intro_circ, origin_circuit_t *rend_circ, const hs_desc_intro_point_t *ip, - const uint8_t *subcredential) + const hs_subcredential_t *subcredential) { int ret = -1; ssize_t payload_len; diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h index 92231369c6..22e936e685 100644 --- a/src/feature/hs/hs_circuit.h +++ b/src/feature/hs/hs_circuit.h @@ -46,15 +46,16 @@ int hs_circ_handle_intro_established(const hs_service_t *service, origin_circuit_t *circ, const uint8_t *payload, size_t payload_len); +struct hs_subcredential_t; int hs_circ_handle_introduce2(const hs_service_t *service, const origin_circuit_t *circ, hs_service_intro_point_t *ip, - const uint8_t *subcredential, + const struct hs_subcredential_t *subcredential, const uint8_t *payload, size_t payload_len); int hs_circ_send_introduce1(origin_circuit_t *intro_circ, origin_circuit_t *rend_circ, const hs_desc_intro_point_t *ip, - const uint8_t *subcredential); + const struct hs_subcredential_t *subcredential); int hs_circ_send_establish_rendezvous(origin_circuit_t *circ); /* e2e circuit API. */ @@ -78,6 +79,12 @@ create_rp_circuit_identifier(const hs_service_t *service, const curve25519_public_key_t *server_pk, const struct hs_ntor_rend_cell_keys_t *keys); +struct hs_cell_introduce2_data_t; +MOCK_DECL(STATIC void, +launch_rendezvous_point_circuit,(const hs_service_t *service, + const hs_service_intro_point_t *ip, + const struct hs_cell_introduce2_data_t *data)); + #endif /* defined(HS_CIRCUIT_PRIVATE) */ #endif /* !defined(TOR_HS_CIRCUIT_H) */ diff --git a/src/feature/hs/hs_circuitmap.c b/src/feature/hs/hs_circuitmap.c index 2343d729dd..466a02de39 100644 --- a/src/feature/hs/hs_circuitmap.c +++ b/src/feature/hs/hs_circuitmap.c @@ -76,11 +76,11 @@ hs_circuit_hash_token(const circuit_t *circuit) HT_PROTOTYPE(hs_circuitmap_ht, // The name of the hashtable struct circuit_t, // The name of the element struct, hs_circuitmap_node, // The name of HT_ENTRY member - hs_circuit_hash_token, hs_circuits_have_same_token) + hs_circuit_hash_token, hs_circuits_have_same_token); HT_GENERATE2(hs_circuitmap_ht, circuit_t, hs_circuitmap_node, hs_circuit_hash_token, hs_circuits_have_same_token, - 0.6, tor_reallocarray, tor_free_) + 0.6, tor_reallocarray, tor_free_); #ifdef TOR_UNIT_TESTS diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index af8cb0b410..cc1b01d2ef 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -646,7 +646,7 @@ send_introduce1(origin_circuit_t *intro_circ, /* Send the INTRODUCE1 cell. */ if (hs_circ_send_introduce1(intro_circ, rend_circ, ip, - desc->subcredential) < 0) { + &desc->subcredential) < 0) { if (TO_CIRCUIT(intro_circ)->marked_for_close) { /* If the introduction circuit was closed, we were unable to send the * cell for some reasons. In any case, the intro circuit has to be @@ -1845,7 +1845,7 @@ hs_client_decode_descriptor(const char *desc_str, hs_descriptor_t **desc) { hs_desc_decode_status_t ret; - uint8_t subcredential[DIGEST256_LEN]; + hs_subcredential_t subcredential; ed25519_public_key_t blinded_pubkey; hs_client_service_authorization_t *client_auth = NULL; curve25519_secret_key_t *client_auth_sk = NULL; @@ -1865,13 +1865,13 @@ hs_client_decode_descriptor(const char *desc_str, uint64_t current_time_period = hs_get_time_period_num(0); hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); - hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); + hs_get_subcredential(service_identity_pk, &blinded_pubkey, &subcredential); } /* Parse descriptor */ - ret = hs_desc_decode_descriptor(desc_str, subcredential, + ret = hs_desc_decode_descriptor(desc_str, &subcredential, client_auth_sk, desc); - memwipe(subcredential, 0, sizeof(subcredential)); + memwipe(&subcredential, 0, sizeof(subcredential)); if (ret != HS_DESC_DECODE_OK) { goto err; } @@ -2486,4 +2486,3 @@ set_hs_client_auths_map(digest256map_t *map) } #endif /* defined(TOR_UNIT_TESTS) */ - diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c index f8b031cc26..4639cdb68a 100644 --- a/src/feature/hs/hs_common.c +++ b/src/feature/hs/hs_common.c @@ -22,6 +22,7 @@ #include "feature/hs/hs_client.h" #include "feature/hs/hs_common.h" #include "feature/hs/hs_dos.h" +#include "feature/hs/hs_ob.h" #include "feature/hs/hs_ident.h" #include "feature/hs/hs_service.h" #include "feature/hs_common/shared_random_client.h" @@ -808,12 +809,12 @@ hs_parse_address_impl(const char *address, ed25519_public_key_t *key_out, } /** Using the given identity public key and a blinded public key, compute the - * subcredential and put it in subcred_out (must be of size DIGEST256_LEN). + * subcredential and put it in subcred_out. * This can't fail. */ void hs_get_subcredential(const ed25519_public_key_t *identity_pk, const ed25519_public_key_t *blinded_pk, - uint8_t *subcred_out) + hs_subcredential_t *subcred_out) { uint8_t credential[DIGEST256_LEN]; crypto_digest_t *digest; @@ -841,7 +842,8 @@ hs_get_subcredential(const ed25519_public_key_t *identity_pk, sizeof(credential)); crypto_digest_add_bytes(digest, (const char *) blinded_pk->pubkey, ED25519_PUBKEY_LEN); - crypto_digest_get_digest(digest, (char *) subcred_out, DIGEST256_LEN); + crypto_digest_get_digest(digest, (char *) subcred_out->subcred, + SUBCRED_LEN); crypto_digest_free(digest); memwipe(credential, 0, sizeof(credential)); @@ -909,30 +911,35 @@ hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn) * case the caller would want only one field. checksum_out MUST at least be 2 * bytes long. * - * Return 0 if parsing went well; return -1 in case of error. */ + * Return 0 if parsing went well; return -1 in case of error and if errmsg is + * non NULL, a human readable string message is set. */ int -hs_parse_address(const char *address, ed25519_public_key_t *key_out, - uint8_t *checksum_out, uint8_t *version_out) +hs_parse_address_no_log(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out, + const char **errmsg) { char decoded[HS_SERVICE_ADDR_LEN]; tor_assert(address); + if (errmsg) { + *errmsg = NULL; + } + /* Obvious length check. */ if (strlen(address) != HS_SERVICE_ADDR_LEN_BASE32) { - log_warn(LD_REND, "Service address %s has an invalid length. " - "Expected %lu but got %lu.", - escaped_safe_str(address), - (unsigned long) HS_SERVICE_ADDR_LEN_BASE32, - (unsigned long) strlen(address)); + if (errmsg) { + *errmsg = "Invalid length"; + } goto invalid; } /* Decode address so we can extract needed fields. */ if (base32_decode(decoded, sizeof(decoded), address, strlen(address)) != sizeof(decoded)) { - log_warn(LD_REND, "Service address %s can't be decoded.", - escaped_safe_str(address)); + if (errmsg) { + *errmsg = "Unable to base32 decode"; + } goto invalid; } @@ -944,6 +951,22 @@ hs_parse_address(const char *address, ed25519_public_key_t *key_out, return -1; } +/** Same has hs_parse_address_no_log() but emits a log warning on parsing + * failure. */ +int +hs_parse_address(const char *address, ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out) +{ + const char *errmsg = NULL; + int ret = hs_parse_address_no_log(address, key_out, checksum_out, + version_out, &errmsg); + if (ret < 0) { + log_warn(LD_REND, "Service address %s failed to be parsed: %s", + escaped_safe_str(address), errmsg); + } + return ret; +} + /** Validate a given onion address. The length, the base32 decoding, and * checksum are validated. Return 1 if valid else 0. */ int @@ -1807,6 +1830,7 @@ hs_free_all(void) hs_service_free_all(); hs_cache_free_all(); hs_client_free_all(); + hs_ob_free_all(); } /** For the given origin circuit circ, decrement the number of rendezvous diff --git a/src/feature/hs/hs_common.h b/src/feature/hs/hs_common.h index 8f743d4d37..997b7298a6 100644 --- a/src/feature/hs/hs_common.h +++ b/src/feature/hs/hs_common.h @@ -179,6 +179,10 @@ void hs_build_address(const struct ed25519_public_key_t *key, uint8_t version, int hs_address_is_valid(const char *address); int hs_parse_address(const char *address, struct ed25519_public_key_t *key_out, uint8_t *checksum_out, uint8_t *version_out); +int hs_parse_address_no_log(const char *address, + struct ed25519_public_key_t *key_out, + uint8_t *checksum_out, uint8_t *version_out, + const char **errmsg); void hs_build_blinded_pubkey(const struct ed25519_public_key_t *pubkey, const uint8_t *secret, size_t secret_len, @@ -210,9 +214,10 @@ const uint8_t *rend_data_get_pk_digest(const rend_data_t *rend_data, routerstatus_t *pick_hsdir(const char *desc_id, const char *desc_id_base32); +struct hs_subcredential_t; void hs_get_subcredential(const struct ed25519_public_key_t *identity_pk, const struct ed25519_public_key_t *blinded_pk, - uint8_t *subcred_out); + struct hs_subcredential_t *subcred_out); uint64_t hs_get_previous_time_period_num(time_t now); uint64_t hs_get_time_period_num(time_t now); diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c index 64656b1935..684f7bc975 100644 --- a/src/feature/hs/hs_config.c +++ b/src/feature/hs/hs_config.c @@ -26,6 +26,7 @@ #include "feature/hs/hs_common.h" #include "feature/hs/hs_config.h" #include "feature/hs/hs_client.h" +#include "feature/hs/hs_ob.h" #include "feature/hs/hs_service.h" #include "feature/rend/rendclient.h" #include "feature/rend/rendservice.h" @@ -219,6 +220,7 @@ config_has_invalid_options(const config_line_t *line_, "HiddenServiceEnableIntroDoSDefense", "HiddenServiceEnableIntroDoSRatePerSec", "HiddenServiceEnableIntroDoSBurstPerSec", + "HiddenServiceOnionBalanceInstance", NULL /* End marker. */ }; @@ -317,7 +319,7 @@ config_service_v3(const config_line_t *line_, int have_num_ip = 0; bool export_circuit_id = false; /* just to detect duplicate options */ bool dos_enabled = false, dos_rate_per_sec = false; - bool dos_burst_per_sec = false; + bool dos_burst_per_sec = false, ob_instance = false; const char *dup_opt_seen = NULL; const config_line_t *line; @@ -402,6 +404,27 @@ config_service_v3(const config_line_t *line_, config->intro_dos_burst_per_sec); continue; } + if (!strcasecmp(line->key, "HiddenServiceOnionBalanceInstance")) { + bool enabled = !!helper_parse_uint64(line->key, line->value, + 0, 1, &ok); + if (!ok || ob_instance) { + if (ob_instance) { + dup_opt_seen = line->key; + } + goto err; + } + ob_instance = true; + if (!enabled) { + /* Skip if this is disabled. */ + continue; + } + /* Option is enabled, parse config file. */ + ok = hs_ob_parse_config_file(config); + if (!ok) { + goto err; + } + continue; + } } /* We do not load the key material for the service at this stage. This is diff --git a/src/feature/hs/hs_descriptor.c b/src/feature/hs/hs_descriptor.c index 65d6c7a581..c274ed7581 100644 --- a/src/feature/hs/hs_descriptor.c +++ b/src/feature/hs/hs_descriptor.c @@ -211,7 +211,7 @@ build_secret_input(const hs_descriptor_t *desc, memcpy(secret_input, secret_data, secret_data_len); offset += secret_data_len; /* Copy subcredential. */ - memcpy(secret_input + offset, desc->subcredential, DIGEST256_LEN); + memcpy(secret_input + offset, desc->subcredential.subcred, DIGEST256_LEN); offset += DIGEST256_LEN; /* Copy revision counter value. */ set_uint64(secret_input + offset, @@ -1018,10 +1018,6 @@ desc_encode_v3(const hs_descriptor_t *desc, tor_assert(encoded_out); tor_assert(desc->plaintext_data.version == 3); - if (BUG(desc->subcredential == NULL)) { - goto err; - } - /* Build the non-encrypted values. */ { char *encoded_cert; @@ -1366,8 +1362,7 @@ encrypted_data_length_is_valid(size_t len) * and return the buffer's length. The caller should wipe and free its content * once done with it. This function can't fail. */ static size_t -build_descriptor_cookie_keys(const uint8_t *subcredential, - size_t subcredential_len, +build_descriptor_cookie_keys(const hs_subcredential_t *subcredential, const curve25519_secret_key_t *sk, const curve25519_public_key_t *pk, uint8_t **keys_out) @@ -1389,7 +1384,7 @@ build_descriptor_cookie_keys(const uint8_t *subcredential, /* Calculate KEYS = KDF(subcredential | SECRET_SEED, 40) */ xof = crypto_xof_new(); - crypto_xof_add_bytes(xof, subcredential, subcredential_len); + crypto_xof_add_bytes(xof, subcredential->subcred, SUBCRED_LEN); crypto_xof_add_bytes(xof, secret_seed, sizeof(secret_seed)); crypto_xof_squeeze_bytes(xof, keystream, keystream_len); crypto_xof_free(xof); @@ -1426,11 +1421,12 @@ decrypt_descriptor_cookie(const hs_descriptor_t *desc, sizeof(desc->superencrypted_data.auth_ephemeral_pubkey))); tor_assert(!fast_mem_is_zero((char *) client_auth_sk, sizeof(*client_auth_sk))); - tor_assert(!fast_mem_is_zero((char *) desc->subcredential, DIGEST256_LEN)); + tor_assert(!fast_mem_is_zero((char *) desc->subcredential.subcred, + DIGEST256_LEN)); /* Get the KEYS component to derive the CLIENT-ID and COOKIE-KEY. */ keystream_length = - build_descriptor_cookie_keys(desc->subcredential, DIGEST256_LEN, + build_descriptor_cookie_keys(&desc->subcredential, client_auth_sk, &desc->superencrypted_data.auth_ephemeral_pubkey, &keystream); @@ -2558,7 +2554,7 @@ hs_desc_decode_plaintext(const char *encoded, * set to NULL. */ hs_desc_decode_status_t hs_desc_decode_descriptor(const char *encoded, - const uint8_t *subcredential, + const hs_subcredential_t *subcredential, const curve25519_secret_key_t *client_auth_sk, hs_descriptor_t **desc_out) { @@ -2576,7 +2572,7 @@ hs_desc_decode_descriptor(const char *encoded, goto err; } - memcpy(desc->subcredential, subcredential, sizeof(desc->subcredential)); + memcpy(&desc->subcredential, subcredential, sizeof(desc->subcredential)); ret = hs_desc_decode_plaintext(encoded, &desc->plaintext_data); if (ret != HS_DESC_DECODE_OK) { @@ -2666,7 +2662,7 @@ hs_desc_encode_descriptor,(const hs_descriptor_t *desc, * symmetric only if the client auth is disabled. That is, the descriptor * cookie will be NULL. */ if (!descriptor_cookie) { - ret = hs_desc_decode_descriptor(*encoded_out, desc->subcredential, + ret = hs_desc_decode_descriptor(*encoded_out, &desc->subcredential, NULL, NULL); if (BUG(ret != HS_DESC_DECODE_OK)) { ret = -1; @@ -2870,7 +2866,7 @@ hs_desc_build_fake_authorized_client(void) * key, and descriptor cookie, build the auth client so we can then encode the * descriptor for publication. client_out must be already allocated. */ void -hs_desc_build_authorized_client(const uint8_t *subcredential, +hs_desc_build_authorized_client(const hs_subcredential_t *subcredential, const curve25519_public_key_t *client_auth_pk, const curve25519_secret_key_t * auth_ephemeral_sk, @@ -2898,7 +2894,7 @@ hs_desc_build_authorized_client(const uint8_t *subcredential, /* Get the KEYS part so we can derive the CLIENT-ID and COOKIE-KEY. */ keystream_length = - build_descriptor_cookie_keys(subcredential, DIGEST256_LEN, + build_descriptor_cookie_keys(subcredential, auth_ephemeral_sk, client_auth_pk, &keystream); tor_assert(keystream_length > 0); diff --git a/src/feature/hs/hs_descriptor.h b/src/feature/hs/hs_descriptor.h index 639dd31c8f..08daa904b6 100644 --- a/src/feature/hs/hs_descriptor.h +++ b/src/feature/hs/hs_descriptor.h @@ -14,6 +14,7 @@ #include "core/or/or.h" #include "trunnel/ed25519_cert.h" /* needed for trunnel */ #include "feature/nodelist/torcert.h" +#include "core/crypto/hs_ntor.h" /* for hs_subcredential_t */ /* Trunnel */ struct link_specifier_t; @@ -238,7 +239,7 @@ typedef struct hs_descriptor_t { /** Subcredentials of a service, used by the client and service to decrypt * the encrypted data. */ - uint8_t subcredential[DIGEST256_LEN]; + hs_subcredential_t subcredential; } hs_descriptor_t; /** Return true iff the given descriptor format version is supported. */ @@ -277,7 +278,7 @@ MOCK_DECL(int, char **encoded_out)); int hs_desc_decode_descriptor(const char *encoded, - const uint8_t *subcredential, + const hs_subcredential_t *subcredential, const curve25519_secret_key_t *client_auth_sk, hs_descriptor_t **desc_out); int hs_desc_decode_plaintext(const char *encoded, @@ -302,7 +303,7 @@ void hs_desc_authorized_client_free_(hs_desc_authorized_client_t *client); hs_desc_authorized_client_t *hs_desc_build_fake_authorized_client(void); -void hs_desc_build_authorized_client(const uint8_t *subcredential, +void hs_desc_build_authorized_client(const hs_subcredential_t *subcredential, const curve25519_public_key_t * client_auth_pk, const curve25519_secret_key_t * diff --git a/src/feature/hs/hs_ob.c b/src/feature/hs/hs_ob.c new file mode 100644 index 0000000000..f135ecd3f4 --- /dev/null +++ b/src/feature/hs/hs_ob.c @@ -0,0 +1,408 @@ +/* Copyright (c) 2017-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ob.c + * \brief Implement Onion Balance specific code. + **/ + +#define HS_OB_PRIVATE + +#include "feature/hs/hs_service.h" + +#include "feature/nodelist/networkstatus.h" +#include "feature/nodelist/networkstatus_st.h" + +#include "lib/confmgt/confmgt.h" +#include "lib/encoding/confline.h" + +#include "feature/hs/hs_ob.h" + +/* Options config magic number. */ +#define OB_OPTIONS_MAGIC 0x631DE7EA + +/* Helper macros. */ +#define VAR(varname, conftype, member, initvalue) \ + CONFIG_VAR_ETYPE(ob_options_t, varname, conftype, member, 0, initvalue) +#define V(member,conftype,initvalue) \ + VAR(#member, conftype, member, initvalue) + +/* Dummy instance of ob_options_t, used for type-checking its members with + * CONF_CHECK_VAR_TYPE. */ +DUMMY_TYPECHECK_INSTANCE(ob_options_t); + +/* Array of variables for the config file options. */ +static const config_var_t config_vars[] = { + V(MasterOnionAddress, LINELIST, NULL), + + END_OF_CONFIG_VARS +}; + +/* "Extra" variable in the state that receives lines we can't parse. This + * lets us preserve options from versions of Tor newer than us. */ +static const struct_member_t config_extra_vars = { + .name = "__extra", + .type = CONFIG_TYPE_LINELIST, + .offset = offsetof(ob_options_t, ExtraLines), +}; + +/* Configuration format of ob_options_t. */ +static const config_format_t config_format = { + .size = sizeof(ob_options_t), + .magic = { + "ob_options_t", + OB_OPTIONS_MAGIC, + offsetof(ob_options_t, magic_), + }, + .vars = config_vars, + .extra = &config_extra_vars, +}; + +/* Global configuration manager for the config file. */ +static config_mgr_t *config_options_mgr = NULL; + +/* Return the configuration manager for the config file. */ +static const config_mgr_t * +get_config_options_mgr(void) +{ + if (PREDICT_UNLIKELY(config_options_mgr == NULL)) { + config_options_mgr = config_mgr_new(&config_format); + config_mgr_freeze(config_options_mgr); + } + return config_options_mgr; +} + +#define ob_option_free(val) \ + FREE_AND_NULL(ob_options_t, ob_option_free_, (val)) + +/** Helper: Free a config options object. */ +static void +ob_option_free_(ob_options_t *opts) +{ + if (opts == NULL) { + return; + } + config_free(get_config_options_mgr(), opts); +} + +/** Return an allocated config options object. */ +static ob_options_t * +ob_option_new(void) +{ + ob_options_t *opts = config_new(get_config_options_mgr()); + config_init(get_config_options_mgr(), opts); + return opts; +} + +/** Helper function: From the configuration line value which is an onion + * address with the ".onion" extension, find the public key and put it in + * pkey_out. + * + * On success, true is returned. Else, false and pkey is untouched. */ +static bool +get_onion_public_key(const char *value, ed25519_public_key_t *pkey_out) +{ + char address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; + + tor_assert(value); + tor_assert(pkey_out); + + if (strcmpend(value, ".onion")) { + /* Not a .onion extension, bad format. */ + return false; + } + + /* Length validation. The -1 is because sizeof() counts the NUL byte. */ + if (strlen(value) > + (HS_SERVICE_ADDR_LEN_BASE32 + sizeof(".onion") - 1)) { + /* Too long, bad format. */ + return false; + } + + /* We don't want the .onion so we add 2 because size - 1 is copied with + * strlcpy() in order to accomodate the NUL byte and sizeof() counts the NUL + * byte so we need to remove them from the equation. */ + strlcpy(address, value, strlen(value) - sizeof(".onion") + 2); + + if (hs_parse_address_no_log(address, pkey_out, NULL, NULL, NULL) < 0) { + return false; + } + + /* Success. */ + return true; +} + +/** Parse the given ob options in opts and set the service config object + * accordingly. + * + * Return 1 on success else 0. */ +static int +ob_option_parse(hs_service_config_t *config, const ob_options_t *opts) +{ + int ret = 0; + config_line_t *line; + + tor_assert(config); + tor_assert(opts); + + for (line = opts->MasterOnionAddress; line; line = line->next) { + /* Allocate config list if need be. */ + if (!config->ob_master_pubkeys) { + config->ob_master_pubkeys = smartlist_new(); + } + ed25519_public_key_t *pubkey = tor_malloc_zero(sizeof(*pubkey)); + + if (!get_onion_public_key(line->value, pubkey)) { + log_warn(LD_REND, "OnionBalance: MasterOnionAddress %s is invalid", + line->value); + tor_free(pubkey); + goto end; + } + smartlist_add(config->ob_master_pubkeys, pubkey); + log_info(LD_REND, "OnionBalance: MasterOnionAddress %s registered", + line->value); + } + /* Success. */ + ret = 1; + + end: + /* No keys added, we free the list since no list means no onion balance + * support for this tor instance. */ + if (smartlist_len(config->ob_master_pubkeys) == 0) { + smartlist_free(config->ob_master_pubkeys); + } + return ret; +} + +/** For the given master public key and time period, compute the subcredential + * and put them into subcredential. The subcredential parameter needs to be at + * least DIGEST256_LEN in size. */ +static void +build_subcredential(const ed25519_public_key_t *pkey, uint64_t tp, + hs_subcredential_t *subcredential) +{ + ed25519_public_key_t blinded_pubkey; + + tor_assert(pkey); + tor_assert(subcredential); + + hs_build_blinded_pubkey(pkey, NULL, 0, tp, &blinded_pubkey); + hs_get_subcredential(pkey, &blinded_pubkey, subcredential); +} + +/* + * Public API. + */ + +/** Return true iff the given service is configured as an onion balance + * instance. To satisfy that condition, there must at least be one master + * ed25519 public key configured. */ +bool +hs_ob_service_is_instance(const hs_service_t *service) +{ + if (BUG(service == NULL)) { + return false; + } + + /* No list, we are not an instance. */ + if (!service->config.ob_master_pubkeys) { + return false; + } + + return smartlist_len(service->config.ob_master_pubkeys) > 0; +} + +/** Read and parse the config file at fname on disk. The service config object + * is populated with the options if any. + * + * Return 1 on success else 0. This is to follow the "ok" convention in + * hs_config.c. */ +int +hs_ob_parse_config_file(hs_service_config_t *config) +{ + static const char *fname = "ob_config"; + int ret = 0; + char *content = NULL, *errmsg = NULL, *config_file_path = NULL; + ob_options_t *options = NULL; + config_line_t *lines = NULL; + + tor_assert(config); + + /* Read file from disk. */ + config_file_path = hs_path_from_filename(config->directory_path, fname); + content = read_file_to_str(config_file_path, 0, NULL); + if (!content) { + log_warn(LD_FS, "OnionBalance: Unable to read config file %s", + escaped(config_file_path)); + goto end; + } + + /* Parse lines. */ + if (config_get_lines(content, &lines, 0) < 0) { + goto end; + } + + options = ob_option_new(); + config_assign(get_config_options_mgr(), options, lines, 0, &errmsg); + if (errmsg) { + log_warn(LD_REND, "OnionBalance: Unable to parse config file: %s", + errmsg); + tor_free(errmsg); + goto end; + } + + /* Parse the options and set the service config object with the details. */ + ret = ob_option_parse(config, options); + + end: + config_free_lines(lines); + ob_option_free(options); + tor_free(content); + tor_free(config_file_path); + return ret; +} + +/** Compute all possible subcredentials for every onion master key in the given + * service config object. subcredentials_out is allocated and set as an + * continous array containing all possible values. + * + * On success, return the number of subcredential put in the array which will + * correspond to an arry of size: n * DIGEST256_LEN where DIGEST256_LEN is the + * length of a single subcredential. + * + * If the given configuration object has no OB master keys configured, 0 is + * returned and subcredentials_out is set to NULL. + * + * Otherwise, this can't fail. */ +STATIC size_t +compute_subcredentials(const hs_service_t *service, + hs_subcredential_t **subcredentials_out) +{ + unsigned int num_pkeys, idx = 0; + hs_subcredential_t *subcreds = NULL; + const int steps[3] = {0, -1, 1}; + const unsigned int num_steps = ARRAY_LENGTH(steps); + const uint64_t tp = hs_get_time_period_num(0); + + tor_assert(service); + tor_assert(subcredentials_out); + /* Our caller has checked these too */ + tor_assert(service->desc_current); + tor_assert(service->desc_next); + + /* Make sure we are an OB instance, or bail out. */ + num_pkeys = smartlist_len(service->config.ob_master_pubkeys); + if (!num_pkeys) { + *subcredentials_out = NULL; + return 0; + } + + /* Time to build all the subcredentials for each time period: two for each + * instance descriptor plus three for the onionbalance frontend service: the + * previous one (-1), the current one (0) and the next one (1) for each + * configured key in order to accomodate client and service consensus skew. + * + * If the client consensus after_time is at 23:00 but the service one is at + * 01:00, the client will be using the previous time period where the + * service will think it is the client next time period. Thus why we have + * to try them all. + * + * The normal use case works because the service gets the descriptor object + * that corresponds to the intro point's request, and because each + * descriptor corresponds to a specific subcredential, we get the right + * subcredential out of it, and use that to do the decryption. + * + * As a slight optimization, statistically, the current time period (0) will + * be the one to work first so we'll put them first in the array to maximize + * our chance of success. */ + + /* We use a flat array, not a smartlist_t, in order to minimize memory + * allocation. + * + * Size of array is: length of a single subcredential multiplied by the + * number of time period we need to compute and finally multiplied by the + * total number of keys we are about to process. In other words, for each + * key, we allocate 3 subcredential slots. Then in the end we also add two + * subcredentials for this instance's active descriptors. */ + subcreds = + tor_calloc((num_steps * num_pkeys) + 2, sizeof(hs_subcredential_t)); + + /* For each master pubkey we add 3 subcredentials: */ + for (unsigned int i = 0; i < num_steps; i++) { + SMARTLIST_FOREACH_BEGIN(service->config.ob_master_pubkeys, + const ed25519_public_key_t *, pkey) { + build_subcredential(pkey, tp + steps[i], &subcreds[idx]); + idx++; + } SMARTLIST_FOREACH_END(pkey); + } + + /* And then in the end we add the two subcredentials of the current active + * instance descriptors */ + memcpy(&subcreds[idx++], &service->desc_current->desc->subcredential, + sizeof(hs_subcredential_t)); + memcpy(&subcreds[idx++], &service->desc_next->desc->subcredential, + sizeof(hs_subcredential_t)); + + log_info(LD_REND, "Refreshing %u onionbalance keys (TP #%d).", + idx, (int)tp); + + *subcredentials_out = subcreds; + return idx; +} + +/** + * If we are an Onionbalance instance, refresh our keys. + * + * If we are not an Onionbalance instance or we are not ready to do so, this + * is a NOP. + * + * This function is called everytime we build a new descriptor. That's because + * we want our Onionbalance keys to always use up-to-date subcredentials both + * for the instance (ourselves) and for the onionbalance frontend. + */ +void +hs_ob_refresh_keys(hs_service_t *service) +{ + hs_subcredential_t *ob_subcreds = NULL; + size_t num_subcreds; + + tor_assert(service); + + /* Don't do any of this if we are not configured as an OB instance */ + if (!hs_ob_service_is_instance(service)) { + return; + } + + /* We need both service descriptors created to make onionbalance keys. + * + * That's because we fetch our own (the instance's) subcredentials from our + * own descriptors which should always include the latest subcredentials that + * clients would use. + * + * This function is called with each descriptor build, so we will be + * eventually be called when both descriptors are created. */ + if (!service->desc_current || !service->desc_next) { + return; + } + + /* Get a new set of subcreds */ + num_subcreds = compute_subcredentials(service, &ob_subcreds); + if (BUG(!num_subcreds)) { + return; + } + + /* Delete old subcredentials if any */ + if (service->ob_subcreds) { + tor_free(service->ob_subcreds); + } + + service->ob_subcreds = ob_subcreds; + service->n_ob_subcreds = num_subcreds; +} + +/** Free any memory allocated by the onionblance subsystem. */ +void +hs_ob_free_all(void) +{ + config_mgr_free(config_options_mgr); +} diff --git a/src/feature/hs/hs_ob.h b/src/feature/hs/hs_ob.h new file mode 100644 index 0000000000..d6e6e73a84 --- /dev/null +++ b/src/feature/hs/hs_ob.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file hs_ob.h + * \brief Header file for the specific code for onion balance. + **/ + +#ifndef TOR_HS_OB_H +#define TOR_HS_OB_H + +#include "feature/hs/hs_service.h" + +bool hs_ob_service_is_instance(const hs_service_t *service); + +int hs_ob_parse_config_file(hs_service_config_t *config); + +struct hs_subcredential_t; + +void hs_ob_free_all(void); + +void hs_ob_refresh_keys(hs_service_t *service); + +#ifdef HS_OB_PRIVATE + +STATIC size_t compute_subcredentials(const hs_service_t *service, + struct hs_subcredential_t **subcredentials); + +typedef struct ob_options_t { + /** Magic number to identify the structure in memory. */ + uint32_t magic_; + /** Master Onion Address(es). */ + struct config_line_t *MasterOnionAddress; + /** Extra Lines for configuration we might not know. */ + struct config_line_t *ExtraLines; +} ob_options_t; + +#endif /* defined(HS_OB_PRIVATE) */ + +#endif /* !defined(TOR_HS_OB_H) */ diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index b366ce83d9..3a2beb766f 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -41,6 +41,7 @@ #include "feature/hs/hs_intropoint.h" #include "feature/hs/hs_service.h" #include "feature/hs/hs_stats.h" +#include "feature/hs/hs_ob.h" #include "feature/dircommon/dir_connection_st.h" #include "core/or/edge_connection_st.h" @@ -151,11 +152,11 @@ HT_PROTOTYPE(hs_service_ht, /* Name of hashtable. */ hs_service_t, /* Object contained in the map. */ hs_service_node, /* The name of the HT_ENTRY member. */ hs_service_ht_hash, /* Hashing function. */ - hs_service_ht_eq) /* Compare function for objects. */ + hs_service_ht_eq); /* Compare function for objects. */ HT_GENERATE2(hs_service_ht, hs_service_t, hs_service_node, hs_service_ht_hash, hs_service_ht_eq, - 0.6, tor_reallocarray, tor_free_) + 0.6, tor_reallocarray, tor_free_); /** Query the given service map with a public key and return a service object * if found else NULL. It is also possible to set a directory path in the @@ -267,6 +268,11 @@ service_clear_config(hs_service_config_t *config) service_authorized_client_free(p)); smartlist_free(config->clients); } + if (config->ob_master_pubkeys) { + SMARTLIST_FOREACH(config->ob_master_pubkeys, ed25519_public_key_t *, k, + tor_free(k)); + smartlist_free(config->ob_master_pubkeys); + } memset(config, 0, sizeof(*config)); } @@ -701,8 +707,8 @@ get_extend_info_from_intro_point(const hs_service_intro_point_t *ip, /** Return the number of introduction points that are established for the * given descriptor. */ -static unsigned int -count_desc_circuit_established(const hs_service_descriptor_t *desc) +MOCK_IMPL(STATIC unsigned int, +count_desc_circuit_established, (const hs_service_descriptor_t *desc)) { unsigned int count = 0; @@ -1764,7 +1770,8 @@ build_service_desc_superencrypted(const hs_service_t *service, sizeof(curve25519_public_key_t)); /* Test that subcred is not zero because we might use it below */ - if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential, DIGEST256_LEN))) { + if (BUG(fast_mem_is_zero((char*)desc->desc->subcredential.subcred, + DIGEST256_LEN))) { return -1; } @@ -1781,7 +1788,7 @@ build_service_desc_superencrypted(const hs_service_t *service, /* Prepare the client for descriptor and then add to the list in the * superencrypted part of the descriptor */ - hs_desc_build_authorized_client(desc->desc->subcredential, + hs_desc_build_authorized_client(&desc->desc->subcredential, &client->client_pk, &desc->auth_ephemeral_kp.seckey, desc->descriptor_cookie, desc_client); @@ -1837,7 +1844,7 @@ build_service_desc_plaintext(const hs_service_t *service, /* Set the subcredential. */ hs_get_subcredential(&service->keys.identity_pk, &desc->blinded_kp.pubkey, - desc->desc->subcredential); + &desc->desc->subcredential); plaintext = &desc->desc->plaintext_data; @@ -1980,9 +1987,15 @@ build_service_descriptor(hs_service_t *service, uint64_t time_period_num, /* Assign newly built descriptor to the next slot. */ *desc_out = desc; + /* Fire a CREATED control port event. */ hs_control_desc_event_created(service->onion_address, &desc->blinded_kp.pubkey); + + /* If we are an onionbalance instance, we refresh our keys when we rotate + * descriptors. */ + hs_ob_refresh_keys(service); + return; err: @@ -3042,13 +3055,85 @@ service_desc_hsdirs_changed(const hs_service_t *service, return should_reupload; } +/** These are all the reasons why a descriptor upload can't occur. We use + * those to log the reason properly with the right rate limiting and for the + * right descriptor. */ +typedef enum { + LOG_DESC_UPLOAD_REASON_MISSING_IPS = 0, + LOG_DESC_UPLOAD_REASON_IP_NOT_ESTABLISHED = 1, + LOG_DESC_UPLOAD_REASON_NOT_TIME = 2, + LOG_DESC_UPLOAD_REASON_NO_LIVE_CONSENSUS = 3, + LOG_DESC_UPLOAD_REASON_NO_DIRINFO = 4, +} log_desc_upload_reason_t; + +/** Maximum number of reasons. This is used to allocate the static array of + * all rate limiting objects. */ +#define LOG_DESC_UPLOAD_REASON_MAX LOG_DESC_UPLOAD_REASON_NO_DIRINFO + +/** Log the reason why we can't upload the given descriptor for the given + * service. This takes a message string (allocated by the caller) and a + * reason. + * + * Depending on the reason and descriptor, different rate limit applies. This + * is done because this function will basically be called every second. Each + * descriptor for each reason uses its own log rate limit object in order to + * avoid message suppression for different reasons and descriptors. */ +static void +log_cant_upload_desc(const hs_service_t *service, + const hs_service_descriptor_t *desc, const char *msg, + const log_desc_upload_reason_t reason) +{ + /* Writing the log every minute shouldn't be too annoying for log rate limit + * since this can be emitted every second for each descriptor. + * + * However, for one specific case, we increase it to 10 minutes because it + * is hit constantly, as an expected behavior, which is the reason + * indicating that it is not the time to upload. */ + static ratelim_t limits[2][LOG_DESC_UPLOAD_REASON_MAX + 1] = + { { RATELIM_INIT(60), RATELIM_INIT(60), RATELIM_INIT(60 * 10), + RATELIM_INIT(60), RATELIM_INIT(60) }, + { RATELIM_INIT(60), RATELIM_INIT(60), RATELIM_INIT(60 * 10), + RATELIM_INIT(60), RATELIM_INIT(60) }, + }; + bool is_next_desc = false; + unsigned int rlim_pos = 0; + ratelim_t *rlim = NULL; + + tor_assert(service); + tor_assert(desc); + tor_assert(msg); + + /* Make sure the reason value is valid. It should never happen because we + * control that value in the code flow but will be apparent during + * development if a reason is added but LOG_DESC_UPLOAD_REASON_NUM_ is not + * updated. */ + if (BUG(reason > LOG_DESC_UPLOAD_REASON_MAX || reason < 0)) { + return; + } + + /* Ease our life. Flag that tells us if the descriptor is the next one. */ + is_next_desc = (service->desc_next == desc); + + /* Current descriptor is the first element in the ratelimit object array. + * The next descriptor is the second element. */ + rlim_pos = (is_next_desc ? 1 : 0); + /* Get the ratelimit object for the reason _and_ right descriptor. */ + rlim = &limits[rlim_pos][reason]; + + log_fn_ratelim(rlim, LOG_INFO, LD_REND, + "Service %s can't upload its %s descriptor: %s", + safe_str_client(service->onion_address), + (is_next_desc) ? "next" : "current", msg); +} + /** Return 1 if the given descriptor from the given service can be uploaded * else return 0 if it can not. */ static int should_service_upload_descriptor(const hs_service_t *service, const hs_service_descriptor_t *desc, time_t now) { - unsigned int num_intro_points; + char *msg = NULL; + unsigned int num_intro_points, count_ip_established; tor_assert(service); tor_assert(desc); @@ -3068,34 +3153,54 @@ should_service_upload_descriptor(const hs_service_t *service, * upload descriptor in this case. We need at least one for the service to * be reachable. */ if (desc->missing_intro_points && num_intro_points == 0) { + msg = tor_strdup("Missing intro points"); + log_cant_upload_desc(service, desc, msg, + LOG_DESC_UPLOAD_REASON_MISSING_IPS); goto cannot; } /* Check if all our introduction circuit have been established for all the * intro points we have selected. */ - if (count_desc_circuit_established(desc) != num_intro_points) { + count_ip_established = count_desc_circuit_established(desc); + if (count_ip_established != num_intro_points) { + tor_asprintf(&msg, "Intro circuits aren't yet all established (%d/%d).", + count_ip_established, num_intro_points); + log_cant_upload_desc(service, desc, msg, + LOG_DESC_UPLOAD_REASON_IP_NOT_ESTABLISHED); goto cannot; } /* Is it the right time to upload? */ if (desc->next_upload_time > now) { + tor_asprintf(&msg, "Next upload time is %ld, it is now %ld.", + (long int) desc->next_upload_time, (long int) now); + log_cant_upload_desc(service, desc, msg, + LOG_DESC_UPLOAD_REASON_NOT_TIME); goto cannot; } /* Don't upload desc if we don't have a live consensus */ if (!networkstatus_get_live_consensus(now)) { + msg = tor_strdup("No live consensus"); + log_cant_upload_desc(service, desc, msg, + LOG_DESC_UPLOAD_REASON_NO_LIVE_CONSENSUS); goto cannot; } /* Do we know enough router descriptors to have adequate vision of the HSDir hash ring? */ if (!router_have_minimum_dir_info()) { + msg = tor_strdup("Not enough directory information"); + log_cant_upload_desc(service, desc, msg, + LOG_DESC_UPLOAD_REASON_NO_DIRINFO); goto cannot; } /* Can upload! */ return 1; + cannot: + tor_free(msg); return 0; } @@ -3369,7 +3474,7 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, /* The following will parse, decode and launch the rendezvous point circuit. * Both current and legacy cells are handled. */ - if (hs_circ_handle_introduce2(service, circ, ip, desc->desc->subcredential, + if (hs_circ_handle_introduce2(service, circ, ip, &desc->desc->subcredential, payload, payload_len) < 0) { goto err; } @@ -4048,6 +4153,11 @@ hs_service_free_(hs_service_t *service) replaycache_free(service->state.replay_cache_rend_cookie); } + /* Free onionbalance subcredentials (if any) */ + if (service->ob_subcreds) { + tor_free(service->ob_subcreds); + } + /* Wipe service keys. */ memwipe(&service->keys.identity_sk, 0, sizeof(service->keys.identity_sk)); diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index 8809411e01..3fe14878ed 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -248,10 +248,14 @@ typedef struct hs_service_config_t { /** Does this service export the circuit ID of its clients? */ hs_circuit_id_protocol_t circuit_id_protocol; - /* DoS defenses. For the ESTABLISH_INTRO cell extension. */ + /** DoS defenses. For the ESTABLISH_INTRO cell extension. */ unsigned int has_dos_defense_enabled : 1; uint32_t intro_dos_rate_per_sec; uint32_t intro_dos_burst_per_sec; + + /** If set, contains the Onion Balance master ed25519 public key (taken from + * an .onion addresses) that this tor instance serves as backend. */ + smartlist_t *ob_master_pubkeys; } hs_service_config_t; /** Service state. */ @@ -301,8 +305,13 @@ typedef struct hs_service_t { /** Next descriptor. */ hs_service_descriptor_t *desc_next; - /* XXX: Credential (client auth.) #20700. */ - + /* If this is an onionbalance instance, this is an array of subcredentials + * that should be used when decrypting an INTRO2 cell. If this is not an + * onionbalance instance, this is NULL. + * See [ONIONBALANCE] section in rend-spec-v3.txt for more details . */ + hs_subcredential_t *ob_subcreds; + /* Number of OB subcredentials */ + size_t n_ob_subcreds; } hs_service_t; /** For the service global hash map, we define a specific type for it which @@ -375,6 +384,9 @@ STATIC hs_service_t *get_first_service(void); STATIC hs_service_intro_point_t *service_intro_point_find_by_ident( const hs_service_t *service, const hs_ident_circuit_t *ident); + +MOCK_DECL(STATIC unsigned int, count_desc_circuit_established, + (const hs_service_descriptor_t *desc)); #endif /* defined(TOR_UNIT_TESTS) */ /* Service accessors. */ diff --git a/src/feature/hs/include.am b/src/feature/hs/include.am index 5e69607e59..f83907c76b 100644 --- a/src/feature/hs/include.am +++ b/src/feature/hs/include.am @@ -13,6 +13,7 @@ LIBTOR_APP_A_SOURCES += \ src/feature/hs/hs_dos.c \ src/feature/hs/hs_ident.c \ src/feature/hs/hs_intropoint.c \ + src/feature/hs/hs_ob.c \ src/feature/hs/hs_service.c \ src/feature/hs/hs_stats.c @@ -30,6 +31,7 @@ noinst_HEADERS += \ src/feature/hs/hs_dos.h \ src/feature/hs/hs_ident.h \ src/feature/hs/hs_intropoint.h \ + src/feature/hs/hs_ob.h \ src/feature/hs/hs_service.h \ src/feature/hs/hs_stats.h \ src/feature/hs/hsdir_index_st.h diff --git a/src/feature/hs_common/shared_random_client.c b/src/feature/hs_common/shared_random_client.c index a46666ab50..3f46321be4 100644 --- a/src/feature/hs_common/shared_random_client.c +++ b/src/feature/hs_common/shared_random_client.c @@ -11,7 +11,8 @@ #include "feature/hs_common/shared_random_client.h" #include "app/config/config.h" -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/authmode.h" +#include "feature/dirauth/voting_schedule.h" #include "feature/nodelist/networkstatus.h" #include "lib/encoding/binascii.h" @@ -31,6 +32,24 @@ srv_to_control_string(const sr_srv_t *srv) return srv_str; } +/** + * If we have no consensus and we are not an authority, assume that this is + * the voting interval. We should never actually use this: only authorities + * should be trying to figure out the schedule when they don't have a + * consensus. + **/ +#define DEFAULT_NETWORK_VOTING_INTERVAL (3600) + +/* This is an unpleasing workaround for tests. Our unit tests assume that we + * are scheduling all of our shared random stuff as if we were a directory + * authority, but they do not always set V3AuthoritativeDir. + */ +#ifdef TOR_UNIT_TESTS +#define ASSUME_AUTHORITY_SCHEDULING 1 +#else +#define ASSUME_AUTHORITY_SCHEDULING 0 +#endif + /** Return the voting interval of the tor vote subsystem. */ int get_voting_interval(void) @@ -39,40 +58,27 @@ get_voting_interval(void) networkstatus_t *consensus = networkstatus_get_live_consensus(time(NULL)); if (consensus) { + /* Ideally we have a live consensus and we can just use that. */ + interval = (int)(consensus->fresh_until - consensus->valid_after); + } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) { + /* If we don't have a live consensus and we're an authority, + * we should believe our own view of what the schedule ought to be. */ + interval = dirauth_sched_get_configured_interval(); + } else if ((consensus = networkstatus_get_latest_consensus())) { + /* If we're a client, then maybe a latest consensus is good enough? + * It's better than falling back to the non-consensus case. */ interval = (int)(consensus->fresh_until - consensus->valid_after); } else { - /* Same for both a testing and real network. We voluntarily ignore the - * InitialVotingInterval since it complexifies things and it doesn't - * affect the SR protocol. */ - interval = get_options()->V3AuthVotingInterval; + /* We should never be reaching this point, since a client should never + * call this code unless they have some kind of a consensus. All we can + * do is hope that this network is using the default voting interval. */ + tor_assert_nonfatal_unreached_once(); + interval = DEFAULT_NETWORK_VOTING_INTERVAL; } tor_assert(interval > 0); return interval; } -/** Given the current consensus, return the start time of the current round of - * the SR protocol. For example, if it's 23:47:08, the current round thus - * started at 23:47:00 for a voting interval of 10 seconds. - * - * This function uses the consensus voting schedule to derive its results, - * instead of the actual consensus we are currently using, so it should be used - * for voting purposes. */ -time_t -get_start_time_of_current_round(void) -{ - const or_options_t *options = get_options(); - int voting_interval = get_voting_interval(); - /* First, get the start time of the next round */ - time_t next_start = voting_schedule_get_next_valid_after_time(); - /* Now roll back next_start by a voting interval to find the start time of - the current round. */ - time_t curr_start = voting_schedule_get_start_of_next_interval( - next_start - voting_interval - 1, - voting_interval, - options->TestingV3AuthVotingStartOffset); - return curr_start; -} - /* * Public API */ @@ -237,13 +243,27 @@ sr_state_get_start_time_of_current_protocol_run(void) time_t beginning_of_curr_round; /* This function is not used for voting purposes, so if we have a live - consensus, use its valid-after as the beginning of the current round, - otherwise resort to the voting schedule which should always exist. */ + consensus, use its valid-after as the beginning of the current round. + If we have no consensus but we're an authority, use our own + schedule. Otherwise, try using our view of the voting interval + to figure out when the current round _should_ be starting. + */ networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); if (ns) { beginning_of_curr_round = ns->valid_after; + } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) { + beginning_of_curr_round = dirauth_sched_get_cur_valid_after_time(); } else { - beginning_of_curr_round = get_start_time_of_current_round(); + /* voting_interval comes from get_voting_interval(), so if we're in + * this case as a client, we already tried to get the voting interval + * from the latest_consensus and gave a bug warning if we couldn't. + * + * We wouldn't want to look at the latest consensus's valid_after time, + * since that would be out of date. */ + beginning_of_curr_round = voting_sched_get_start_of_interval_after( + approx_time() - voting_interval, + voting_interval, + 0); } /* Get current SR protocol round */ diff --git a/src/feature/hs_common/shared_random_client.h b/src/feature/hs_common/shared_random_client.h index 3031a2bb9a..37a086d590 100644 --- a/src/feature/hs_common/shared_random_client.h +++ b/src/feature/hs_common/shared_random_client.h @@ -38,11 +38,9 @@ time_t sr_state_get_start_time_of_current_protocol_run(void); time_t sr_state_get_start_time_of_previous_protocol_run(void); unsigned int sr_state_get_phase_duration(void); unsigned int sr_state_get_protocol_run_duration(void); -time_t get_start_time_of_current_round(void); #ifdef TOR_UNIT_TESTS #endif /* TOR_UNIT_TESTS */ #endif /* !defined(TOR_SHARED_RANDOM_CLIENT_H) */ - diff --git a/src/feature/nodelist/authcert.c b/src/feature/nodelist/authcert.c index 7bdfabaeab..9c7525b6e2 100644 --- a/src/feature/nodelist/authcert.c +++ b/src/feature/nodelist/authcert.c @@ -46,7 +46,7 @@ #include "feature/nodelist/networkstatus_voter_info_st.h" #include "feature/nodelist/node_st.h" -DECLARE_TYPED_DIGESTMAP_FNS(dsmap_, digest_ds_map_t, download_status_t) +DECLARE_TYPED_DIGESTMAP_FNS(dsmap, digest_ds_map_t, download_status_t) #define DSMAP_FOREACH(map, keyvar, valvar) \ DIGESTMAP_FOREACH(dsmap_to_digestmap(map), keyvar, download_status_t *, \ valvar) diff --git a/src/feature/nodelist/microdesc.c b/src/feature/nodelist/microdesc.c index d32a4ea61e..cf7732b8dc 100644 --- a/src/feature/nodelist/microdesc.c +++ b/src/feature/nodelist/microdesc.c @@ -90,10 +90,10 @@ microdesc_eq_(microdesc_t *a, microdesc_t *b) } HT_PROTOTYPE(microdesc_map, microdesc_t, node, - microdesc_hash_, microdesc_eq_) + microdesc_hash_, microdesc_eq_); HT_GENERATE2(microdesc_map, microdesc_t, node, microdesc_hash_, microdesc_eq_, 0.6, - tor_reallocarray_, tor_free_) + tor_reallocarray_, tor_free_); /************************* md fetch fail cache *****************************/ diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c index cc4b8e1c34..2188e47177 100644 --- a/src/feature/nodelist/networkstatus.c +++ b/src/feature/nodelist/networkstatus.c @@ -66,7 +66,7 @@ #include "feature/dirclient/dirclient_modes.h" #include "feature/dirclient/dlstatus.h" #include "feature/dircommon/directory.h" -#include "feature/dircommon/voting_schedule.h" +#include "feature/dirauth/voting_schedule.h" #include "feature/dirparse/ns_parse.h" #include "feature/hibernate/hibernate.h" #include "feature/hs/hs_dos.h" @@ -2120,7 +2120,7 @@ networkstatus_set_current_consensus(const char *consensus, * the first thing we need to do is recalculate the voting schedule static * object so we can use the timings in there needed by some subsystems * such as hidden service and shared random. */ - voting_schedule_recalculate_timing(options, now); + dirauth_sched_recalculate_timing(options, now); reschedule_dirvote(options); nodelist_set_consensus(c); @@ -2765,3 +2765,47 @@ networkstatus_free_all(void) } } } + +/** Return the start of the next interval of size <b>interval</b> (in + * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always + * starts a fresh interval, and if the last interval of a day would be + * truncated to less than half its size, it is rolled into the + * previous interval. */ +time_t +voting_sched_get_start_of_interval_after(time_t now, int interval, + int offset) +{ + struct tm tm; + time_t midnight_today=0; + time_t midnight_tomorrow; + time_t next; + + tor_gmtime_r(&now, &tm); + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + + if (tor_timegm(&tm, &midnight_today) < 0) { + // LCOV_EXCL_START + log_warn(LD_BUG, "Ran into an invalid time when trying to find midnight."); + // LCOV_EXCL_STOP + } + midnight_tomorrow = midnight_today + (24*60*60); + + next = midnight_today + ((now-midnight_today)/interval + 1)*interval; + + /* Intervals never cross midnight. */ + if (next > midnight_tomorrow) + next = midnight_tomorrow; + + /* If the interval would only last half as long as it's supposed to, then + * skip over to the next day. */ + if (next + interval/2 > midnight_tomorrow) + next = midnight_tomorrow; + + next += offset; + if (next - interval > now) + next -= interval; + + return next; +} diff --git a/src/feature/nodelist/networkstatus.h b/src/feature/nodelist/networkstatus.h index 5e8c8a9e57..ce050aeadc 100644 --- a/src/feature/nodelist/networkstatus.h +++ b/src/feature/nodelist/networkstatus.h @@ -153,6 +153,9 @@ void vote_routerstatus_free_(vote_routerstatus_t *rs); void set_routerstatus_from_routerinfo(routerstatus_t *rs, const node_t *node, const routerinfo_t *ri); +time_t voting_sched_get_start_of_interval_after(time_t now, + int interval, + int offset); #ifdef NETWORKSTATUS_PRIVATE #ifdef TOR_UNIT_TESTS diff --git a/src/feature/nodelist/nodefamily.c b/src/feature/nodelist/nodefamily.c index 7ae8620749..feaa3730dc 100644 --- a/src/feature/nodelist/nodefamily.c +++ b/src/feature/nodelist/nodefamily.c @@ -69,9 +69,9 @@ static HT_HEAD(nodefamily_map, nodefamily_t) the_node_families = HT_INITIALIZER(); HT_PROTOTYPE(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash, - nodefamily_eq) + nodefamily_eq); HT_GENERATE2(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash, - node_family_eq, 0.6, tor_reallocarray_, tor_free_) + node_family_eq, 0.6, tor_reallocarray_, tor_free_); /** * Parse the family declaration in <b>s</b>, returning the canonical diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index b7c7552561..fcf27b1a3a 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -153,9 +153,9 @@ node_id_eq(const node_t *node1, const node_t *node2) return tor_memeq(node1->identity, node2->identity, DIGEST_LEN); } -HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq) +HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq); HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, - 0.6, tor_reallocarray_, tor_free_) + 0.6, tor_reallocarray_, tor_free_); static inline unsigned int node_ed_id_hash(const node_t *node) @@ -170,9 +170,9 @@ node_ed_id_eq(const node_t *node1, const node_t *node2) } HT_PROTOTYPE(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, - node_ed_id_eq) + node_ed_id_eq); HT_GENERATE2(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, - node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_) + node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_); /** The global nodelist. */ static nodelist_t *the_nodelist=NULL; @@ -1240,8 +1240,8 @@ node_get_rsa_id_digest(const node_t *node) * If node is NULL, returns an empty smartlist. * * The smartlist must be freed using link_specifier_smartlist_free(). */ -smartlist_t * -node_get_link_specifier_smartlist(const node_t *node, bool direct_conn) +MOCK_IMPL(smartlist_t *, +node_get_link_specifier_smartlist,(const node_t *node, bool direct_conn)) { link_specifier_t *ls; tor_addr_port_t ap; diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index 87020b81eb..6e854ec879 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -80,8 +80,8 @@ int node_supports_ed25519_hs_intro(const node_t *node); int node_supports_v3_rendezvous_point(const node_t *node); int node_supports_establish_intro_dos_extension(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); -smartlist_t *node_get_link_specifier_smartlist(const node_t *node, - bool direct_conn); +MOCK_DECL(smartlist_t *,node_get_link_specifier_smartlist,(const node_t *node, + bool direct_conn)); void link_specifier_smartlist_free_(smartlist_t *ls_list); #define link_specifier_smartlist_free(ls_list) \ FREE_AND_NULL(smartlist_t, link_specifier_smartlist_free_, (ls_list)) diff --git a/src/feature/nodelist/routerlist.c b/src/feature/nodelist/routerlist.c index 42ce6f4c4e..f4e1215a40 100644 --- a/src/feature/nodelist/routerlist.c +++ b/src/feature/nodelist/routerlist.c @@ -117,9 +117,9 @@ /* Typed wrappers for different digestmap types; used to avoid type * confusion. */ -DECLARE_TYPED_DIGESTMAP_FNS(sdmap_, digest_sd_map_t, signed_descriptor_t) -DECLARE_TYPED_DIGESTMAP_FNS(rimap_, digest_ri_map_t, routerinfo_t) -DECLARE_TYPED_DIGESTMAP_FNS(eimap_, digest_ei_map_t, extrainfo_t) +DECLARE_TYPED_DIGESTMAP_FNS(sdmap, digest_sd_map_t, signed_descriptor_t) +DECLARE_TYPED_DIGESTMAP_FNS(rimap, digest_ri_map_t, routerinfo_t) +DECLARE_TYPED_DIGESTMAP_FNS(eimap, digest_ei_map_t, extrainfo_t) #define SDMAP_FOREACH(map, keyvar, valvar) \ DIGESTMAP_FOREACH(sdmap_to_digestmap(map), keyvar, signed_descriptor_t *, \ valvar) diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c index da0cbb1df4..5f868a9020 100644 --- a/src/feature/relay/dns.c +++ b/src/feature/relay/dns.c @@ -146,9 +146,9 @@ cached_resolve_hash(cached_resolve_t *a) } HT_PROTOTYPE(cache_map, cached_resolve_t, node, cached_resolve_hash, - cached_resolves_eq) + cached_resolves_eq); HT_GENERATE2(cache_map, cached_resolve_t, node, cached_resolve_hash, - cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_) + cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_); /** Initialize the DNS cache. */ static void @@ -268,22 +268,6 @@ has_dns_init_failed(void) return nameserver_config_failed; } -/** Helper: Given a TTL from a DNS response, determine what TTL to give the - * OP that asked us to resolve it, and how long to cache that record - * ourselves. */ -uint32_t -dns_clip_ttl(uint32_t ttl) -{ - /* This logic is a defense against "DefectTor" DNS-based traffic - * confirmation attacks, as in https://nymity.ch/tor-dns/tor-dns.pdf . - * We only give two values: a "low" value and a "high" value. - */ - if (ttl < MIN_DNS_TTL_AT_EXIT) - return MIN_DNS_TTL_AT_EXIT; - else - return MAX_DNS_TTL_AT_EXIT; -} - /** Helper: free storage held by an entry in the DNS cache. */ static void free_cached_resolve_(cached_resolve_t *r) @@ -521,7 +505,7 @@ send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type, uint32_t ttl; buf[0] = answer_type; - ttl = dns_clip_ttl(conn->address_ttl); + ttl = clip_dns_ttl(conn->address_ttl); switch (answer_type) { @@ -593,7 +577,7 @@ send_resolved_hostname_cell,(edge_connection_t *conn, size_t namelen = strlen(hostname); tor_assert(namelen < 256); - ttl = dns_clip_ttl(conn->address_ttl); + ttl = clip_dns_ttl(conn->address_ttl); buf[0] = RESOLVED_TYPE_HOSTNAME; buf[1] = (uint8_t)namelen; @@ -987,25 +971,6 @@ assert_connection_edge_not_dns_pending(edge_connection_t *conn) #endif /* 1 */ } -/** Log an error and abort if any connection waiting for a DNS resolve is - * corrupted. */ -void -assert_all_pending_dns_resolves_ok(void) -{ - pending_connection_t *pend; - cached_resolve_t **resolve; - - HT_FOREACH(resolve, cache_map, &cache_root) { - for (pend = (*resolve)->pending_connections; - pend; - pend = pend->next) { - assert_connection_ok(TO_CONN(pend->conn), 0); - tor_assert(!SOCKET_OK(pend->conn->base_.s)); - tor_assert(!connection_in_array(TO_CONN(pend->conn))); - } - } -} - /** Remove <b>conn</b> from the list of connections waiting for conn-\>address. */ void @@ -1063,7 +1028,7 @@ connection_dns_remove(edge_connection_t *conn) * the resolve for <b>address</b> itself, and remove any cached results for * <b>address</b> from the cache. */ -MOCK_IMPL(void, +MOCK_IMPL(STATIC void, dns_cancel_pending_resolve,(const char *address)) { pending_connection_t *pend; @@ -1338,7 +1303,7 @@ make_pending_resolve_cached(cached_resolve_t *resolve) resolve->ttl_hostname < ttl) ttl = resolve->ttl_hostname; - set_expiry(new_resolve, time(NULL) + dns_clip_ttl(ttl)); + set_expiry(new_resolve, time(NULL) + clip_dns_ttl(ttl)); } assert_cache_ok(); @@ -2051,12 +2016,12 @@ dns_launch_correctness_checks(void) /* Wait a while before launching requests for test addresses, so we can * get the results from checking for wildcarding. */ - if (! launch_event) + if (!launch_event) launch_event = tor_evtimer_new(tor_libevent_get_base(), launch_test_addresses, NULL); timeout.tv_sec = 30; timeout.tv_usec = 0; - if (evtimer_add(launch_event, &timeout)<0) { + if (evtimer_add(launch_event, &timeout) < 0) { log_warn(LD_BUG, "Couldn't add timer for checking for dns hijacking"); } } @@ -2188,7 +2153,7 @@ dns_cache_handle_oom(time_t now, size_t min_remove_bytes) total_bytes_removed += bytes_removed; /* Increase time_inc by a reasonable fraction. */ - time_inc += (MAX_DNS_TTL_AT_EXIT / 4); + time_inc += (MAX_DNS_TTL / 4); } while (total_bytes_removed < min_remove_bytes); return total_bytes_removed; diff --git a/src/feature/relay/dns.h b/src/feature/relay/dns.h index 2b1da8d126..120b75bf8d 100644 --- a/src/feature/relay/dns.h +++ b/src/feature/relay/dns.h @@ -12,29 +12,14 @@ #ifndef TOR_DNS_H #define TOR_DNS_H -/** Lowest value for DNS ttl that a server will give. */ -#define MIN_DNS_TTL_AT_EXIT (5*60) -/** Highest value for DNS ttl that a server will give. */ -#define MAX_DNS_TTL_AT_EXIT (60*60) - -/** How long do we keep DNS cache entries before purging them (regardless of - * their TTL)? */ -#define MAX_DNS_ENTRY_AGE (3*60*60) -/** How long do we cache/tell clients to cache DNS records when no TTL is - * known? */ -#define DEFAULT_DNS_TTL (30*60) +#ifdef HAVE_MODULE_RELAY int dns_init(void); int has_dns_init_failed(void); -void dns_free_all(void); -uint32_t dns_clip_ttl(uint32_t ttl); int dns_reset(void); void connection_dns_remove(edge_connection_t *conn); void assert_connection_edge_not_dns_pending(edge_connection_t *conn); -void assert_all_pending_dns_resolves_ok(void); -MOCK_DECL(void,dns_cancel_pending_resolve,(const char *question)); int dns_resolve(edge_connection_t *exitconn); -void dns_launch_correctness_checks(void); int dns_seems_to_be_broken(void); int dns_seems_to_be_broken_for_ipv6(void); void dns_reset_correctness_checks(void); @@ -42,6 +27,48 @@ size_t dns_cache_total_allocation(void); void dump_dns_mem_usage(int severity); size_t dns_cache_handle_oom(time_t now, size_t min_remove_bytes); +/* These functions are only used within the feature/relay module, and don't + * need stubs. */ +void dns_free_all(void); +void dns_launch_correctness_checks(void); + +#else /* !defined(HAVE_MODULE_RELAY) */ + +#define dns_init() (0) +#define dns_seems_to_be_broken() (0) +#define has_dns_init_failed() (0) +#define dns_cache_total_allocation() (0) + +#define dns_reset_correctness_checks() STMT_NIL + +#define assert_connection_edge_not_dns_pending(conn) \ + ((void)(conn)) +#define dump_dns_mem_usage(severity)\ + ((void)(severity)) +#define dns_cache_handle_oom(now, bytes) \ + ((void)(now), (void)(bytes), 0) + +#define connection_dns_remove(conn) \ + STMT_BEGIN \ + (void)(conn); \ + tor_assert_nonfatal_unreached(); \ + STMT_END + +static inline int +dns_reset(void) +{ + return 0; +} +static inline int +dns_resolve(edge_connection_t *exitconn) +{ + (void)exitconn; + tor_assert_nonfatal_unreached(); + return -1; +} + +#endif /* defined(HAVE_MODULE_RELAY) */ + #ifdef DNS_PRIVATE #include "feature/relay/dns_structs.h" @@ -50,6 +77,7 @@ size_t number_of_configured_nameservers(void); tor_addr_t *configured_nameserver_address(const size_t idx); #endif +MOCK_DECL(STATIC void,dns_cancel_pending_resolve,(const char *question)); MOCK_DECL(STATIC int,dns_resolve_impl,(edge_connection_t *exitconn, int is_resolve,or_circuit_t *oncirc, char **hostname_out, int *made_connection_pending_out, cached_resolve_t **resolve_out)); @@ -74,4 +102,3 @@ launch_resolve,(cached_resolve_t *resolve)); #endif /* defined(DNS_PRIVATE) */ #endif /* !defined(TOR_DNS_H) */ - diff --git a/src/feature/relay/ext_orport.c b/src/feature/relay/ext_orport.c index ce4e043dd7..5568dacf1a 100644 --- a/src/feature/relay/ext_orport.c +++ b/src/feature/relay/ext_orport.c @@ -652,6 +652,77 @@ connection_ext_or_start_auth(or_connection_t *or_conn) return 0; } +/** Global map between Extended ORPort identifiers and OR + * connections. */ +static digestmap_t *orconn_ext_or_id_map = NULL; + +/** Remove the Extended ORPort identifier of <b>conn</b> from the + * global identifier list. Also, clear the identifier from the + * connection itself. */ +void +connection_or_remove_from_ext_or_id_map(or_connection_t *conn) +{ + or_connection_t *tmp; + if (!orconn_ext_or_id_map) + return; + if (!conn->ext_or_conn_id) + return; + + tmp = digestmap_remove(orconn_ext_or_id_map, conn->ext_or_conn_id); + if (!tor_digest_is_zero(conn->ext_or_conn_id)) + tor_assert(tmp == conn); + + memset(conn->ext_or_conn_id, 0, EXT_OR_CONN_ID_LEN); +} + +#ifdef TOR_UNIT_TESTS +/** Return the connection whose ext_or_id is <b>id</b>. Return NULL if no such + * connection is found. */ +or_connection_t * +connection_or_get_by_ext_or_id(const char *id) +{ + if (!orconn_ext_or_id_map) + return NULL; + return digestmap_get(orconn_ext_or_id_map, id); +} +#endif /* defined(TOR_UNIT_TESTS) */ + +/** Deallocate the global Extended ORPort identifier list */ +void +connection_or_clear_ext_or_id_map(void) +{ + digestmap_free(orconn_ext_or_id_map, NULL); + orconn_ext_or_id_map = NULL; +} + +/** Creates an Extended ORPort identifier for <b>conn</b> and deposits + * it into the global list of identifiers. */ +void +connection_or_set_ext_or_identifier(or_connection_t *conn) +{ + char random_id[EXT_OR_CONN_ID_LEN]; + or_connection_t *tmp; + + if (!orconn_ext_or_id_map) + orconn_ext_or_id_map = digestmap_new(); + + /* Remove any previous identifiers: */ + if (conn->ext_or_conn_id && !tor_digest_is_zero(conn->ext_or_conn_id)) + connection_or_remove_from_ext_or_id_map(conn); + + do { + crypto_rand(random_id, sizeof(random_id)); + } while (digestmap_get(orconn_ext_or_id_map, random_id)); + + if (!conn->ext_or_conn_id) + conn->ext_or_conn_id = tor_malloc_zero(EXT_OR_CONN_ID_LEN); + + memcpy(conn->ext_or_conn_id, random_id, EXT_OR_CONN_ID_LEN); + + tmp = digestmap_set(orconn_ext_or_id_map, random_id, conn); + tor_assert(!tmp); +} + /** Free any leftover allocated memory of the ext_orport.c subsystem. */ void ext_orport_free_all(void) diff --git a/src/feature/relay/ext_orport.h b/src/feature/relay/ext_orport.h index dbe89fce18..416c358397 100644 --- a/src/feature/relay/ext_orport.h +++ b/src/feature/relay/ext_orport.h @@ -31,26 +31,56 @@ #define EXT_OR_CONN_STATE_FLUSHING 5 #define EXT_OR_CONN_STATE_MAX_ 5 -int connection_ext_or_start_auth(or_connection_t *or_conn); - -ext_or_cmd_t *ext_or_cmd_new(uint16_t len); +#ifdef HAVE_MODULE_RELAY -#define ext_or_cmd_free(cmd) \ - FREE_AND_NULL(ext_or_cmd_t, ext_or_cmd_free_, (cmd)) +int connection_ext_or_start_auth(or_connection_t *or_conn); -void ext_or_cmd_free_(ext_or_cmd_t *cmd); void connection_or_set_ext_or_identifier(or_connection_t *conn); void connection_or_remove_from_ext_or_id_map(or_connection_t *conn); void connection_or_clear_ext_or_id_map(void); -or_connection_t *connection_or_get_by_ext_or_id(const char *id); - int connection_ext_or_finished_flushing(or_connection_t *conn); int connection_ext_or_process_inbuf(or_connection_t *or_conn); +char *get_ext_or_auth_cookie_file_name(void); +/* (No stub needed for these: they are only called within feature/relay.) */ int init_ext_or_cookie_authentication(int is_enabled); -char *get_ext_or_auth_cookie_file_name(void); void ext_orport_free_all(void); +#else /* !defined(HAVE_MODULE_RELAY) */ + +static inline int +connection_ext_or_start_auth(or_connection_t *conn) +{ + (void)conn; + tor_assert_nonfatal_unreached(); + return -1; +} +static inline int +connection_ext_or_finished_flushing(or_connection_t *conn) +{ + (void)conn; + tor_assert_nonfatal_unreached(); + return -1; +} +static inline int +connection_ext_or_process_inbuf(or_connection_t *conn) +{ + (void)conn; + tor_assert_nonfatal_unreached(); + return -1; +} +#define connection_or_set_ext_or_identifier(conn) \ + ((void)(conn)) +#define connection_or_remove_from_ext_or_id_map(conn) \ + ((void)(conn)) +#define connection_or_clear_ext_or_id_map() \ + STMT_NIL + +#define get_ext_or_auth_cookie_file_name() \ + (NULL) + +#endif /* defined(HAVE_MODULE_RELAY) */ + #ifdef EXT_ORPORT_PRIVATE STATIC int connection_write_ext_or_command(connection_t *conn, uint16_t command, @@ -60,9 +90,11 @@ STATIC int handle_client_auth_nonce(const char *client_nonce, size_t client_nonce_len, char **client_hash_out, char **reply_out, size_t *reply_len_out); + #ifdef TOR_UNIT_TESTS extern uint8_t *ext_or_auth_cookie; extern int ext_or_auth_cookie_is_set; +or_connection_t *connection_or_get_by_ext_or_id(const char *id); #endif #endif /* defined(EXT_ORPORT_PRIVATE) */ diff --git a/src/feature/relay/include.am b/src/feature/relay/include.am index a4c025ae12..813ddb8fb1 100644 --- a/src/feature/relay/include.am +++ b/src/feature/relay/include.am @@ -1,21 +1,22 @@ # Legacy shared relay code: migrate to the relay module over time LIBTOR_APP_A_SOURCES += \ - src/feature/relay/dns.c \ - src/feature/relay/ext_orport.c \ src/feature/relay/onion_queue.c \ - src/feature/relay/router.c \ - src/feature/relay/routerkeys.c \ - src/feature/relay/selftest.c + src/feature/relay/router.c # The Relay module. # ADD_C_FILE: INSERT SOURCES HERE. MODULE_RELAY_SOURCES = \ + src/feature/relay/dns.c \ + src/feature/relay/ext_orport.c \ src/feature/relay/routermode.c \ src/feature/relay/relay_config.c \ + src/feature/relay/relay_handshake.c \ src/feature/relay/relay_periodic.c \ src/feature/relay/relay_sys.c \ + src/feature/relay/routerkeys.c \ + src/feature/relay/selftest.c \ src/feature/relay/transport_config.c # ADD_C_FILE: INSERT HEADERS HERE. @@ -25,6 +26,7 @@ noinst_HEADERS += \ src/feature/relay/ext_orport.h \ src/feature/relay/onion_queue.h \ src/feature/relay/relay_config.h \ + src/feature/relay/relay_handshake.h \ src/feature/relay/relay_periodic.h \ src/feature/relay/relay_sys.h \ src/feature/relay/router.h \ diff --git a/src/feature/relay/onion_queue.c b/src/feature/relay/onion_queue.c index ce2d41b7e1..3cbaa65d28 100644 --- a/src/feature/relay/onion_queue.c +++ b/src/feature/relay/onion_queue.c @@ -49,10 +49,12 @@ typedef struct onion_queue_t { /** 5 seconds on the onion queue til we just send back a destroy */ #define ONIONQUEUE_WAIT_CUTOFF 5 +TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t); +typedef struct onion_queue_head_t onion_queue_head_t; + /** Array of queues of circuits waiting for CPU workers. An element is NULL * if that queue is empty.*/ -static TOR_TAILQ_HEAD(onion_queue_head_t, onion_queue_t) - ol_list[MAX_ONION_HANDSHAKE_TYPE+1] = +static onion_queue_head_t ol_list[MAX_ONION_HANDSHAKE_TYPE+1] = { TOR_TAILQ_HEAD_INITIALIZER(ol_list[0]), /* tap */ TOR_TAILQ_HEAD_INITIALIZER(ol_list[1]), /* fast */ TOR_TAILQ_HEAD_INITIALIZER(ol_list[2]), /* ntor */ diff --git a/src/feature/relay/relay_handshake.c b/src/feature/relay/relay_handshake.c new file mode 100644 index 0000000000..030dc94956 --- /dev/null +++ b/src/feature/relay/relay_handshake.c @@ -0,0 +1,565 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_handshake.c + * @brief Functions to implement the relay-only parts of our + * connection handshake. + * + * Some parts of our TLS link handshake are only done by relays (including + * bridges). Specifically, only relays need to send CERTS cells; only + * relays need to send or receive AUTHCHALLENGE cells, and only relays need to + * send or receive AUTHENTICATE cells. + **/ + +#include "orconfig.h" +#include "core/or/or.h" +#include "feature/relay/relay_handshake.h" + +#include "app/config/config.h" +#include "core/or/connection_or.h" +#include "lib/crypt_ops/crypto_rand.h" +#include "trunnel/link_handshake.h" +#include "feature/relay/routerkeys.h" +#include "feature/nodelist/torcert.h" + +#include "core/or/or_connection_st.h" +#include "core/or/or_handshake_certs_st.h" +#include "core/or/or_handshake_state_st.h" +#include "core/or/var_cell_st.h" + +#include "lib/tls/tortls.h" +#include "lib/tls/x509.h" + +/** 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>. + * (If <b>cert</b> is NULL, take no action.) */ +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>. (If <b>cert</b> is NULL, take no action.) */ +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); +} + +#ifdef TOR_UNIT_TESTS +int certs_cell_ed25519_disabled_for_testing = 0; +#else +#define certs_cell_ed25519_disabled_for_testing 0 +#endif + +/** 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 *global_link_cert = NULL, *id_cert = NULL; + tor_x509_cert_t *own_link_cert = NULL; + var_cell_t *cell; + + 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, + &global_link_cert, &id_cert) < 0) + return -1; + + if (conn_in_server_mode) { + own_link_cert = tor_tls_get_own_cert(conn->tls); + } + tor_assert(id_cert); + + certs_cell = certs_cell_new(); + + /* Start adding certs. First the link cert or auth1024 cert. */ + if (conn_in_server_mode) { + tor_assert_nonfatal(own_link_cert); + add_x509_cert(certs_cell, + OR_CERT_TYPE_TLS_LINK, own_link_cert); + } else { + tor_assert(global_link_cert); + add_x509_cert(certs_cell, + OR_CERT_TYPE_AUTH_1024, global_link_cert); + } + + /* 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) { + tor_assert_nonfatal(conn->handshake_state->own_link_cert || + certs_cell_ed25519_disabled_for_testing); + add_ed25519_cert(certs_cell, + CERTTYPE_ED_SIGN_LINK, + conn->handshake_state->own_link_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); + tor_x509_cert_free(own_link_cert); + + return 0; +} + +#ifdef TOR_UNIT_TESTS +int testing__connection_or_pretend_TLSSECRET_is_supported = 0; +#else +#define testing__connection_or_pretend_TLSSECRET_is_supported 0 +#endif + +/** 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: +#ifdef HAVE_WORKING_TOR_TLS_GET_TLSSECRETS + return 1; +#else + return testing__connection_or_pretend_TLSSECRET_is_supported; +#endif + 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 +connection_or_send_auth_challenge_cell(or_connection_t *conn) +{ + var_cell_t *cell = NULL; + int r = -1; + tor_assert(conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3); + + if (! conn->handshake_state) + return -1; + + auth_challenge_cell_t *ac = auth_challenge_cell_new(); + + tor_assert(sizeof(ac->challenge) == 32); + crypto_rand((char*)ac->challenge, sizeof(ac->challenge)); + + if (authchallenge_type_is_supported(AUTHTYPE_RSA_SHA256_TLSSECRET)) + 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); */ + if (authchallenge_type_is_supported(AUTHTYPE_ED25519_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) { + /* 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); + r = 0; + + done: + var_cell_free(cell); + auth_challenge_cell_free(ac); + + return r; +} + +/** Compute the main body of an AUTHENTICATE cell that a client can use + * 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 + * determined by the rest of the handshake, and which match the provided value + * exactly. + * + * If <b>server</b> is false and <b>signing_key</b> is NULL, calculate the + * first V3_AUTH_BODY_LEN bytes of the authenticator (that is, everything + * that should be signed), but don't actually sign it. + * + * If <b>server</b> is false and <b>signing_key</b> is provided, calculate the + * entire authenticator, signed with <b>signing_key</b>. + * + * Return the length of the cell body on success, and -1 on failure. + */ +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) +{ + auth1_t *auth = NULL; + auth_ctx_t *ctx = auth_ctx_new(); + var_cell_t *result = NULL; + int old_tlssecrets_algorithm = 0; + const char *authtype_str = NULL; + + int 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), authtype_str, 8); + + { + const tor_x509_cert_t *id_cert=NULL; + const common_digests_t *my_digests, *their_digests; + const uint8_t *my_id, *their_id, *client_id, *server_id; + if (tor_tls_get_my_certs(server, NULL, &id_cert)) + goto err; + my_digests = tor_x509_cert_get_id_digests(id_cert); + their_digests = + 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]; + their_id = (uint8_t*)their_digests->d[DIGEST_SHA256]; + + client_id = server ? their_id : my_id; + server_id = server ? my_id : their_id; + + /* Client ID digest: 32 octets. */ + memcpy(auth->cid, client_id, 32); + + /* Server ID digest: 32 octets. */ + 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) { + server_d = conn->handshake_state->digest_sent; + client_d = conn->handshake_state->digest_received; + } else { + client_d = conn->handshake_state->digest_sent; + server_d = conn->handshake_state->digest_received; + } + + /* Server log digest : 32 octets */ + crypto_digest_get_digest(server_d, (char*)auth->slog, 32); + + /* Client log digest : 32 octets */ + crypto_digest_get_digest(client_d, (char*)auth->clog, 32); + } + + { + /* Digest of cert used on TLS link : 32 octets. */ + tor_x509_cert_t *cert = NULL; + if (server) { + cert = tor_tls_get_own_cert(conn->tls); + } else { + cert = tor_tls_get_peer_cert(conn->tls); + } + if (!cert) { + log_warn(LD_OR, "Unable to find cert when making %s data.", + authtype_str); + goto err; + } + + memcpy(auth->scert, + tor_x509_cert_get_cert_digests(cert)->d[DIGEST_SHA256], 32); + + tor_x509_cert_free(cert); + } + + /* HMAC of clientrandom and serverrandom using master key : 32 octets */ + if (old_tlssecrets_algorithm) { + if (tor_tls_get_tlssecrets(conn->tls, auth->tlssecrets) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_OR, "Somebody asked us for an older TLS " + "authentication method (AUTHTYPE_RSA_SHA256_TLSSECRET) " + "which we don't support."); + } + } else { + char label[128]; + tor_snprintf(label, sizeof(label), + "EXPORTER FOR TOR TLS CLIENT BINDING %s", authtype_str); + int r = tor_tls_export_key_material(conn->tls, auth->tlssecrets, + auth->cid, sizeof(auth->cid), + label); + if (r < 0) { + if (r != -2) + log_warn(LD_BUG, "TLS key export failed for unknown reason."); + // If r == -2, this was openssl bug 7712. + goto err; + } + } + + /* 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) { + /* 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) { + /* 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->payload_len = (tmp->end_of_signed - result->payload); + + auth1_free(tmp); + if (len2 != len) { + /* LCOV_EXCL_START */ + log_warn(LD_BUG, "Mismatched length when re-parsing AUTH1 data."); + goto err; + /* LCOV_EXCL_STOP */ + } + goto done; + } + + 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]; + crypto_digest256(d, (char*)out, len, DIGEST_SHA256); + int siglen = crypto_pk_private_sign(signing_key, + (char*)auth1_getarray_sig(auth), + auth1_getlen_sig(auth), + d, 32); + if (siglen < 0) { + log_warn(LD_OR, "Unable to sign AUTH1 data."); + goto err; + } + + auth1_setlen_sig(auth, siglen); + } + + 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 */ + } + 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: + var_cell_free(result); + result = NULL; + done: + auth1_free(auth); + auth_ctx_free(ctx); + return result; +} + +/** Send an AUTHENTICATE cell on the connection <b>conn</b>. Return 0 on + * success, -1 on failure */ +MOCK_IMPL(int, +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(); + /* 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 (! authchallenge_type_is_supported(authtype)) { + log_warn(LD_BUG, "Tried to send authenticate cell with unknown " + "authentication type %d", authtype); + return -1; + } + + cell = connection_or_compute_authenticate_cell_body(conn, + authtype, + pk, + get_current_auth_keypair(), + 0 /* not server */); + if (! cell) { + log_fn(LOG_PROTOCOL_WARN, LD_NET, "Unable to compute authenticate cell!"); + return -1; + } + connection_or_write_var_cell_to_buf(cell, conn); + var_cell_free(cell); + + return 0; +} diff --git a/src/feature/relay/relay_handshake.h b/src/feature/relay/relay_handshake.h new file mode 100644 index 0000000000..99a658cbcc --- /dev/null +++ b/src/feature/relay/relay_handshake.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file relay_handshake.h + * @brief Header for feature/relay/relay_handshake.c + **/ + +#ifndef TOR_CORE_OR_RELAY_HANDSHAKE_H +#define TOR_CORE_OR_RELAY_HANDSHAKE_H + +#ifdef HAVE_MODULE_RELAY +struct ed25519_keypair_t; + +int connection_or_send_certs_cell(or_connection_t *conn); +int connection_or_send_auth_challenge_cell(or_connection_t *conn); + +var_cell_t *connection_or_compute_authenticate_cell_body( + or_connection_t *conn, + const int authtype, + crypto_pk_t *signing_key, + const struct ed25519_keypair_t *ed_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); + +MOCK_DECL(int,connection_or_send_authenticate_cell, + (or_connection_t *conn, int type)); + +#ifdef TOR_UNIT_TESTS +extern int certs_cell_ed25519_disabled_for_testing; +#endif +#else /* !defined(HAVE_MODULE_RELAY) */ + +static inline int +connection_or_send_certs_cell(or_connection_t *conn) +{ + (void)conn; + tor_assert_nonfatal_unreached(); + return -1; +} +static inline int +connection_or_send_auth_challenge_cell(or_connection_t *conn) +{ + (void)conn; + tor_assert_nonfatal_unreached(); + return -1; +} + +static inline var_cell_t * +connection_or_compute_authenticate_cell_body( + or_connection_t *conn, + const int authtype, + crypto_pk_t *signing_key, + const struct ed25519_keypair_t *ed_signing_key, + int server) +{ + (void)conn; + (void)authtype; + (void)signing_key; + (void)ed_signing_key; + (void)server; + tor_assert_nonfatal_unreached(); + return NULL; +} + +#define authchallenge_type_is_supported(t) (0) +#define authchallenge_type_is_better(a, b) (0) + +static inline int +connection_or_send_authenticate_cell(or_connection_t *conn, int type) +{ + (void)conn; + (void)type; + tor_assert_nonfatal_unreached(); + return -1; +} + +#ifdef TOR_UNIT_TESTS +extern int certs_cell_ed25519_disabled_for_testing; +#endif + +#endif /* defined(HAVE_MODULE_RELAY) */ + +#endif /* !defined(TOR_CORE_OR_RELAY_HANDSHAKE_H) */ diff --git a/src/feature/relay/router.h b/src/feature/relay/router.h index 782609d8ab..2e07df2e88 100644 --- a/src/feature/relay/router.h +++ b/src/feature/relay/router.h @@ -117,7 +117,6 @@ const char *routerinfo_err_to_string(int err); int routerinfo_err_is_transient(int err); void router_reset_warnings(void); -void router_reset_reachability(void); void router_free_all(void); #ifdef ROUTER_PRIVATE diff --git a/src/feature/relay/routerkeys.h b/src/feature/relay/routerkeys.h index c2475f195f..2b5f03a2a3 100644 --- a/src/feature/relay/routerkeys.h +++ b/src/feature/relay/routerkeys.h @@ -11,6 +11,8 @@ #include "lib/crypt_ops/crypto_ed25519.h" +#ifdef HAVE_MODULE_RELAY + const ed25519_public_key_t *get_master_identity_key(void); MOCK_DECL(const ed25519_keypair_t *, get_master_signing_keypair,(void)); MOCK_DECL(const struct tor_cert_st *, get_master_signing_key_cert,(void)); @@ -24,6 +26,7 @@ void get_master_rsa_crosscert(const uint8_t **cert_out, int router_ed25519_id_is_me(const ed25519_public_key_t *id); +/* These are only used by router.c */ struct tor_cert_st *make_ntor_onion_key_crosscert( const curve25519_keypair_t *onion_key, const ed25519_public_key_t *master_id_key, @@ -42,6 +45,85 @@ int generate_ed_link_cert(const or_options_t *options, time_t now, int force); void routerkeys_free_all(void); +#else /* !defined(HAVE_MODULE_RELAY) */ + +#define router_ed25519_id_is_me(id) \ + ((void)(id), 0) + +static inline void * +relay_key_is_unavailable_(void) +{ + tor_assert_nonfatal_unreached(); + return NULL; +} +#define relay_key_is_unavailable(type) \ + ((type)(relay_key_is_unavailable_())) + +// Many of these can be removed once relay_handshake.c is relay-only. +#define get_current_auth_keypair() \ + relay_key_is_unavailable(const ed25519_keypair_t *) +#define get_master_signing_keypair() \ + relay_key_is_unavailable(const ed25519_keypair_t *) +#define get_current_link_cert_cert() \ + relay_key_is_unavailable(const struct tor_cert_st *) +#define get_current_auth_key_cert() \ + relay_key_is_unavailable(const struct tor_cert_st *) +#define get_master_signing_key_cert() \ + relay_key_is_unavailable(const struct tor_cert_st *) +#define get_master_rsa_crosscert(cert_out, size_out) \ + STMT_BEGIN \ + tor_assert_nonfatal_unreached(); \ + *(cert_out) = NULL; \ + *(size_out) = 0; \ + STMT_END +#define get_master_identity_key() \ + relay_key_is_unavailable(const ed25519_public_key_t *) + +#define generate_ed_link_cert(options, now, force) \ + ((void)(options), (void)(now), (void)(force), 0) +#define should_make_new_ed_keys(options, now) \ + ((void)(options), (void)(now), 0) + +// These can get removed once router.c becomes relay-only. +static inline struct tor_cert_st * +make_ntor_onion_key_crosscert(const curve25519_keypair_t *onion_key, + const ed25519_public_key_t *master_id_key, + time_t now, time_t lifetime, + int *sign_out) +{ + (void)onion_key; + (void)master_id_key; + (void)now; + (void)lifetime; + (void)sign_out; + tor_assert_nonfatal_unreached(); + return NULL; +} +static inline uint8_t * +make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, + const ed25519_public_key_t *master_id_key, + const crypto_pk_t *rsa_id_key, + int *len_out) +{ + (void)onion_key; + (void)master_id_key; + (void)rsa_id_key; + (void)len_out; + tor_assert_nonfatal_unreached(); + return NULL; +} + +/* This calls is used outside of relay mode, but only to implement + * CMD_KEY_EXPIRATION */ +#define log_cert_expiration() \ + (puts("Not available: Tor has been compiled without relay support"), 0) +/* This calls is used outside of relay mode, but only to implement + * CMD_KEYGEN. */ +#define load_ed_keys(x,y) \ + (puts("Not available: Tor has been compiled without relay support"), 0) + +#endif /* defined(HAVE_MODULE_RELAY) */ + #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); diff --git a/src/feature/relay/selftest.h b/src/feature/relay/selftest.h index 94f305f203..f3dd698bb7 100644 --- a/src/feature/relay/selftest.h +++ b/src/feature/relay/selftest.h @@ -12,6 +12,7 @@ #ifndef TOR_SELFTEST_H #define TOR_SELFTEST_H +#ifdef HAVE_MODULE_RELAY struct or_options_t; int check_whether_orport_reachable(const struct or_options_t *options); int check_whether_dirport_reachable(const struct or_options_t *options); @@ -20,5 +21,37 @@ void router_do_reachability_checks(int test_or, int test_dir); void router_orport_found_reachable(void); void router_dirport_found_reachable(void); void router_perform_bandwidth_test(int num_circs, time_t now); +void router_reset_reachability(void); + +#else /* !defined(HAVE_MODULE_RELAY) */ + +#define check_whether_orport_reachable(opts) \ + ((void)(opts), 0) +#define check_whether_dirport_reachable(opts) \ + ((void)(opts), 0) + +#define router_orport_found_reachable() \ + STMT_NIL +#define router_dirport_found_reachable() \ + STMT_NIL +#define router_reset_reachability() \ + STMT_NIL + +static inline void +router_do_reachability_checks(int test_or, int test_dir) +{ + (void)test_or; + (void)test_dir; + tor_assert_nonfatal_unreached(); +} +static inline void +router_perform_bandwidth_test(int num_circs, time_t now) +{ + (void)num_circs; + (void)now; + tor_assert_nonfatal_unreached(); +} + +#endif /* defined(HAVE_MODULE_RELAY) */ #endif /* !defined(TOR_SELFTEST_H) */ diff --git a/src/feature/stats/geoip_stats.c b/src/feature/stats/geoip_stats.c index 3228b18973..f9a2f19d2e 100644 --- a/src/feature/stats/geoip_stats.c +++ b/src/feature/stats/geoip_stats.c @@ -146,9 +146,9 @@ clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b) } HT_PROTOTYPE(clientmap, clientmap_entry_t, node, clientmap_entry_hash, - clientmap_entries_eq) + clientmap_entries_eq); HT_GENERATE2(clientmap, clientmap_entry_t, node, clientmap_entry_hash, - clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_) + clientmap_entries_eq, 0.6, tor_reallocarray_, tor_free_); #define clientmap_entry_free(ent) \ FREE_AND_NULL(clientmap_entry_t, clientmap_entry_free_, ent) @@ -484,9 +484,9 @@ dirreq_map_ent_hash(const dirreq_map_entry_t *entry) } HT_PROTOTYPE(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash, - dirreq_map_ent_eq) + dirreq_map_ent_eq); HT_GENERATE2(dirreqmap, dirreq_map_entry_t, node, dirreq_map_ent_hash, - dirreq_map_ent_eq, 0.6, tor_reallocarray_, tor_free_) + dirreq_map_ent_eq, 0.6, tor_reallocarray_, tor_free_); /** Helper: Put <b>entry</b> into map of directory requests using * <b>type</b> and <b>dirreq_id</b> as key parts. If there is diff --git a/src/feature/stats/rephist.c b/src/feature/stats/rephist.c index d229c755b4..71e2e00086 100644 --- a/src/feature/stats/rephist.c +++ b/src/feature/stats/rephist.c @@ -2285,9 +2285,9 @@ bidi_map_ent_hash(const bidi_map_entry_t *entry) } HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, - bidi_map_ent_eq) + bidi_map_ent_eq); HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash, - bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_) + bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_); /* DOCDOC bidi_map_free */ static void |